#!/usr/X386/bin/wish -f
set manx(version) 1.5
set man(zcat) zcat
set man(compress) gzip
set man(format) {gtbl | gneqn | gnroff -man | col}
set man(print) {gtroff -man}
set man(catprint) lpr
set man(mandir) man




set sbx(boilerplate) {
Searchbox
Copyright (c) 1993  T.A. Phelps

Permission to use, copy, modify, and distribute this software and its
documentation for educational, research and non-profit purposes, 
without fee, and without a written agreement is hereby granted, 
provided that the above copyright notice and the following three 
paragraphs appear in all copies.  

Permission to incorporate this software into commercial products may 
be obtained from the Office of Technology Licensing, 2150 Shattuck 
Avenue, Suite 510, Berkeley, CA  94704. 

IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 
SUCH DAMAGE.

THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 
PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 
ENHANCEMENTS, OR MODIFICATIONS.
}



set sb(key,*) "add modifiers in this order: M, C, A, S"


set sb(key,C-x) exchangepointandmark
set sb(key,C-space) setmark
set sb(key,-Delete) pageup
set sb(key,M-v) pageup
set sb(key,-space) pagedown
set sb(key,C-v) pagedown
set sb(key,MS-less) pagestart
set sb(key,MS-greater) pageend
set sb(key,-Escape) searchkill
set sb(key,C-g) searchkill
set sb(key,C-n) nextline
set sb(key,-Down) nextline
set sb(key,C-p) prevline
set sb(key,-Up) prevline
set sb(key,C-s) incrsearch
set sb(key,C-r) revincrsearch
set sb(key,MS-question) help
set sb(key,-Help) help


set sb(key,C-f) pagedown
set sb(key,C-b) pageup





proc searchboxSearch {str regexp casesen tag w wv {wmsg ""} {wcnt ""}} {
   global sbx

   if {$str==""} {
      winstderr $wmsg "Nothing to search for!  Type a regexp and click `Search'."
      return
   }

   if {$regexp} {set type regexp} {set type ""}
   set cnt [${type}TextSearch $w $str $tag $casesen]
   if {$cnt==-1} {winstderr $wmsg "Malformed regular expression."; return}
   if {$cnt==1} {set txt "$cnt match"} {set txt "$cnt matches"}
   winstdout $wcnt $txt

   searchboxNext $tag $w $wv $wmsg [expr [lindex [$wv get] 2]+1].0
}




proc searchboxNext {tag w wv {wmsg ""} {next ""}} {
   global sbx

   if {$next==""} {set next [expr [lindex [$wv get] 3]+1+1].0}
   set tmp [$w tag nextrange $tag $next]
   if {$tmp==""} {
      winstdout $wmsg "No more matches; restarting at top."
      $w yview 0
   } else {
      $w yview -pickplace [lindex $tmp 0]
      update
      winstdout $wmsg "Viewing lines [lindex [$wv get] 2] to [lindex [$wv get] 3]."
   }
}




proc searchboxKeyNav {m k casesen w wv {wmsg ""} {firstmode 0}} {
   global sb sbx

   if {[regexp {(Shift|Control|Meta)_.} $k]} return
   if {![info exists sbx(try$w)]} {
      set sbx(try$w) 0
      set sbx(vect$w) 1
      set sbx(lastkeys$w) [set sbx(lastkeys-old$w) ""]
      set sbx(inmediares$w) 0
   }


   set minele 1
   if {[winfo class $w]=="Text"} {set off 1; scan [$w index end] %d numLines} \
   elseif {[winfo class $w]=="Listbox"} {set off 0; set numLines [$w size]; set minele 0}
   scan [$wv get] "%d %d %d %d" total window first last


   if {!$firstmode && ($sbx(try$w) || $sbx(lastkeys$w)!="")} {
      switch -exact -- $k {
         space {set k " "}
         Delete {
            set k ""
            set last [expr [string length $sbx(lastkeys$w)]-2]
            set sbx(lastkeys$w) [string range $sbx(lastkeys$w) 0 $last]
            set sbx(try$w) 1
         }
         default { if {$m==""||$m=="S"} {set k [name2char $k]} }
      }
   }


   set mk $m-$k
   if {[info exists sb(key,$mk)]} {set op $sb(key,$mk)} {set op default}
   if {$op=="searchkill" && $sbx(inmediares$w)} {set $sbx(inmediares$w) 0}
   switch -exact -- $op {
      help {$w.occ.m invoke Help; return}
      exchangepointandmark {
         set tmp [expr [lindex [$wv get] 2]+1].0
         $w yview xmark
         update
         $w mark set xmark $tmp
      }
      setmark {$w mark set xmark [expr [lindex [$wv get] 2]+1].0}
      pageup {$w yview [max [expr $first-$window+1] 0]}
      pagedown {$w yview [min [expr $first+$window-1] [expr $numLines-$window]]}
      pagestart {$w yview 0}
      pageend {$w yview [max [expr $numLines-$window] 0]}
      searchkill {
         if {$sbx(lastkeys$w)!=""} {set sbx(lastkeys-old$w) $sbx(lastkeys$w)}
         set sbx(lastkeys$w) ""; set sbx(try$w) 0; winstdout $wmsg ""
      }
      C-l {$w yview [max [expr $first-$window/2] 0]}
      nextline {$w yview [min [expr $first+1] [expr $numLines-$window]]}
      prevline {$w yview [max [expr $first-1] 0]}
      default {
         if {$op=="incrsearch"} {
            if {$sbx(try$w)&&$sbx(lastkeys$w)==""} {set sbx(lastkeys$w) $sbx(lastkeys-old$w)}
            incr off; set sbx(vect$w) 1; set sbx(try$w) 1
         } elseif {$op=="revincrsearch"} {
            if {$sbx(try$w)&&$sbx(lastkeys$w)==""} {set sbx(lastkeys$w) $sbx(lastkeys-old$w)}
            incr off -1; set sbx(vect$w) -1; set sbx(try$w) 1
         } elseif {$firstmode} {
            set sbx(lastkeys$w) $k
            set sbx(try$w) 1
         } elseif {$sbx(try$w)} {
	    append sbx(lastkeys$w) $k
         } else return

         if {$firstmode} {
            set curline 0
         } else {
            set curline [lindex [$wv get] 2]
            winstdout $wmsg "Searching for \"$sbx(lastkeys$w)\" ..."; update idletasks
         }
         if {[set keys $sbx(lastkeys$w)]==""} return

         set klen [string length $keys]
         set found -1
         set sbx(inmediares$w) 1
         for {set i [expr $curline+$off]} \
             {$sbx(inmediares$w) && $minele<=$i && $i<=$numLines && $sbx(try$w)} \
             {incr i $sbx(vect$w)} {
            if {[expr $i%250]==0} update
            if {$firstmode} {
               if {"$keys"=="[$w get $i.0 $i.$klen]"} {set found 0; break}
            } elseif {!$casesen} {
               if {[set found [string first [string tolower $keys] \
                  [string tolower [$w get $i.0 "$i.0 lineend"]]]]!=-1} \
                  break;
            } elseif {[set found [string first $keys [$w get $i.0 "$i.0 lineend"]]]!=-1} {
               break
            }
         }
	 if {!$sbx(inmediares$w)} {return}
	 set sbx(inmediares$w) 0

         if {$sbx(try$w)==0} {
         } elseif {$found!=-1} {
            $w yview [expr $i-1]; update idletasks
            if {!$firstmode} {winstdout $wmsg "\"$keys\" found on line $i"}
         } elseif {$op=="incrsearch"} {
            $w yview 0
            winstdout $wmsg "No more matches found; restarting search at top."
         } elseif {$op=="revincrsearch"} {
             $w yview $total
	    winstdout $wmsg "No more match found; restarting search at bottom."
         } else {
            winstdout $wmsg "\"$keys\" not found"
            set sbx(try$w) 0
         }
      }
   }
}




proc searchboxSaveConfig {fid} {
   global sb sbx

   puts $fid "#\n# SearchBox\n#\n"
   foreach i [lsort [array names sb]] {
      puts $fid "set sb($i) [list $sb($i)]"
   }
   puts $fid "\n"
}




proc TextSearch {w string tag {case 1}} {
   set cnt 0

    $w tag remove $tag 0.0 end
    scan [$w index end] %d numLines
    set l [string length $string]
    if {!$case} {set string [string tolower $string]}
    for {set i 1} {$i <= $numLines} {incr i} {
	set match [$w get $i.0 $i.1000]
	if {!$case} {set match [string tolower $match]}
	if {[string first $string $match] == -1} {
	    continue
	}
	set line [$w get $i.0 $i.1000]
	set offset 0
	while 1 {
	    set index [string first $string $line]
	    if {$index < 0} {
		break
	    }
	    incr offset $index
	    $w tag add $tag $i.[expr $offset] $i.[expr $offset+$l]
            incr cnt
	    incr offset $l
	    # below bug fix from mkSearch.tcl
	    set line [string range $line [expr $index+$l] 1000]
	}
    }
   return $cnt
}



proc regexpTextSearch {w string tag {case 1}} {
   set cnt 0
   if {$case} {set case ""} {set case "-nocase"}
   if {[catch {regexp $string bozomaniac}]} {return -1}

    $w tag remove $tag 0.0 end
    scan [$w index end] %d numLines

    for {set i 1} {$i <= $numLines} {incr i} {
      set line [$w get $i.0 $i.1000]
      set offset 0
      while 1 {
         if {![eval regexp $case -indices {"$string"} {"$line"} match]} break
         scan $match "%d %d" index iend
         $w tag add $tag $i.[expr $offset+$index] $i.[expr $offset+$iend+1]
         set line [string range $line [expr $iend+1] end]
         incr offset [expr $iend+1]
         incr cnt
      }
   }
   return $cnt
}



if {[info procs winstdout]==""} {

proc winstderr {w msg} {
   if {![winfo exists $w]} return

   set fg [lindex [$w configure -foreground] 4]
   set bg [lindex [$w configure -background] 4]

   winstdout $w $msg
   $w configure -foreground $bg -background $fg
   update idletasks; after 500
   $w configure -foreground $fg -background $bg
}

proc winstdout {w msg} {
   global winout

   if {![winfo exists $w]} return
   $w configure -text $msg
   set winout(lastMessage$w) $msg
}

}




proc pipeexp {p} {
   set p [string trim $p]

   set expp ""
   foreach i $p {
      if {[regexp {^[.~/$]} $i]} {lappend expp [fileexp $i]} \
      else {lappend expp $i}
   }
   return $expp
}

proc fileexp {f} {
   global env

   set f [string trim $f]
   set l [string length $f]
   set expf ""

   set dir [pwd]
   foreach i [split $f /] {
      switch -glob $i {
         "" {set dir ""}
         ~  {set dir $env(HOME)}
	 $* {set val $env([string trim [string range $i 1 end] ()])
             if {[string match /* $val]} {set dir $val} {append expf /$val)}}
         .  {set dir $dir}
	 .. {set dir [file dirname $dir]}
	 default {append expf /$i}
      }
   }

   return $dir$expf
}



proc filecomplete {f} {
   set expf [fileexp [file dirname $f]]/[file tail $f]
   set tail [file tail $f]
   set posn [string last $tail $f]
   if [string match */ $f] {append expf /; set tail /; set posn [string length $f]}
   set l [glob -nocomplain $expf*]
   set ll [llength $l]

   if {!$ll} {
      set tail ""
   } elseif {$ll==1} {
      set tail [file tail $l]
      if {[file isdirectory $l]} {append tail /}
   } else {
      set lf [lfirst $l]; set lfl [string length $lf]
      set last $lfl
      set ni [expr [string last / $lf]+1]
      foreach i $l {
         set il [string length $i]
         for {set j $ni} {$j<=$last} {incr j} {
            if {[string range $lf $j $j]!=[string range $i $j $j]} break
         }
         set last [min $last [expr $j-1]]
      }
      set tail [file tail [string range [lfirst $l] 0 $last]]
   }

   if {$posn>0 && $ll} {
      set tail [string range $f 0 [expr $posn-1]]$tail
   }

   if {$ll<2} {return $tail} {return "$tail $l"}
}



proc isalpha {c} {
   return [regexp -nocase {[a-z]} $c]
}
proc isnum {c} {return [expr [string first $c "123456790"]!=-1]}
proc isalphanum {c} {return [expr [isalpha $c]||[isnum $c]]}

proc stringicap {s} {return [string toupper [string range $s 0 0]][string range $s 1 end]}

proc tr {s c1 c2} {
   set l2 ""
   foreach i [split $s $c1] {
      append l2 $i $c2
   }
   return [string trimright $l2 $c2]
}


proc bolg {f {l ""}} {
   if {$l==""} {global file; set l $file(globList)}

   foreach i $l {
      if [regsub ([glob -nocomplain $i])(.*) $f "$i\\2" short] {return $short}
   }
   return $f
}


proc setinsert {l i e} {
   return [linsert [lfilter $e $l] $i $e]
}


proc unsplit {l c} {
   foreach i $l {
      append l2 $i $c
   }
   return [string range $l2 0 [expr [string length $l2]-2]]
}

proc bytes2prefix {x} {
   set k 1024
   set mb [expr $k*$k]
   set gb [expr $k*$mb]
   set bp 10

   return [
   if {$x<$k} {format " $x bytes"} \
   elseif {$x<[expr $k*$bp]} {format "%0.1f K" [expr ($x+0.0)/$k]} \
   elseif {$x<$mb} {format "[expr $x/$k] K"} \
   elseif {$x<[expr $mb*$bp]} {format "%0.1f MB" [expr ($x+0.0)/$mb]} \
   elseif {$x<$gb} {format "[expr $x/$mb] MB"} \
   elseif {$x<[expr $gb*$bp]} {format "%0.1f GB" [expr ($x+0.0)/$gb]} \
   else {format "[expr $x/$gb] GB"}
   ]
}

proc bytes2prefix {x} {
   set pfx {bytes KB MB GB TB QB}
   set bp 20
   set k 1024
   set sz $k

   set y BIG
   for {set i 0} {$i<[llength $pfx]} {incr i} {
      if {$x<$sz} {
         set y [format " %0.0f [lindex $pfx $i]" [expr $x/($sz/$k)]]
         break
      } elseif {$x<[expr $sz*$bp]} {
         set y [format " %0.1f [lindex $pfx [expr $i+1]]" [expr ($x+0.0)/$sz]]
         break
      }

      set sz [expr $sz*$k]
   }

   return $y
}




proc quote {x} {return $x}

proc uniqlist {l} {
   set l1 [lsort $l]
   set e ""
   set l2 ""
   foreach i $l1 {
      if {$e!=$i} {
         set e $i
         lappend l2 $e
      }
   }
   return $l2
}

proc uniqilist {l} {
   set l1 [lsort -integer $l]
   set e ""
   set l2 ""
   foreach i $l1 {
      if {$e!=$i} {
         set e $i
         lappend l2 $e
      }
   }
   return $l2
}


proc min {args} {
   set x [lindex $args 0]
   foreach i $args {
      if {$i<$x} {set x $i}
   }
   return $x
}

proc avg {args} {
   set sum 0.0

   if {$args==""} return
   
   foreach i $args {set sum [expr $sum+$i]}
   return [expr ($sum+0.0)/[llength $args]]
}

proc max {args} {
   set x [lindex $args 0]
   foreach i $args {
      if {$i>$x} {set x $i}
   }
   return $x
}

proc abs {x} {
   if {$x<0} {return [expr 0-$x]} {return $x}
}


proc lfirst {l} {return [lindex $l 0]}
proc lsecond {l} {return [lindex $l 1]}
proc lthird {l} {return [lindex $l 2]}
proc lfourth {l} {return [lindex $l 3]}
proc lfifth {l} {return [lindex $l 4]}
proc lsixth {l} {return [lindex $l 5]}
proc lseventh {l} {return [lindex $l 6]}
proc lrest {l} {return [lrange $l 1 end]}

proc llast {l} {
   set end [llength $l]
   if {!$end} {return ""}
   return [lindex $l [expr $end-1]]
}

proc setappend {l e} {
   return "[lfilter $e $l] $e"
}

proc lfilter {p l} {
   set l2 ""

   foreach i $l {
      if ![string match $p $i] "lappend l2 [list $i]"
   }
   return $l2
}

proc lassoc {l k} {

   foreach i $l {
      if {[lindex $i 0]==$k} {return [lindex $i 1]}
   }
}

proc lbssoc {l k} {

   foreach i $l {
      if {[lindex $i 1]==$k} {return [lindex $i 0]}
   }
}

proc lreverse {l} {
   set l2 ""
   for {set i [expr [llength $l]-1]} {$i>=0} {incr i -1} {
      lappend l2 [lindex $l $i]
   }
   return $l2
}



proc geom2posn {g} {
   regexp {(=?[0-9]+x[0-9]+)([-+]+[0-9]+[-+]+[0-9]+)} $g both d p
   return $p
}






set name2charList {
   minus plus percent ampersand asciitilde at less greater equal
   numbersign dollar asciicircum asterisk quoteleft quoteright
   parenleft parenright bracketleft bracketright braceleft braceright
   semicolon colon question slash bar period underscore backslash
   exclam comma
}

proc name2char {c} {
   global name2charList

   if {[set x [lsearch $name2charList $c]]!=-1} {
       return [string index "-+%&~@<>=#$^*`'()\[\]{};:?/|._\\!," $x]
   } else {return $c}
}

proc key_state2mnemon {n} {
   set mod ""

   if {$n>=8} {append mod M; set n [expr $n-8]}
   if {$n>=4} {append mod C; set n [expr $n-4]}
   if {$n>=2} {append mod A; set n [expr $n-2]}
   if {$n} {append mod S}
   
   return $mod
}

proc lmatch {mode list {pattern ""}} {
   if {$pattern==""} {set pattern $list; set list $mode; set mode -glob}
   return [expr [lsearch $mode $list $pattern]!=-1]
}



proc stringremove {s {c " "}} {
   set s2 ""
   set slen [string length $s]

   for {set i 0} {$i<$slen} {incr i} {
      set sc [string index $s $i]
      if [string match $c $sc]==0 {append s2 $sc}
   }
   return $s2
}

proc tk_listboxNoSelect args {
    foreach w $args {
        bind $w <Button-1> {format x}
	bind $w <B1-Motion> {format x}
	bind $w <Shift-1> {format x}
	bind $w <Shift-B1-Motion> {format x}
    }
}


proc listboxshowS {lb s {first 0} {cnstr yes}} {
   set sz [$lb size]

   for {set i $first} {$i<$sz} {incr i} {
      if [string match $s [$lb get $i]] {
         listboxshowI $lb $i $cnstr
         return $i
      }
   }
   return -1
}

proc listboxshowI {lb high {cnstr yes}} {
   set high [max 0 [min $high [expr [$lb size]-1]]]

   set hb [lindex [split [lindex [$lb configure -geometry] 4] x] 1]
   set hx [max 0 [expr [$lb size]-$hb]]
   if {$cnstr=="yes"} {set hl [expr $high<$hb?0:[min $high $hx]]} {set hl $high}
   $lb select from $high
   $lb yview $hl
}

proc listboxreplace {lb index new} {
   $lb delete $index
   $lb insert $index $new
   $lb select from $index
}



proc listboxmove {l1 l2} {
   listboxcopy $l1 $l2
   $l1 delete 0 end
}

proc listboxcopy {l1 l2} {

   $l2 delete 0 end
   listboxappend $l1 $l2
   catch {$l2 select from [$l1 curselection]}
}

proc listboxappend {l1 l2} {

   set size [$l1 size]

   for {set i 0} {$i<$size} {incr i} {
      $l2 insert end [$l1 get $i]
   }
}



bind Text <B1-Motion> {textb1motion %W @%x,%y}
bind Text <ButtonRelease-1> {set text(txnd) 0}
set text(txnd) 0
set text(delay) 100

proc textb1motion { w loc } {
   global text

   set ypos [lindex [split $loc ","] 1]
   if {$ypos > [winfo height $w]} {
      if {!$text(txnd)} {after $text(delay) textextend $w}
      set text(txnd) 1
      set text(direction) down
   } elseif {$ypos < 0} {
      if {!$text(txnd)} {after $text(delay) textextend $w}
      set text(txnd) 1
      set text(direction) up
   } else {
      set text(txnd) 0
      set text(direction) 0
   }

   if {!$text(txnd)} {
      tk_textSelectTo $w $loc
   }
}

proc textextend { w } {
   global text

   if {$text(txnd)} {
      if {$text(direction) == "down"} {
         tk_textSelectTo $w sel.last+1l
         $w yview -pickplace sel.last+1l
      } elseif {$text(direction) == "up"} {
         tk_textSelectTo $w sel.first-1l
         $w yview -pickplace sel.first-1l
      } else return

      after $text(delay) textextend $w
   }
}



set text(b2-time) 0
set text(b2-x) 0

foreach c {Entry Text} {
   bind $c <Button-2> {+set text(b2-time) %t; set text(b2-x) %x}
}
bind Entry <ButtonRelease-2> {+
   if {[expr abs(%t-$text(b2-time))]<500 && [expr abs(%x-$text(b2-x))]<3} {
      catch {%W insert insert [selection get]; tk_entrySeeCaret %W}
   }
}
bind Text <ButtonRelease-2> {+
   if {[expr abs(%t-$text(b2-time))]<500 && [expr abs(%x-$text(b2-x))]<3} {
      catch {%W insert insert [selection get]; %W yview -pickplace insert}
   }
}


proc emacsbind {w} {
   bind $w <Enter> "focus $w"
   bind $w <Control-KeyPress-d> "$w delete \[$w index insert\]; tk_entrySeeCaret $w"
   bind $w <Control-KeyPress-k> "$w delete \[$w index insert\] end; tk_entrySeeCaret $w"
   bind $w <Control-KeyPress-f> "$w icursor \[expr \[$w index insert\]+1\]; tk_entrySeeCaret $w"
   bind $w <Right> "$w icursor \[expr \[$w index insert\]+1\]; tk_entrySeeCaret $w"
   bind $w <Control-KeyPress-b> "$w icursor \[expr \[$w index insert\]-1\]; tk_entrySeeCaret $w"
   bind $w <Left> "$w icursor \[expr \[$w index insert\]-1\]; tk_entrySeeCaret $w"
   bind $w <Control-KeyPress-a> "$w icursor 0; tk_entrySeeCaret $w"
   bind $w <Control-KeyPress-e> "$w icursor end; tk_entrySeeCaret $w"

   bind $w <Control-KeyPress-h> "
      if \[catch {$w delete sel.first sel.last}\] \
         {$w delete \[expr \[$w index insert\]-1\]}
      tk_entrySeeCaret $w
   "
   bind $w <KeyPress-Delete> "
      if \[catch {$w delete sel.first sel.last}\] \
         {$w delete \[expr \[$w index insert\]-1\]}
      tk_entrySeeCaret $w
   "
   bind $w <KeyPress-BackSpace> "
      if \[catch {$w delete sel.first sel.last}\] \
         {$w delete \[expr \[$w index insert\]-1\]}
      tk_entrySeeCaret $w
   "
   bind $w <KeyPress> "catch {$w delete sel.first sel.last}; [bind Entry <Any-Key>]"
   bind $w <Double-Button-1> "$w select from 0; $w select to end"
}


proc winstderr {w msg} {
   if {![winfo exists $w]} return

   set fg [lindex [$w configure -foreground] 4]
   set bg [lindex [$w configure -background] 4]

   winstdout $w $msg
   $w configure -foreground $bg -background $fg
   update idletasks; after 500
   $w configure -foreground $fg -background $bg
}

proc winstdout {w msg} {
   global winout

   if {![winfo exists $w]} return
   $w configure -text $msg
   set winout(lastMessage$w) $msg
}



proc beep {} {
   puts -nonewline stdout "\007"; flush stdout
}



set manx(boilerplate) {\
A bird?  A plane?  TkMan!  (TkPerson?)
by Tom Phelps

based on Tcl 7.0/Tk 3.3
Compatible with Hewlett-Packard HP-UX, AT&T System V, SunOS, Sun Solaris, DEC Ultrix, SGI IRIX, Linux

Copyright  1993  T. A. Phelps
All Rights Reserved.
University of California, Berkeley
Department of Electrical Engineering and Computer Science
Computer Science Division


The latest version of TkMan is always available by anonymous FTP at \
ftp.cs.Berkeley.EDU in the /ucb/mhgs directory.


PERMISSION IS GRANTED TO DISTRIBUTE THIS SOFTWARE FREELY, BUT ONE MAY NOT \
CHARGE FOR IT OR INCLUDE IT WITH SOFTWARE WHICH IS SOLD.

If you send me bug reports and/or suggestions for new features, include the \
versions of TkMan, Tcl, Tk, X, and UNIX, your machine and X window \
manager names, and a copy of your ~/.tkman file.  First check that values \
changed in the Makefile or source code aren't being unexpectedly \
overridden in ~/.tkman.



============
Introduction
------------

A manual page browser, TkMan offers two major advantages over xman:
hypertext links to other man pages (click on a word in the text which
corresponds to a man page, and you jump there), and better navigation
within long man pages with searches (both incremental and regular
expression) and jumps to section headers.  TkMan also offers some
convenience features, like a user-configurable list of commonly used man
pages, a one-click printout, and integration of `whatis' and `apropos'.
Further, one may highlight, as if with a yellow marker, arbitrary passages
of text in man pages and subsequently jump directly to these passages by
selecting an identifying excerpt from a pulldown menu.  Finally, TkMan
gives one control over the directory-to-menu volume mapping of man pages
with a capability similar to but superior to xman's mandesc in that rather
than forcing all who share a man directory to follow a single organization,
TkMan gives control to the individual.  In fact, one may decide he has no
use for a large set of man pages--say for instance the programmer routines
in volumes 2, 3, 4, 8--and eliminate them from his personal database.

Since man page formatting follows conventions but not rigid standards, not
all man pages can be parsed fully.  However, most yield their section
titles and SEE ALSOs and their emphasized words.  TkMan also tries to
filter out the unsightly page footers and headers put in by `nroff', but
nonstandard formatting can slip by.

First I'll describe how to use TkMan, although I hope that the use of most
features is intuitive.  At the end are my address and UC's disclaimer and
licensing information.



===========
Using TkMan
-----------

= Locating a man page =

There are several ways to identify the manual page you desire.  You can
type its name into the entry box at the top of the screen and press return
or click `man'.  The name may be just the name of the command or may
include a `.n' or `(n)' at the end where `n' specifies in which section to
look.  Man pages are matched using file name globbing (as in `csh'), so you
can use `?' to match any single character, `*' to match any (zero or more)
characters, `[' .. `]' to match any single character in the enclosed class,
and `{' .. `}' to expand the enclosed strings.  If you're running TkMan
from a shell and giving it an initial man page name to load up as an
argument, use this syntax (adequately quoted for protection from the
shell), as opposed to the syntax of the standard `man' command.  Usually
TkMan searches the directories in your MANPATH environment variable for the
man page, but you may instead provide a path name for the man page by
beginning it with `~', `/', `.' or `..'; this is the way to access a man
page which isn't installed in a MANPATH man directory.  Further, other Tcl
interpreters may display a man page in TkMan by `send'ing a message to the
function `manShowMan' with the name of the desired man page, for example
`send tkman manShowMan tcl.n'.  If multiple man page names match the
specification, the first match (as searched for in MANPATH order) is shown
and a pulldown menu appears which contains a list of the other matches.

`apropos' information is available by typing the name and clicking
`apropos' or hitting meta-return (for meta information, of course).  The
output of `apropos' is piped through `sort' and `uniq' to remove
duplicates.  To pass the matches through another filter, simply give the
pipe as in a shell, e.g., `search | grep ^g' (each space character is
significant) returns all the search-related commands which begin with the
letter `g'.

The `Paths' pulldown gives you complete control over which directories of
your MANPATH are searched for man pages and apropos information.  You can
call up a listing of all man pages in a volume through the `Volumes'
pulldown menu and then select one to view by double-clicking on its name.
Typing a letter jumps to the line in the listing starting with that letter
(capital and lower case letters are distinct).  The `all' pseudo volume can
be useful when used with `Paths' to obtain a complete listing of man pages
in, say, one directory tree.

Once you have a man page name in the text display box, whether from
apropos, a volume listing or a reference within another man page, you can
double-click on it to hypertext-jump to it.

The last few man pages you looked at can be accessed directly through the
`History' pulldown menu.  `Shortcuts' lists your personal favorites and is
used just like `History', with the additional options of adding (by
clicking `+') the current man page or removing (`-') it from the list.

(Man pages specified as above are processed through an `nroff' filter.
TkMan can also read raw text from a file or from a command pipeline, which
can then be read, searched and highlighted same as a man page.  To read
from a file, make the first character in the name a `<', as in
`<~/foo.txt'.  To open a pipe, make the first character a `|' (vertical
bar), as in `|gzcat foo.txt.gz' or `|cat ../foo.txt | grep bar' (that's no
space after the first `|', a space before and after any subsequent ones).
After reading a file, the current directory is set to its directory.
Commands are not processed by a shell, but the metacharacters `.', `..',
`~' and `$' (for environment variables), are expanded nonetheless.  Typing
is eased further by file name completion, bound to the escape key.  Lone
files (i.e., not part of a pipe) are automatically uncompressed--no need to
read compressed files through a `zcat' pipe.  It is not expected that
reading raw text will be done much; it is included so the occasional
non-man page documentation may be read from the same environment.
For more sophisticated file browsing, use NBT, my Tcl/Tk-based file
browser, which available from TkMan's home FTP site, mentioned above.)


= Working within a man page =

The `whatis' information for a man page, if any, appears at the top of the
screen.

To the extent it follows convention, the man page is parsed to yield its
section and subsection titles (which are directly available from the
`Sections' pulldown) and references to other man pages from its SEE ALSO
section (`Links' pulldown).  One may jump directly to a section within a
man page or a man page referenced in the SEE ALSO section, respectively, by
selecting the corresponding entry from one of these pulldowns.  It may be
handy to tear off the `Sections' and `Links' menus (by dragging the menu
title with mouse button 2 pressed).

Within a man page or raw text file or pipe, you may added ad hoc
highlighting, as though with a yellow marker (underlining on monochrome
monitors).  Highlighted regions may then be scrolled to directly through
the `Highlights' pulldown menu.  To highlight a region, select the desired
text by clicking button 1, dragging to the far extent of the desired
region, releasing the button, then clicking on the `+' next to
`Highlights'.  To remove any highlights or portions thereof in a region,
select it as before but then click on `-'.  Highlighting information is
persistent across executions of TkMan.

You can move about the man page by using the scrollbar, or typing a number
of key combinations familiar to Emacs aficionados.  Space or C-v page down,
and delete or M-v page up.  C-n and C-p scroll up and down,
respectively, by a single line.  M-< goes to the head and M-> to the tail
of the text.  One may "scan" the page by dragging up and down with the
middle mouse button pressed.  Like Emacs, C-space will mark one's current
location, which can be returned to later with C-x, which exchanges the
then-current position with the saved mark; a second C-x swaps back.

C-s initiates a search.  Subsequently typing a few letters attempts to find
a line with that string, starting its search with the topmost line
currently visible.  A second C-s finds the next match of the string typed
so far.  (If the current search string is empty, a second C-s retrieves the
previous search pattern.) C-r is similar to C-s but searches backwards.
Escape or C-g cancels the search.  This incremental search can be used to
quickly locate a particular command-line option or a particular command in
a group (as in `csh').  At the bottom of the screen, type in a regular
expression to search for and hit return or click `Search' to begin a
search.  Hit `Next' or keep hitting return to search for the next
occurance.  [`Prev' will be added when Tk supports a `tag prevrange'
command.]

The tab key moves the focus from the man page type-in line to the text view
of the man page to the search line and back around.


= Other commands =

The `Occasionals' menu holds commands and options which you probably won't
use much.  The first group in this menu is comprised of commands which you
may invoke several times in a single TkMan session.  `Help' returns to this
information screen.  Although virtually made obsolete by TkMan, `Print'
makes a copy of the current man page on dead trees, helping to starve the
planet of life-giving oxygen.  (If the troff source is not available, TkMan
asks if it should try to reverse compile the man page.  If successful, this
produces much more appealing output than a straight ASCII dump.)  By
default, incremental searching is not case sensitive, but regular
expression searching is; these settings can be toggled with the next two
menu checkboxes.  With proportional fonts giving a ragged right, any change
bars in the right margin will form an uneven line; by opting for
`Changebars on left', they will form a straight line at the left margin.
If you install new manual pages, invoking `Rebuild Database' will permit
them to show up the next time that volume list is shown and be found in the
next search without the bother of re-executing TkMan.  (If you want to add
paths to your MANPATH, or edit ~/.tkman, you will have to restart to see
them take effect, however.)  Usually TkMan saves its persistent variables,
only and always, when exited with the `Quit' button.  One may guard against
losing highlighting, shortcuts and other what-should-be persistent
information by checkpointing the current state with the "Update .tkman,
don't quit" button.  "Quit, don't update" performs the opposite operation,
obviously.

Like `xman' one may instantiate multiple viewers.  When there is more than
one viewer you may choose man pages in one viewer and have their contents
shown in another.  Use the `Output' pulldown (which appears and disappears
as relevant) to direct one viewer's output destination to another.  With
this feature one may easily compare two similar man pages for differences,
keep one man page always visible, or examine several man pages from a
particular volume listing or a SEE ALSO section.  `Output' only affects the
display destination of man pages.

You will probably only use commands in the next two clusters of
`Occasionals' once or twice and leave them set for all executions of TkMan.
Choose between seeing a man page's `whatis' information and the full path
name of the found file with the `Show Path of Found Man' switch.  Until
`wish' has an option to startup iconified, TkMan has a checkbox for that.
Subsection parsing doesn't work for all varieties of man page macros
formatting, but it can usefully augment the Sections listing for long man
pages.  At the factory, TkMan is set to format the contents of man volumes
on demand, which entails a little wait the first time that volume is shown,
as opposed to waiting for all volumes to be loaded at startup but no
waiting thereafter; the `Preformat Volumes' switch chooses between these.
If a man page has not been formatted by `nroff', TkMan must first pipe the
source text through `nroff'.  By turing on `Save on nroff', the
`nroff'-formatted text is saved to disk (if possible), thereby eliminating
this time-consuming step the next time the man page is read.

At the bottom right corner of the screen, `Mono' toggles between the
proportionally-spaced font and a monospaced one, for use in those man pages
that rely on a constant-width font to align columns [when Tk supports tabs
better, the need for this will diminish].  `Quit' exits TkMan, of course,
after saving some status information (see below).  To exit without saving
status information, select the `Quit' option from the `Occasionals'
pulldown.



=================
Customizing TkMan
-----------------

There are three levels of configuration to TkMan.

(1) Transparent.  Simply use TkMan and it will remember your window size
and placement and short cuts (if you quit out of TkMan via the `Quit'
button).


(2) Configuration file.  Most interesting persistent information, like the
command(s) used to print the man page, the fonts used, and some key
bindings, can be changed by editing one's own ~/.tkman.  Thus, a single
copy of TkMan (i.e., the executable `tkman') can be shared, but each user
can have his own customized setup.  (The file ~/.tkman is created/rewritten
every time one quits TkMan via the `Quit' button in the lower right corner.
Thus, to get a ~/.tkman to edit, first run and quit TkMan.  Do not create
one from scratch as it will not have the proper format used for saving
other persistent information, and your work will be overwritten, which is
to say lost.)

Most persistent information can be changed in the obvious way.  For
example, to change the means of highlighting from a light yellow background
to an underline (to display better on a monochrome monitor), EDIT (change
in place)
`set man(show,highlight) {-background #ffd8ffffb332}'
to
`set man(show,highlight) {-underline yes}'

To change colors append `option' commands to ~/.tkman.  Alternatively,
TkMan also works with standard X11 resources.  For instance, this is how I
set my foreground and background colors (X11 resource lines should be
placed in one's `.Xdefaults' file, not in ~/.tkman):

*TkMan*Foreground: SlateGray4
*TkMan*Background: beige
*TkMan*Text.foreground: black

Additional useful commands include `wm', which deals with the window
manager; and `bind', which changes keyboard and mouse bindings not related
to the text display window.  For example, to set an icon image for TkMan,
add this line to the end of ~/.tkman:

`wm iconbitmap .man @/some/directory/TkMan.xpm'


(3) Source code.  Of course, but if you make generally useful changes or
have suggestions for some, please report them back to me so I may share the
wealth with the next release.


= Command line options =

`-title <title>'
Place `<title>' in the window's title bar.

`-tkmangeometry <geometry>'
Specify the geometry for this invocation only.  To assign a persistent
geometry, start up TkMan, size and place the window as desired, then (this
is important) quit via the `Quit' button in the lower right corner.
(Unfortunately, the option name `-geometry' is eaten by `wish' before
having a chance to be processed by TkMan.)

`-iconify' and `--iconify'
Start up iconified or uniconified (the default), respectively.

`-iconname <name>'
Use `<name>' in place of the uniconified window's title for the icon name.

`-iconbitmap <bitmap-path>' and `-iconmask <bitmap-path>'
Specify the icon bitmap and its mask.

`-iconposition <geometry>'
Place the icon with the given geometry; "" cancels any such hints to the
window manager.

`-startup <filename>'
Use `<filename>' in place of ~/.tkman as the startup file; "" dictates
no startup file.

`-quit save' and `-quit nosave'
Specify that the startup file (usually ~/.tkman) should be updated (`save')
or not (`nosave') when quitting by the `Quit' button.

`-v'
Show the current version of TkMan and exit.

`-M <path-list>'
`-M+ <path-list>'
`-+M <path-list>'
As with `man', change the search path for manual pages to the given
colon-separated list of directory subtrees.  `-M+' appends and `-+M'
prepends these directories to the current list.


= Key bindings =

Key bindings related to the text display box are kept in the `sb' array in
~/.tkman (see the Tcl documentation for more information on Tcl arrays).
In editing the `sb(key,...)' keyboard bindings, modifiers MUST be listed in
the following order: `M' (for meta), `C' (control), `A' (alt), `S' (shift).
For instance, `set sb(key,MS-less) pagestart' is a valid binding, whereas
`set sb(key,SM-less)' is not.  To make a binding without a modifier key,
precede the character by `-', as in `set sb(key,-space) pagedown'.  If you
have NBT and would like to share key bindings between them, save the
keybindings in their own file, then `source' this file at the bottom of
~/.tkman.


= tkmandesc =

Like `xman', TkMan gives you directory-by-directory control over named
volume contents.  Unlike and superior to `xman', however, each individual
controls directory-to-volume placement, rather than facing a single
specification for each directory tree that must be observed by all.

By default a matrix is created by taking the product of directories in the
MANPATH crossed with volume names, with the yield of each volume containing
all the corresponding subdirectories in the MANPATH.  By adding Tcl
commands to your ~/.tkman (see above), you may add new volume names and
add, move, copy and delete directories to/from/among directories.

The interface to this functionality takes the form of Tcl commands, so you
may need to learn Tcl--particularly the sections on Tcl lists and, perhaps,
iteration (`foreach' or `for')--to use this facility to its fullest.

Directory titles and SINGLE LETTER abbrevations are kept in lists.  Letters
MUST be unique (capital letters are distinct from lower case), but need not
correspond to actual directories.  In fact, volume letters specified here
supercede the defaults in identifying a volume in man page searches.


COMMANDS

These commands are appended to the file ~/.tkman (see Customizing TkMan,
above).

To set absolutely the volume names for which all directories should be
searched, EDIT the parallel arrays on these EXISTING lines:
`set man(manList) ...'
`set man(manTitleList) ...'

To recreate a cross product of current section lists:
`manDescDefaults'

To add "pseudo" sections to default/current volume name list, at the
end of the list, in alphabetical order, or before or after a specific volume:
`manDescAddSects <list of <letter, title pairs>>'
or `manDescAddSects <list of <letter, title pairs>> sort'
or `manDescAddSects <list of <letter, title pairs>> before <sect-letter>'
or `manDescAddSects <list of <letter, title pairs>> after <sect-letter>'

To move/copy/delete/add directories:
`manDescMove <from-list> <to-list> <dir-patterns-list>'
`manDescCopy <from-list> <to-list> <dir-patterns-list>'
`manDescDelete <from-list> <dir-patterns-list>'
`manDescAdd <to-list> <dir-list>'

The `<dir-patterns-list>' uses the same meta characters as man page searching
(see above).  It is matched against MANPATH directories with volume
subdirectory appended, as in `/usr/man/man3'.  `<from-list>' and
`<to-list>' are Tcl lists of the unique single letter volume names; `*' is
an abbreviation for all volumes.

Warning: Moving directories from their natural home slightly impairs
searching speed when following a reference within a man page.  For
instance, say you've moved man pages for X Windows subroutines from their
natural home in volume 3 to their own volume called "X".  Following a
reference in `XButtonEvent' to `XAnyEvent(3X11)' first searches volume 3;
not finding it, TkMan searches all volumes and finally finds it in volume
X.  With no hint to look in volume 3 (as given by the `3X11' suffix), the
full volume search would have begun straight away.  (Had you double-clicked
in the volume listing for volume X or specified the man page as
`XButtonEvent.X', volume X would have been searched first, successfully.)

To help debug tkmandesc scripts, you may invoke `manDescShow' to dump to
stdout the current correspondence of directories to volumes names.


EXAMPLES

(1) To collect together all man pages in default volumes 2 and 3 in all
directories into a volume called "Programmer Subroutines", add these lines
to the tail of ~/.tkman:

`manDescAddSects {{p "Programmer Subroutines"}}'
`manDescMove {2 3} p *'

To place the new section at the same position in the volume pulldown list
as volumes 2 and 3:
`manDescAddSects {{p "Programmer Subroutines"}} after 2'
`manDescMove {2 3} p *'

To move only a selected set of directories:
`manDescAddSects {{p "Programmer Subroutines"}}'
`manDescMove * p {/usr/man/man2 /usr/local/man/man3}'


(2) To have a separate volume with all of your and a friend's personal man
pages, keeping with a duplicate in their default locations:

`manDescAddSects {{t "Man Pages du Tom"} {b "Betty Page(s)"}}'
`manDescCopy *phelps* t *'
`manDescCopy *page* t *'


(3) To collect the X windows man pages into two sections of their own, one
for programmer subroutines and another for the others.

`manDescAddSects {{x "X Windows"}} after 1'
`manDescAddSects {{X "X Subroutines"}} after 3'
`manDescMove * x *X11*'
`manDescMove x X *3'

We must name the X Subroutines volume "X", not "x" or "xsub" as the former
would duplicate the letter used for other X man pages and the latter is a
string of multiple letters.


(4) If you never use the programmer subroutines, why not
save time and memory by not reading them into the database?

`manDescDelete * {*[2348]}; # braces prevent Tcl from trying to execute [2348]'


Alternatively but not equivalently:
`manDescDelete {2 3 4 8} *'


= tkmandesc vs. xman and SGI =

TkMan's `tkmandesc' capability is patterned after xman's mandesc files.  By
placing a mandesc file at the root of a man page directory tree, one may
create pseudo volumes and move and copy subdirectories into them.  Silicon
Graphics has modified xman so that simply by creating a subdirectory in a
regular man subdirectory one creates a new volume.  This is evil.  It
violates the individual user's rights to arrange the directory-volume
mapping as he pleases, as the mandesc file or subdirectory that
spontaneously creates a volume is set a single place and must be observed
by all who read that directory.  By contrast, TkMan places the
directory-to-volume mapping control in an individual's own ~/.tkman file.
This gives the individual complete control and inflicts no pogrom on
others who share man page directories.  Therefore, mandesc files are not
supported in any way by TkMan.

One may still share custom setups, however, by sharing the relevant lines
of ~/.tkman.  In fact, a tkmandesc version of the standard SGI man page
directory setup is included in the `contrib' directory of the TkMan
distribution.  For assistance with SGI-specific directory manipulation,
contact Paul Raines (raines@bohr.physics.upenn.edu).



=========
Addresses
---------

Tom Phelps
University of California, Berkeley
Computer Science Division
571 Evans Hall
Berkeley, CA  94720
USA

(510) 642-8155
phelps@cs.Berkeley.EDU



==========
Disclaimer
----------

Permission to use, copy, modify, and distribute this software and its
documentation for educational, research and non-profit purposes, 
without fee, and without a written agreement is hereby granted, 
provided that the above copyright notice and the following three 
paragraphs appear in all copies.  

Permission to incorporate this software into commercial products may 
be obtained from the Office of Technology Licensing, 2150 Shattuck 
Avenue, Suite 510, Berkeley, CA  94704. 

IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY \
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES \
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF \
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF \
SUCH DAMAGE.

THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, \
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF \
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE \
PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF \
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, \
ENHANCEMENTS, OR MODIFICATIONS.


$Date: 1993/11/02 22:21:16 $
}



wm withdraw .; update idletasks



proc TkMan {} {
   global man manx env winout

   if {$manx(uid)==1} {
      set dup 0
      set w .man
      bind $w <Destroy> "after 1 bind $w <Destroy> {}; after 2 exit 0"
   } else {
      set dup 1
      set w .man$manx(uid)
      toplevel $w -class TkMan
   }

   set manx(man$w) ""
   set manx(manfull$w) ""
   set manx(catfull$w) ""
   set manx(name$w) ""
   set manx(num$w) ""
   set winout(lastMessage$w.info) ""
   set manx(hv$w) [set manx(oldmode$w) [set manx(mode$w) help]]
   foreach i {iconbitmap iconmask} {
      if {$manx($i)!=""} {wm $i $w @$manx($i)}
   }


   wm minsize $w 200 200

   if {!$dup} {
      wm geometry $w $man(geom)
      wm geometry $w $manx(geom)
      wm title $w $manx(title)
      wm iconname $w $manx(iconname)
      if {$manx(iconify)} {
         update idletasks
         wm iconify $w
      }
   } else {
      wm title $w "$manx(title) - #$manx(uid)"
      wm iconname $w "$manx(iconname) - $manx(uid)"
      wm geometry $w [lfirst [split $man(geom) +]]
      wm geometry $w [lfirst [split $manx(geom) +]]
   }


   label $w.info -anchor w

   frame $w.kind
   button $w.man -text "man" -command "manShowMan \$manx(typein$w) {} $w"
   bind $w.man <Shift-Button-1> "manSuperMan $w \$manx(typein$w)"
   bind $w.man <Meta-Button-1> "manSuperMan $w \$manx(typein$w)"
   button $w.apropos -text "apropos" -command "manApropos $w \$manx(typein$w)"
   entry $w.mantypein -relief sunken -textvariable manx(typein$w)
   emacsbind $w.mantypein
   bind $w.mantypein <Shift-KeyPress-Return> "manSuperMan $w \$manx(typein$w)"
   bind $w.mantypein <KeyPress-Return> "$w.man invoke"
   bind $w.mantypein <KeyPress-Escape> "
      if \[regexp {^\[<|.~/$\]} \$manx(typein$w)\] {manFilecomplete $w}
   "
   bind $w.mantypein <Mod1-KeyPress-Return> "$w.apropos invoke"
   menubutton $w.dups -text "\337" -font $man(symbolfont) -menu $w.dups.m -relief raised
   menu $w.dups.m

   pack $w.man -in $w.kind -side left -padx 2 -anchor e
   pack $w.apropos -in $w.kind -side left -padx 4
   pack $w.mantypein -in $w.kind -side left -ipadx 5 -anchor w

   menubutton $w.paths -text "Paths$man(popupchar)" -relief raised -menu $w.paths.m
   menu $w.paths.m
   if {[llength $manx(paths)]>2} {
      $w.paths.m add command -label "All Paths On" -command {
         foreach i $manx(paths) {set man($i) 1}
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
      $w.paths.m add command -label "All Paths Off" -command {
         foreach i $manx(paths) {set man($i) 0}
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
      $w.paths.m add command -label "Save Paths Selections" -command {
         set manx(pathstat) ""
         foreach i $manx(paths) {lappend manx(pathstat) $man($i)}
      }
      $w.paths.m add command -label "Restore Paths Selections" -command {
         set ctr 0
         foreach i $manx(paths) {set man($i) [lindex $manx(pathstat) $ctr]; incr ctr}
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
      $w.paths.m add separator
   }
   foreach i $manx(paths) {
      $w.paths.m add checkbutton -label $i -variable man($i) -command {
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
   }

   menubutton $w.vols -text "Volumes$man(popupchar)" -relief raised -menu $w.vols.m
   menu $w.vols.m
   set ctr 0
   foreach i $manx(manList) {
      $w.vols.m add command -label "($i) [lindex $manx(manTitleList) $ctr]" \
         -command "manShowSection $w $i"
      incr ctr
   }
   label $w.volnow

   pack $w.paths -in $w.kind -side left -padx 4
   pack $w.vols $w.volnow -in $w.kind -side left


   frame $w.nav
   menubutton $w.sections -text "Sections$man(popupchar)" -relief raised -menu $w.sections.m
   menu $w.sections.m
   menubutton $w.links -text "Links$man(popupchar)" -relief raised -menu $w.links.m
   menu $w.links.m

   frame $w.hlf
   menubutton $w.high -text "Highlights" -relief raised -menu $w.high.m
   menu $w.high.m
   button $w.hadd -text "+" -padx 4 -command "
      if \[llength \[$w.show tag nextrange sel 1.0\]\]==0 {
         winstderr $w.info {Select a range of characters to highlight.}
         return
      }
      $w.show tag add highlight sel.first sel.last
      selection clear $w.show
      manHighlights $w
   "
   button $w.hsub -text "-" -padx 4 -command "
      if \[llength \[$w.show tag nextrange sel 1.0\]\]==0 {
         winstderr $w.info {Select a range of characters to unhighlight.}
         return
      }
      $w.show tag remove highlight sel.first sel.last
      selection clear $w.show
      manHighlights $w
   "
   pack $w.high $w.hadd $w.hsub -in $w.hlf -side left


   frame $w.scf
   menubutton $w.shortcuts -text "Shortcuts$man(popupchar)" -relief raised -menu $w.shortcuts.m
   menu $w.shortcuts.m
   button $w.sadd -text "+" -padx 4 -command "manShortcuts $w add"
   button $w.ssub -text "-" -padx 4 -command "manShortcuts $w sub"
   pack $w.shortcuts $w.sadd $w.ssub -in $w.scf -side left
   manShortcuts $w init


   menubutton $w.history -text "History$man(popupchar)" -relief raised -menu $w.history.m
   set manx(history$w) ""
   menu $w.history.m
   $w.history.m add command -label "(none)"


   menubutton $w.output -text "Output$man(popupchar)" -menu $w.output.m -relief raised
   menu $w.output.m
   set manx(out$w) $w


   menubutton $w.occ -text "Occasionals$man(popupchar)" -relief raised -menu $w.occ.m
   menu $w.occ.m
   $w.occ.m add command -label "Help" -command "manHelp $w"
   $w.occ.m add command -label Print -command "manPrint $w"
   $w.occ.m add checkbutton -label "Incremental Search Case Sensitive" \
      -variable man(incr,case)
   $w.occ.m add checkbutton -label "Regexp Search Case Sensitive" \
      -variable man(regexp,case)
   $w.occ.m add checkbutton -label "Changebars on Left" -variable man(changeleft) \
      -onvalue "-c" -offvalue ""
   $w.occ.m add command -label "Instantiate New View" -command {
      incr manx(uid)
      incr manx(outcnt)
      TkMan
      manOutput
   }
   $w.occ.m add command -label "Rebuild Database" -command "manReadSects $w"
   $w.occ.m add command -label "Update .tkman, don't quit" \
       -command "manSave; winstdout $w.info {[bolg $manx(startup) ~] updated}"
   $w.occ.m add command -label "Quit, don't update .tkman" -command "destroy ."

   $w.occ.m add separator
   $w.occ.m add checkbutton -label "Show Path of Found Man" -variable man(whatwhere)
   $w.occ.m add checkbutton -label "Startup Iconified" -variable man(iconify)
   $w.occ.m add checkbutton -label "Parse Subsections" -variable man(subsect) \
      -onvalue -b -offvalue ""
   $w.occ.m add checkbutton -label "Preformat Volumes" -variable man(preformat)
   $w.occ.m add checkbutton -label "Save on nroff" -variable man(savenroff)
   $w.occ.m add checkbutton -label "Show header & footer at bottom" -variable man(headfoot) \
      -onvalue -k -offvalue ""

   foreach i {sections hlf links history scf occ} {pack $w.$i -in $w.nav -side left}
   foreach i {hlf history occ} {pack configure $w.$i -padx 6}
   pack configure $w.links -expand yes -anchor e
   pack configure $w.scf -expand yes -anchor w


   frame $w.vf
   text $w.show -font $man(show,font) \
      -relief sunken -borderwidth 2 -padx $man(textboxmargin) -pady $man(textboxmargin) \
      -yscrollcommand "$w.v set" -exportselection yes -wrap word -cursor $manx(cursor) \
      -height 10 -width 5

   eval $w.show tag configure highlight $man(show,highlight)
   $w.show tag configure title -font $man(show,bold)
   $w.show tag configure bold -font $man(show,bold)
   $w.show tag configure symbol -font $man(show,symbol)
   $w.show tag configure high -font $man(show,italic)
   $w.show tag configure mono -font $man(show,keyword)
   $w.show tag configure big -font $man(show,bigbold)
   $w.show tag configure sc -font $man(show,smallcaps)
   $w.show tag configure bi -font $man(show,bolditalic)
   eval $w.show tag configure hot $man(show,hot)
   $w.show tag configure help -underline yes
   $w.show tag configure search \
      -foreground [lindex [$w.show configure -background] 4] \
      -background [lindex [$w.show configure -foreground] 4]
   bind $w.show <Any-KeyPress> "manKeyNav $w \[key_state2mnemon %s\] %K"
   bind $w.show <Control-KeyPress-d> "manShowSection $w \$manx(lastvol)"
   bind $w.show <Control-KeyPress-m> "manShowMan \$manx(lastman) {} $w"
   scrollbar $w.v -orient vertical -command "$w.show yview"
   pack $w.v -in $w.vf -side $man(scrollbarside) -fill y
   pack $w.show -in $w.vf -side $man(scrollbarside) -fill both -expand yes

   bind $w.show <Any-Button-1> "set text(b1-time) %t; set text(b1-x) %x; [bind Text <Button-1>]"
   bind $w.show <Any-ButtonRelease-1> "[bind Text <ButtonRelease-1>]
      if {\[expr %t-\$text(b1-time)\]<500 && \[expr %x-\$text(b1-x)\]<3} {
         manHotSpot show %W @%x,%y
         catch {if {\[string trim \[set tmp \[$w.show get hot.first hot.last\]\]\]!={}} {set manx(typein$w) \$tmp}}
      }
   "
   bind $w.show <Meta-Double-Button-1> "
      if {\[expr %t-\$text(b1-time)\]<500 && \[expr %x-\$text(b1-x)\]<3} {
         set text(b1-time) \[expr \$text(b1-time)-500\]
         manShowMan <\[manHotSpot get %W @%x,%y\] $w
      }
   "


   frame $w.search
   button $w.search.s -text "Search" -command "
      winstdout $w.info \"Searching for regular expression \\\"\$manx(search,string$w)\\\" ...\"; update idletasks
      searchboxSearch \$manx(search,string$w) 1 $man(regexp,case) search $w.show $w.v $w.info $w.search.cnt
   "
   button $w.search.n -text "Next" \
      -command "searchboxNext search $w.show $w.v $w.info"
   label $w.search.cnt
   entry $w.search.t -relief sunken -textvariable manx(search,string$w)
   emacsbind $w.search.t
   set manx(search,oldstring$w) ""
   bind $w.search.t <KeyPress-Return> "
      if {\$manx(search,oldstring$w)!=\$manx(search,string$w)} {
         set manx(search,oldstring$w) \$manx(search,string$w)
         $w.search.s invoke
      } else {$w.search.n invoke}"
   pack $w.search.s -side left
   pack $w.search.n -side left -padx 6
   pack $w.search.t -side left -fill x -expand yes -ipadx 10 -anchor w
   pack $w.search.cnt -side left

   bind $w.show <KeyPress-slash> "focus $w.search.t"

   checkbutton $w.mono -text "Mono" -variable man(show,font) \
      -onvalue $man(show,mono) -offvalue $man(show,pro) \
      -font $manx(gui,monofont) \
      -command "
         $w.show configure -font \$man(show,font)
      "

   button $w.quit -text "Quit" -command "manSave; destroy ." -padx 4
   if {!$manx(quit)} {$w.quit configure -command "destroy ."}
   if {$dup} {$w.quit configure -text "Close" -command "destroy $w; incr manx(outcnt) -1; manOutput"}
   bind all <Meta-KeyPress-q> "$w.quit invoke"

   pack $w.mono -in $w.search -side left -padx 3 -anchor e
   pack $w.quit -in $w.search -side left -padx 3


   pack $w.info $w.kind -fill x -pady 4
   pack $w.nav -fill x -pady 6
   pack $w.vf -fill both -expand yes
   pack $w.search -fill x -pady 6


   foreach i {info kind nav} {bind $w.$i <Enter> "focus $w.mantypein"}
   foreach i {vf show v} {bind $w.$i <Enter> "focus $w.show"}
   bind $w.search <Enter> "focus $w.search.t"
   foreach i {mantypein show search.t} {
      bind $w.$i <KeyPress-Tab> "manTab $w"
      bind $w.$i <Meta-KeyPress-Tab> "manTab $w -1"
      bind $w.$i <Shift-KeyPress-Tab> "manTab $w -1"
   }

   manHelp $w
   update idletasks
}




proc manShortcuts {w cmd} {
   global man manx

   set mode $manx(mode$w)
   if {$cmd!="init" && (($mode!="man"&&$mode!="txt") || $manx(man$w)=="")} return
   set n $manx(name$w); set p $manx(man$w)

   set index [lsearch $man(shortcuts) $p]
   if {$cmd=="add" && $index==-1} {lappend man(shortcuts) $p} \
   elseif {$cmd=="sub" && $index!=-1} {set man(shortcuts) [lfilter $p $man(shortcuts)]}

   foreach w [winfo children .] {
      $w.shortcuts.m delete 0 last
      if {[llength $man(shortcuts)]} {
         foreach i $man(shortcuts) {
            if {![regexp {^[<|]} $i]} {set name [file rootname [file rootname $i]]} {set name $i}
            $w.shortcuts.m add command \
               -label $name -command "manShowMan [list $i] {} $w"
         }
      } else {
         $w.shortcuts.m add command -label "(none)"
      }
   }
}



proc manHotSpot {cmd w xy} {
   set manchars {[a-z0-9_.~/$+-]}


   if {$cmd=="get"} {
      return [$w get hot.first hot.last]
   }

   $w tag remove hot 1.0 end

   scan [$w index $xy] "%d.%d" line char
   set c0 $char; set cn $char; set cs $char
   scan [$w index "$line.$char lineend"] "%d.%d" bozo lineend

   while {$c0>=0 && [regexp -nocase $manchars [$w get $line.$c0]]} {incr c0 -1}
   incr c0
   while {$cn<=$lineend && [regexp -nocase $manchars [$w get $line.$cn]]} {incr cn}
   incr cn -1

   for {set cs [expr $cn+1]} {$cs<=$lineend && [$w get $line.$cs]==" "} {incr cs} {}
   if {$cs<=$lineend && [$w get $line.$cs]=="("} {
      incr cs
      while {$cs<=$lineend && [regexp {[^) ]} [$w get $line.$cs]]} {incr cs}
      if {$cs<=$lineend && [$w get $line.$cs]==")"} {set cn $cs}
   }

   $w tag add hot $line.$c0 $line.[expr $cn+1]
}



proc manOutput {} {
   global manx

   set wins [winfo children .]

   foreach i [lsort $wins] {
         set title "#[string range $i 4 4]"
         if {$title=="#"} {append title 1}
         lappend titleList [list $title $i]
   }

   foreach w $wins {
      $w.output.m delete 0 last
      foreach i $titleList {
         $w.output.m add radiobutton -label [lfirst $i] \
            -variable manx(out$w) -value [lsecond $i]
      }
   }

   if {$manx(outcnt)==1} {
      pack forget .man.output
   } else {
      foreach w $wins {pack $w.output -before $w.occ -side left -expand yes}
   }
}





proc manHighView {w} {
   global manx

   if ![catch {set y $manx(yview,$manx(hv$w))}] {
      $w.show yview $y
      $w.show mark set xmark $y.0
   }
   manHighlights $w get
}



proc manHighlights {w {cmd update}} {
   global high man manx

   $w.high.m delete 0 last

   set f $manx(hv$w)
   if [string match <* $f] {set f [string range $f 1 end]}
   if ![catch {set sf [file readlink $f]}] {
      if [string match /* $sf] {
         set f $sf
      } else {
         set f [file dirname $f]
         set strip 1
         while {$strip} {
            switch -glob $f {
               ../* {set f [file dirname $f]; set sf [string range $sf 3 end]}
               ./* {set sf [string range $sf 2 end]}
               default {set strip 0}
            }
         }
         append f /$sf
      }
   }
   set v high($f)
   set tags ""
   if {$cmd=="update"} {
      if [file isfile $f] {set tags "[file mtime $f] "} {set tags "-1 "}
      append tags [$w.show tag ranges highlight]
      if {$f!="" && [llength $tags]>1} {set $v $tags} {catch {unset $v}}
   } elseif {[info exists $v]} {
      if {![file isfile $f] || [file mtime $f]<=[lfirst [set $v]]} {
         set tags [set $v]
      } else {
         if {![tk_dialog .dialog Warning "Highlights out of date for $f.  Delete them?" "" 1 No Yes]} {
            set $v [set tags "[file mtime $f] [lrange [set $v] 1 end]"]
         }
      }
   }

   set len [llength $tags]
   for {set i 1} {$i<$len} {incr i 2} {
      set first [lindex $tags $i]
      set last [lindex $tags [expr $i+1]]
      if {$cmd=="get"} {$w.show tag add highlight $first $last}
      set label \
         [tr [string range [string trim [$w.show get $first $last]] 0 $man(high,hcontext)] \012 " "]
      $w.high.m add command -label $label \
         -command "$w.show yview [max [expr $first-$man(high,vcontext)] 1]"
   }
   if {$len<=1} {$w.high.m add command -label "(none)"}
}



proc manFilecomplete {w} {
   global manx

   set line $manx(typein$w)
   set file [string trim [llast $line] <]
   set posn [string last $file $line]

   set ll [llength [set fc [filecomplete $file]]]
   
   if {!$ll} {
      winstderr $w.info "no matches"
      return
   } elseif {$ll>=2} {
      foreach i $fc {lappend matches [file tail $i]}
      if {$ll<10} {winstderr $w.info [lrest $matches]} {
         manOpenText $w
         $w.show insert end [lrest $matches]
         manCloseText $w
      }
      set fc [lfirst $fc]
   }

   set manx(typein$w) [string range $line 0 [expr $posn-1]]$fc

   $w.mantypein icursor end
   tk_entrySeeCaret $w.mantypein
}



proc manNewMode {w mode {n {""}}} {
   global man manx

   scan [$w.v get] "%d %d %d %d" totalU windowU firstU lastU
   set manx(yview,$manx(hv$w)) $firstU; # can't do this on text widget--grrr!

   set manx(oldmode$w) $manx(mode$w)
   set manx(mode$w) $mode
   set manx(manfull$w) ""
   set manx(catfull$w) ""
   set manx(man$w) ""
   set manx(name$w) ""
   set manx(num$w) ""

   set manx(search,string$w) ""
   $w.search.cnt configure -text ""
   searchboxKeyNav "" Escape 0 $w.show $w.v "" 0
   if {$mode=="section"} {searchboxKeyNav C s 0 $w.show $w.v "" 1}
   after 5 selection clear $w.show

   set manx(vect) 1
   set manx(try) 0
   $w.sections.m delete 0 last
   $w.links.m delete 0 last



   set high(0) disabled; set high(1) normal

   set h $high([lmatch {man help} $mode]); $w.sections configure -state $h
   set h $high([lmatch {man txt help} $mode])
      foreach i {high hadd hsub} {$w.$i configure -state $h}
   set h $high([lmatch {man help} $mode]); $w.links configure -state $h
   set h $high([lmatch {man txt} $mode])
      foreach i {sadd ssub} {$w.$i configure -state $h}
   set h $high([lmatch man $mode]); $w.occ.m entryconfigure Print -state $h

   bind $w.show <Double-Button-1> "
      if {\[expr %t-\$text(b1-time)\]<500 && \[expr %x-\$text(b1-x)\]<3} {
         set text(b1-time) \[expr \$text(b1-time)-500\]
         manShowMan \[manHotSpot get %W @%x,%y\] $n $w
      }
   "
}




proc manOpenText {w} {
   global manx

   $w.show configure -cursor watch; update idletasks
   $w.show configure -state normal
   $w.show delete 0.1 end
}

proc manCloseText {w} {
   global manx

   $w.show configure -state disabled
   $w.show configure -cursor $manx(cursor)
   $w.show mark set xmark 1.0
}

proc manResetEnv {} {
   global env man manx

   set manpath {}
   foreach i $manx(paths) {if {$man($i)} {append manpath :$i}}
   set env(MANPATH) [string range $manpath 1 end]
}

proc manSectButt {w n} {
   global manx

   if {[set f [lsearch $manx(manList) $n]]!=-1} {
      set manx(cursect) $n
      $w.volnow configure -text "([lindex $manx(manList) $f]) [lindex $manx(manTitleList) $f]"
   }
}

proc manTab {w {ink 1}} {
   set l "$w.mantypein $w.show $w.search.t"
   if [set i [lsearch $l [focus]]]==-1 return
   set new [expr ($i+$ink)%[llength $l]]
   focus [lindex $l $new]
}




proc manInit {} {
   global man manx mani

   if {$manx(init)} return

   manReadSects
   set manx(init) 1
}


proc manReadSects {{w .man}} {
   global man manx mani winout

   set dirtmp [pwd]
   winstdout $w.info "Reading Volumes ... "
   $w.show configure -cursor watch; update idletasks

   set manx(manList) ""
   set manx(manTitleList) ""

   set cnt 0
   foreach i $mani(manList) {
      winstdout .man.info "$winout(lastMessage$w.info) $i"; update idletasks
      if {[manReadSection $i]!=0} {
         lappend manx(manList) $i
         lappend manx(manTitleList) [lindex $mani(manTitleList) $cnt]
         if {$man(preformat)} {manFormatSect $i}
      }
      incr cnt
   }

   lappend manx(manList) all
   lappend manx(manTitleList) "All Volumes"
   set mani(all) {}
   set mani(all,form) ""  


   $w.show configure -cursor $manx(cursor)
   winstdout $w.info {}
   cd $dirtmp
}




proc manReadSection {n} {
   global man manx mani mandot env

   if ![info exists mani($n,dirs)] {return 0}
   set mani($n) ""
   set cnt 0
   foreach i $mani($n,dirs) {
      foreach k [glob -nocomplain $i*] {
         if {![catch {cd $k}]} {
            if {![catch {set ltmp [glob -nocomplain *]}]} {
               lappend mani($n) [list $k $ltmp]
               foreach j $ltmp {
                  if {[regexp {\..*\.} $j] && ![regexp ($man(zregexp))|(~$) $j]} {
                     set mandot([file rootname $j]) 1
                  }
               }
               incr cnt [llength $ltmp]
               }
         }
      }
   }

   set mani($n,form) ""

   return $cnt
}





proc manDescDefaults {} {
   global man manx mani

   manDescManiCheck return

   foreach i $mani(manList) {
      set mani($i,dirs) {}
      foreach j $manx(paths) {
         lappend mani($i,dirs) $j/$man(mandir)$i
      }
   }
   set manx(defaults) 1
}



proc manDescMove {from to dirs} {manDesc move $from $to $dirs}
proc manDescDelete {from dirs} {manDesc delete $from "" $dirs}
proc manDescCopy {from to dirs} {manDesc copy $from $to $dirs}
proc manDescAdd {to dirs} {
   global mani

   manDescManiCheck
   foreach t $to {
      foreach d $dirs {
         lappend mani($t,dirs) $d
      }
   }
}

proc manDesc {cmd from to dirs} {
   global man manx mani

   manDescManiCheck
   if {$from=="*"} {set from $mani(manList)}
   if {$to=="*"} {set to $mani(manList)}
   foreach n "$from $to" {
      if [lsearch $mani(manList) $n]==-1 {
         puts stderr "$cmd: Section letter `$n' doesn't exist."
      }
   }

   if $manx(debug) {puts stdout "$cmd {$from} {$to} {$dirs}"}
   foreach d $dirs {
      foreach f $from {
         set newdir {}
         foreach fi $mani($f,dirs) {
            if [string match $d $fi] {
               if {$cmd=="copy"} {lappend newdir $fi}
               if {$cmd=="copy"||$cmd=="move"} {
                  foreach t $to {if {$f!=$t} {lappend mani($t,dirs) $fi} {lappend newdir $fi}}
               }
            } else {lappend newdir $fi}
         }
         set mani($f,dirs) $newdir
         if $manx(debug) {puts stdout $f:$mani($f,dirs)}
      }
   }
}

proc manDescAddSects {l {posn "end"} {what "n"}} {
   global man mani

   manDescManiCheck
   if {$posn=="before"||$posn=="after"} {set l [lreverse $l]}
   foreach i $l {
      set n [lfirst $i]; set tit [lsecond $i]
      if [lsearch $mani(manList) $n]!=-1 {
         puts stderr "Section letter `$n' already in use; request ignored."
         continue
      }

      if {$posn=="end"} {
         lappend mani(manList) $n
         lappend mani(manTitleList) $tit

      } elseif {$posn=="before"||$posn=="after"} {
         if [set ndx [lsearch $mani(manList) $what]]==-1 {
            puts stderr "Requested $posn $what, but $what doesn't exist; request ignored"
            continue
         }
         if {$posn=="after"} {incr ndx}
         set mani(manList) [linsert $mani(manList) $ndx $n]
         set mani(manTitleList) [linsert $mani(manTitleList) $ndx $tit]

      } elseif {$posn=="sort"} {
         lappend mani(manList) $n
         set mani(manList) [lsort $mani(manList)]
         set ndx [lsearch $mani(manList) $n]
         set mani(manTitleList) [linsert $mani(manTitleList) $ndx $tit]
      }

      set mani($n,dirs) {}
   }
}



proc manDescManiCheck {{action "exit"}} {
   global man mani manx env

   if ![info exists mani(manList)] {
      foreach i [split $env(MANPATH) :] {
         if {[file isdirectory $i] && [file readable $i]} {
            lappend manx(paths) [string trimright $i /]
            if {![info exists man($i)]} {set man($i) 1}
            lappend manx(pathstat) $man($i)
         }
      }

      set mani(manList) $man(manList)
      set mani(manTitleList) $man(manTitleList)
      if {$action=="return"} return
      manDescDefaults
   }
}

proc manDescShow {} {
   global man manx mani

   manDescManiCheck
   puts stdout "*** manDescShow"
   foreach i $mani(manList) {
      if [info exists mani($i,dirs)] {puts stdout $i:$mani($i,dirs)}
   }
}




proc manShowSection {w n} {
   global man manx mani

   manNewMode $w section $n
   set manx(lastvol) $n
   set manx(hv$w) $n

   set head [lindex $manx(manTitleList) [lsearch $manx(manList) $n]]
   if {$mani($n,form)==""} {
      $w.show configure -cursor watch
      winstdout $w.info "Formatting $head ..."; update idletasks
      manFormatSect $n
   }
   winstdout $w.info $head
   manSectButt $w $n

   manOpenText $w
   $w.show insert end $mani($n,form)
   $w.search.cnt configure -text $mani($n,cnt)
   manCloseText $w
   manHighView $w
}


proc manFormatSect {n} {
   global man manx mani mandot

   set ltmp ""

   set var mani($n,form)
   if {$n=="all"} {set nl $manx(manList)} {set nl $n}

   foreach k $nl {
      foreach i $mani($k) {
         set p [file dirname [lfirst $i]]
         if {![info exists man($p)]||$man($p)} {
            foreach j [lsecond $i] {
               lappend ltmp $j
            }
         }
      }
   }
   set cnt [llength $ltmp]
   set mani($n,cnt) "$cnt entries"


   set pr ""; set pe ""; set pl ""
   set online 0; set skip 0
   foreach i [lsort $ltmp] {
      if [string match *~ $i] continue
      set ir [file rootname $i]; set ie [file extension $i]; set il [string range $i 0 0]
      if {[regexp $man(zregexp) $ie] && ![info exists mandot($ir)]} {
         set ie [file extension $ir]; set ir [file rootname $ir]
      }

      if {$online>50} {
         append $var "\n"
         set online 0
      } elseif {$pl!=$il && $online>0 && ($n==3 || ![string match {[A-Z]} $il])} {
         append $var "\n\n"
         set online 0
      }

      if {$pr!=$ir} {
         if {$online!=0} {append $var "  "}
         append $var $ir; incr online
         set skip 0
      } elseif {$pe==$ie} {
      } else {
         if {$online!=0} {
            if {!$skip} {append $var $pe}
            append $var "  "
         }
         append $var $i; incr online
         set skip 1
      }

      set pr $ir; set pe $ie; set pl $il
   }

   if {$cnt==0} {set $var "No man pages in currently enabled paths.\nTry turning on some under Paths."}
   return $cnt
}




proc manShowMan {fname {goodnum ""} {w .man}} {
   global man manx mani mandot env

   if {[string trim $fname]==""} return
   if {$goodnum=="all"} {set goodnum ""}


   if {[regexp {^[.~/$]} $fname]} {
      manShowManFound [fileexp [lfirst $fname]] 0 $w
      return
   }


   if {[regexp {^[|<]} $fname]} {manShowText $fname $w; return}



   set oname [string trim [lfirst $fname] { .,?!;"'}]
   winstdout $w.info "Searching for $oname ..."; update idletasks
   set fname [string tolower $oname]; # lc w/  () for regexp
   set sname [string trim $fname ()]; # lc w/o ()
   set oname [string trim $oname ()]; # original case w/o ()

   set name $sname; set num ""; set ext ""
   if {$goodnum!=""} {
      set num $goodnum

   } elseif {![regexp -nocase {([a-z0-9_.-]+)([\t ]*)\(([^)]?)(.*)\)} \
      $fname all name spc num ext]} {

      if {[regexp {\..*\.} $oname] || ![info exists mandot($oname)]} {
         set tmp [string range [file extension $oname] 1 end]
         set num [string range $tmp 0 0]
         set name [file rootname $sname]
         set oname [file rootname $oname]
         set ext [string range $tmp 1 end]
      }
   }
   if [lsearch $manx(manList) $num]==-1 {set num ""}
   set ext [string tolower $ext]
   if $manx(debug) {puts stdout "$name : $num : $ext"}

   $w.show configure -cursor watch; update idletasks

   set found 0
   set foundList ""

   foreach i [if {$num!=""} {format $num} {format $manx(manList)}] {
      foreach j $mani($i) {
         set curdir [lfirst $j]
         if {[info exists man([file dirname $curdir])]&&!$man([file dirname $curdir])} continue
         foreach k [lsecond $j] {
            if {[string match $name.* [string tolower $k]] && ![string match *~ $k]
               && [file readable $curdir/$k] && [file isfile $curdir/$k]
               && [lsearch -exact $foundList $curdir/$k]==-1
            } {
               lappend foundList $curdir/$k
            }
         }
      }
   }
   set found [llength $foundList]

   if {!$found} {
      if {$ext!=""} {manShowMan $name $num $w; return} \
      elseif {$num!=""} {manShowMan $name {} $w; return}
   }

   $w.show configure -cursor $manx(cursor)

   if {!$found} {
      winstdout $w.info "$sname not found"; update idletasks
      return
   } elseif {$found>1} {
      after 1 pack $w.dups -before $w.mantypein -side left -anchor e; $w.dups configure -state active
      $w.dups.m delete 0 last
      set flLen [llength $foundList]
      if {$flLen<40} {set dupsFont $manx(gui,font)} {set dupsFont $man(menu,smallfont)}
      if {$flLen>60} {set foundList [lrange $foundList 0 59]}
      foreach i $foundList {$w.dups.m add command -label $i -command "manShowManFound $i 1 $w"}
      $w.dups.m configure -font $dupsFont

      set show [lfirst $foundList]
      foreach i [lreverse $foundList] {
         set ir [file rootname [file tail $i]]; set in [file extension $i]
         if {[regexp $man(zregexp) $in]} {set in [file extension $ir]; set ir [file rootname $ir]}
         set ie [string range $in 2 end]; set in [string range $in 1 1]

         if {$ir==$oname && $in==$num && $ie==$ext} {set show $i; break} \
         elseif {$ir==$oname && ($in==$num||$num=="")} {set show $i} \
         elseif {$in==$num} {set show $i} \
         elseif {$in==$manx(cursect)} {set show $i}
      }
      manShowManFound $show 1 $w
   } else {manShowManFound $foundList 0 $w}
}




proc manShowManFound {f {keep 0} {w .man}} {
   global man manx

   if {[winfo exists $manx(out$w)]} {set w $manx(out$w)} {set manx(out$w) $w}

   manNewMode $w man
   set manx(lastman) $f
   if {!$keep} {pack forget $w.dups; #$w.dups.m unpost}
   set manx(links) ""

   set tmpdir [file dirname $f]


   set manx(manfull$w) $f
   set manx(man$w) [file tail $f]
   if {[regexp $man(zregexp) $manx(man$w)]} {set manx(man$w) [file rootname $manx(man$w)]}
   set manx(name$w) [file rootname $manx(man$w)]
   set fdir [file dirname $manx(manfull$w)]
   if {[regexp $man(zregexp) $fdir]} {set fdir [file rootname $fdir]}
   set manx(num$w) [string index $fdir [expr [string length $fdir]-1]]
   if {[regexp $man(catsig) $fdir]} {
      set manx(catfull$w) $manx(manfull$w)
   } else {
      set manx(catfull$w) [file dirname [file dirname $f]]/cat$manx(num$w)*/$manx(name$w).*
   }
   set manx(cat) [file dirname [file dirname $f]]/cat$manx(num$w)

   if {[set path [lfirst [glob -nocomplain $manx(catfull$w)]]]!=""
      && (!$man(savenroff) || ![file writable $path] 
          || [file mtime $path]>=[file mtime $manx(manfull$w)])} {
      set manx(catfull$w) $path
      set pipe [manManPipe $path]

   } elseif {[file exists $manx(manfull$w)]} {
      if {[string match */man?* $tmpdir]} {
         set topdir [file dirname $tmpdir]
      } else {set topdir $tmpdir}
      if {[catch {cd $topdir}]} {
         winstderr $w.info "Can't cd into $topdir.  This is bad."
         return
      }
      if {$man(savenroff) && [file writable $manx(cat)]} {
         set path [set manx(catfull$w) $manx(cat)/$manx(man$w)]
         winstdout $w.info "Saving nroff version ..."; update idletasks
         if {![catch {eval "exec [manManPipe $manx(manfull$w)] | $man(format) > $path"}]} {
            if {$man(compressnroff)} {   # this doesn't follow the style for H-P
               exec $man(compress) $manx(catfull$w)
               set path [set manx(catfull$w) [lfirst [glob $manx(catfull$w).*]]]
            }
            set pipe [manManPipe $path]
         } else {
            set path $manx(manfull$w)
            set manx(catfull$w) ""
            set pipe "[manManPipe $path] | $man(format)"
         }

      } else {
         set path $manx(manfull$w)
         set manx(catfull$w) ""
         set pipe "[manManPipe $path] | $man(format)"
      }

   } else {
      winstdout $w.info "$manx(manfull$w) not found"
      return
   }


   set errflag 0
   if [string match *roff* $pipe] {set msg "Formatting and filtering"} {set msg Filtering}
   winstdout $w.info "$msg $manx(name$w) ..."; update idletasks


   manOpenText $w

   append pipe " | bs2tk -T -m $man(subsect) $man(headfoot) $man(changeleft)"

   if {[catch {set fid [open "|$pipe"]}]} {
      winstderr $w.info "Deep weirdness with $path.  Tell your sys admin."
      set errflag 1
   }
   while {![eof $fid]} {eval [gets $fid]}
   if [catch {close $fid}] {
      winstderr $w.info "Man page not installed properly, probably."
      set errflag 1
   }

   manCloseText $w
   if {$errflag} return
   if {$manx(catfull$w)!=""} {set manx(hv$w) $manx(catfull$w)} {set manx(hv$w) $manx(manfull$w)}



   manHighView $w

   focus $w.show

   cd $tmpdir
   manShowManStatus $w
}


proc manManPipe {f} {
   global man

   if {[regexp $man(zregexp) $f] || [regexp $man(zregexp) [file dirname $f]]} {
      set pipe $man(zcat)
   } else {set pipe cat}

   return "$pipe $f"
}

proc manShowManStatus {w} {
   global man manx


   manSectButt $w $manx(num$w)

   set manx(typein$w) $manx(name$w)

   set manx(history$w) \
      [lrange [setinsert $manx(history$w) 0 [list $manx(manfull$w)]] 0 [expr $man(maxhistory)-1]]
   $w.history.m delete 0 last
   foreach i $manx(history$w) {
      if {[llength [lfirst $i]]>=2} {set l [lfirst $i]} {set l $i}
      if {![regexp {^[|<]} $l]} {
         set l [file tail $l]
         if [regexp $man(zregexp) $l] {set l [file rootname $l]}
      }
      $w.history.m add command -label $l -command "manShowMan $i {} $w"
   }

   set E 0
   foreach i $manx(links) {
       foreach j [split $i ,.] {
          if {[regexp {\([1-9lonp].*\)} $j]} {
             $w.links.m add command -label $j -command "manShowMan $j {} $w"
             incr E
          }
       }
   }
   if {!$E && $manx(mode$w)=="man"} {$w.links.m add command -label "(none)"}

   winstdout $w.info ""
   if {$man(whatwhere)} {
      winstdout $w.info $manx(manfull$w)
   } else {
      scan [$w.show index end] %d n
      set n [max $n 20]
      for {set i 1} {$i<=$n} {incr i} {
         if {[regexp -- {- (.*)} [$w.show get $i.0 "$i.0 lineend"] all info]} {
            incr i
            while {[regexp {( +)(.*)} [$w.show get $i.0 "$i.0 lineend"] all spaces more]} {
               append info " $more"
               incr i
            }
            winstdout $w.info "$info"
            break
         }
      }
   }
}




proc manShowText {f0 {w .man}} {
   global man manx


   if {[string match <* $f0]} {
      set f [fileexp [string range $f0 1 end]]
      if {[regexp $man(zregexp) $f]} {set f "|$man(zcat) $f"}
   } else {set f [pipeexp $f0]}


   manNewMode $w txt

   manOpenText $w
   if {[catch {
      set cnt 0
      set fid [open $f]
      while {![eof $fid]} {$w.show insert end [gets $fid]\n; incr cnt}
      close $fid
   }]} {manCloseText $w; winstderr $w.info "Trouble reading $f0"; return}
   manCloseText $w
   pack forget $w.dups; #$w.dups.m unpost

   if [file isfile $f] {cd [file dirname [glob $f]]}



   set manx(man$w) $f0
   set manx(num$w) X
   set manx(hv$w) $f
   set manx(manfull$w) $f0
   set manx(catfull$w) $f
   set manx(name$w) $f0
   set manx(links) {}
   set tmp $man(whatwhere)
   set man(whatwhere) 1
   manShowManStatus $w
   set man(whatwhere) $tmp

   $w.search.cnt configure -text "$cnt lines"
   manHighView $w
   focus $w.show
}




proc manApropos {w name} {
   global man manx

   if {$name==""} {set name $manx(man$w)}
   if {$name==""} {return}

   manNewMode $w apropos
   set fid [open "|man -k $name $man(aproposfilter)"]
   manOpenText $w

   catch {
      while {![eof $fid]} {
         gets $fid line
         $w.show insert end "$line\n"
      }
      close $fid
   }
   
   manCloseText $w
}




proc manSuperMan {w fname} {
   global man manx env

   if {$fname==""} {set name $manx(man$w)}
   if {$fname==""} {return}
   set max 40

   manNewMode $w super

   if {![regexp -nocase {([a-z0-9_.-]+)([\t ]*)\(([^)]?)(.*)\)} \
      $fname all name spc num ext]} {
      set name [file rootname $fname]
      set tmp [string range [file extension $fname] 1 end]
      set num [string range $tmp 0 0]
      set ext [string range $tmp 1 end]
   }
   set num [string tolower $num]; set ext [string tolower $ext]
   if {$num!=""} {set numList $num} {set numList $manx(manList)}

   set foundList ""
   set cnt [llength $manx(paths)]
   set mcnt 1
   foreach i $manx(paths) {
      set curdir [lfirst $i]
      winstdout $w.info "Wait $cnt ..."; update idletasks
      incr cnt -1
      if {!$man($curdir)} continue

      foreach j $numList {
         foreach k [glob -nocomplain $curdir/man$j*] {
            if {[catch {cd $k}] || $mcnt>$max} continue
            catch {
            foreach l [eval exec egrep -il $name [glob *]] {
               lappend foundList $k/$l
               incr mcnt
               if {$mcnt>$max} break
            }}
         }
      }
   }


   set found [llength $foundList]
   if {$found} {
      $w.dups.m delete 0 last
      foreach i $foundList {
         $w.dups.m add command -label [file tail $i] -command "manShowManFound $i 1 $w"
      }
      if {$mcnt>$max} {$w.dups.m add command -label "(more)"}
      manShowManFound [lfirst $foundList] 1 $w
      after 1 pack $w.dups -before $w.mantypein -side left; $w.dups configure -state active
   }

}




proc manPrint {w} {
   global man manx winout


   set f [string trim $manx(manfull$w)]
   if {$f=="" || ![file exists $f]} return
   set name [file rootname [file tail $f]]; set sect [file extension $f]
   if {[regexp $man(zregexp) $f]} {set sect [file extension $name]; set name [file rootname $name]}

   set tmp $winout(lastMessage$w.info)
   winstdout $w.info "Printing $f ..."; update idletasks
   set tmpdir [pwd]
   set topdir [file dirname $f]
   set printpipe $man(print)
   if {[regexp -- $man(catsig) $topdir]} {
      set printpipe $man(catprint)
      if {[tk_dialog .dialog "NO GUARANTEES" "No troff source.  Try to reverse compile cat-only page?" \
            "" 1 No Yes]} {
         set printpipe "bs2tk -r $man(changeleft) $man(subsect) -n $name -s $sect | $man(print)"
      }
   }
   if {[regexp $man(catsig) $topdir] || [string match */man?* $topdir]} {
      set topdir [file dirname $topdir]
   }

   catch {cd $topdir}
   eval exec [manManPipe $f] | $printpipe
   winstdout $w.info $tmp
   cd $tmpdir
}




proc manHelp {w} {
   global manx

   manNewMode $w help
   set manx(manfull$w) "help"
   set manx(hv$w) help

   manOpenText $w
   if [string length $manx(warnings)] {
      $w.show insert end Warnings\n
      $w.show insert end $manx(warnings)\n\n
   }
   $w.show insert end $manx(boilerplate)

   $w.show tag add big 1.0 "1.0 lineend"
   scan [$w.show index end] %d numLines
   for {set i 1} {$i <= $numLines} {incr i} {
      set line [$w.show get $i.0 "$i.0 lineend"]
      if {[string match ==* $line]} {
         set j [expr $i+1]
         set line [$w.show get $j.0 "$j.0 lineend"]
         $w.sections.m add command -label $line -command "$w.show yview $i"
         $w.show tag add title $j.0 "$j.0 lineend"
      } elseif {[regexp -nocase {^= [a-z ]+ =} $line]} {
         $w.show tag add high $i.0 "$i.0 lineend"
         set sline [string range $line 2 [expr [string length $line]-3]]
         $w.sections.m add command -label "  $sline" -command "$w.show yview [expr $i-1]"
      }
   }
   regexpTextSearch $w.show {[A-Z][^a-z,;.?!]*[A-Z]} sc
   regexpTextSearch $w.show {(`[^']+')|([MC]-.)|([a-z]+@[A-Za-z.]+)} mono

   manCloseText $w
   manHighView $w
   foreach i {man(1) man(7) catman(8) xman(1) Tcl text bind option} {
      $w.links.m add command -label $i -command "manShowMan $i {} $w"
   }
   scan [$w.show index end] %d eot
   $w.search.cnt configure -text "$eot lines"

   update idletasks
   after 1 "winstdout $w.info \"TkMan v$manx(version) by Tom Phelps (phelps@cs.Berkeley.EDU)\""
}




proc manKeyNav {w m k} {
   global man manx

   set firstmode [expr {$manx(mode$w)=="section" || $manx(mode$w)=="apropos"}]
   searchboxKeyNav $m $k $man(incr,case) $w.show $w.v $w.info $firstmode
}




proc manSave {} {
   global man manx env

   if {$manx(savegeom)} {set man(geom) [wm geometry .man]}
   set nfn $manx(startup)
   set ofn $env(HOME)/.oldtkman

   if {![file exists $nfn] || [file writable $nfn]} {
      if {[file exists $nfn]} {exec cp $nfn $ofn}
      set fid [open $nfn w]
      foreach p [info procs *SaveConfig] {eval $p $fid}
      puts $fid $manx(userconfig)

      if {[file exists $ofn]} {
         set ofid [open $ofn]
         set p 0
         while {[gets $ofid line]!=-1} {
            if {$p} {puts $fid $line} \
            elseif {$manx(userconfig)=="$line"} {set p 1}
         }
      }
      close $fid
   }

}




proc manSaveConfig {fid} {
   global man manx high



   puts $fid "#\n# TkMan v$manx(version)\n#\n"
   foreach i [lsort [array names man]] {
      puts $fid "set [list man($i)] [list $man($i)]"
   }

   puts $fid "\n\n#\n# Highlights\n#\n"
   foreach i [lsort [array names high]] {
      puts $fid "set [list high($i)] [list $high($i)]"
   }
   puts $fid "\n"
}




if {$tk_version<3.3} {puts stderr "Tcl 7.0/Tk 3.3 required"; exit 1}







set man(manList) {1 2 3 4 5 6 7 8 9 l o n p}
set man(manTitleList) {
   "User commands" "System calls" "Library functions and subroutines"
   "Devices" "File formats" "Games" "Miscellaneous" "System administration"
   "Kernel internal variables and functions"
   Local Old New Public
}
set man(show,pro) "-adobe-new century schoolbook-medium-r-normal--*-140-*"
set man(show,mono) "-adobe-courier-medium-r-normal--*-120-*"
set man(show,keyword) "-adobe-courier-medium-r-normal--*-120-*"
set man(show,font) $man(show,pro)
set man(show,smallcaps) "-adobe-new century schoolbook-medium-r-normal--*-120-*"
set man(show,bold) "-adobe-new century schoolbook-bold-r-normal--*-120-*"
set man(show,hot) {-underline yes}
set man(show,symbol) "-adobe-symbol-medium-r-normal--*-140-*"
set man(show,bigbold) "-adobe-new century schoolbook-bold-r-normal--*-140-*"
set man(show,italic) "-adobe-new century schoolbook-medium-i-normal--*-140-*"
set man(show,bolditalic) "-adobe-new century schoolbook-bold-i-normal--*-120-*"
set man(gui,font) "-Adobe-Helvetica-Bold-R-Normal--*-120-*"
set man(gui,monofont) "-Adobe-Courier-Bold-R-Normal--*-120-*"
set man(show,highlight) "-background #ffd8ffffb332"; # a pale yellow

if [winfo depth .]==1 {
   set man(show,highlight) "-underline yes"
}


set man(menu,smallfont) "-Adobe-Helvetica-Bold-R-Normal--*-100-*"
set man(symbolfont) *symbol*140*
set man(maxhistory) 15
set man(shortcuts) {}
set man(whatwhere) 0
set man(iconify) 0
set man(subsect) ""
set man(preformat) 0
set man(savenroff) 0
set man(headfoot) ""
set man(incr,case) 0
set man(regexp,case) 1
set man(aproposfilter) {| sort | uniq}
set man(scrollbarside) right
set man(textboxmargin) 5
set man(changeleft) "-c"
set man(zregexp) {\.(gz|z|Z)$}
set man(catsig) {(cat[^/]+(/cat[^/]+)?$)}
set man(compressnroff) 0
set man(high,hcontext) 35
set man(high,vcontext) 10
set man(popupchar) ""
set man(geom) 570x800+150+10
set man(iconname) TkMan
set man(iconbitmap) ""
set man(iconmask) ""
set man(iconposition) ""
set man(quit) 1
set high(*) "format: time, start/end pairs for use by text widget"



set manx(title) "TkMan v$manx(version)"
set manx(warnings) ""
set manx(manList) {}
set manx(manTitleList) {}
set manx(userconfig) "### your additions go below"
set manx(init) 0
set manx(single) 0
set manx(cursor) left_ptr
set manx(cursect) 1
set manx(yview,help) 0
set manx(paths) ""
set manx(pathstat) ""
set manx(uid) 1
set manx(outcnt) 1
set manx(debug) 0
set manx(defaults) 0
set manx(startup) $env(HOME)/.tkman
set manx(savegeom) 1
set manx(lastvol) 1
set manx(lastman) TkMan



for {set i 0} \$i<[llength $argv] {incr i} {
   set arg [lindex $argv $i]; set val [lindex $argv [expr $i+1]]
   switch -glob -- $arg {
      -iconname {set manx(iconname) $val; incr i}
      -iconmask {set manx(iconmask) $val; incr i}
      -iconposition {set manx(iconposition) $val; incr i}
      -iconbitmap {set manx(iconbitmap) $val; incr i}
      -icon* {set manx(iconify) 1}
      --icon* {set manx(iconify) 0}
      -tkmangeom* {set manx(geom) $val; incr i; set manx(savegeom) 0}
      -v* {puts stdout "TkMan v$manx(version)"; exit 0}
      -t* {set manx(title) $val; incr i}
      -guifont {set manx(gui,font) $val; incr i}
      -guimonofont {set manx(gui,monofont) $val; incr i}
      -quit {if [string match no* $val] {set manx(quit) 0} {set manx(quit) 1}}
      -s* {set manx(startup) $val; incr i}
      -d* {set manx(debug) 1; set manx(quit) 0}
      --d* {set manx(debug) 0}
      -M {set env(MANPATH) $val; incr i}
      -M+ {append env(MANPATH) ":$val"; incr i}
      -+M {set env(MANPATH) "$val:$env(MANPATH)"; incr i}
      default {
         after 2000 manShowMan $arg {{}} .man
         break
      }
   }
}

if {![info exists env(MANPATH)] || $env(MANPATH)==""} {
   set env(MANPATH) /usr/man:/usr/local/man:/usr/X386/man
   append manx(warnings) "You don't have a MANPATH environment variable set, but you should.\n"
}

toplevel .man -class TkMan

if {$manx(startup)!="" && [file readable $manx(startup)]} {source $manx(startup)}

foreach i {
      iconify iconname iconbitmap iconmask iconposition
      gui,font gui,monofont
      quit geom
   } {
   if {![info exists manx($i)]} {set manx($i) $man($i)}
}
option add *TkMan*font $manx(gui,font) userDefault

if {!$manx(defaults)} manDescDefaults






TkMan
update idletasks

if {$manx(debug)} {
   puts stdout "init takes [time manInit]"
} else {

   set manx(tmp) $winout(lastMessage.man.info)
   winstdout .man.info "Reading Volumes ... "
   manInit
   winstdout .man.info $manx(tmp)
}

   .man.vols.m delete 0 last
   set ctr 0
   foreach i $manx(manList) {
      .man.vols.m add command -label "($i) [lindex $manx(manTitleList) $ctr]" \
         -command "manShowSection .man $i"
      incr ctr
   }






if {$manx(debug)} {
   entry .man.in -relief sunken -textvariable manx(debugcmd)
   emacsbind .man.in
   bind .man.in <KeyPress-Return> {winstdout .man.info "[eval $manx(debugcmd)]"}
   pack .man.in -fill x

   set w .man
}


if 0 {
set foselx(standalone) 0
source ~/spine/fosel/FoSel.tcl
foselInit
FoSel .fosel
}
