#!/usr/local/bin/wish -f
#
# jbrowser - Tk/Tcl-based directory browser

# Copyright (c) 1992-1994 by Jay Sekora.  All rights reserved, except
# that this file may be freely redistributed without fee for non-profit,
# noncommercial use.

## begin boiler_header

global VERSION
set VERSION {3.6/3.0}

if {[info exists env(JSTOOLS_LIB)]} {
  set jstools_library $env(JSTOOLS_LIB)
} else {
  set jstools_library /usr/local/lib/jstools
}

# add the jstools library to the library search path:

set auto_path [concat [list $jstools_library] $auto_path]

# check for ~/.tk and prepend it to the auto_path if it exists.
# that way the user can override and customise the jstools libraries.

if {[file isdirectory ~/.tk]} then {
  set auto_path [concat [list [glob ~/.tk]] $auto_path]
}

## end boiler_header

# URGENT:
#   rewrite to use plain paths
#   divide into libraries (gui, commands, etc.)
#   exportselection false

# TO DO:
#
# implement paul's idea of a `storage area' for files
#
# `fast' pref to skip directory check (/)
# use the right mouse button in the browser for something.
# make the information display more informative and prettier.
# jbr:configure_browser

######################################################################
# jbr:init - basic initialisation
######################################################################

proc jbr:init {} {
  j:jstools_init			;# prefs, libraries, bindings...
  j:source_config jlistlib.tcl
  
  global COLDIR				;# per-column list of directories
  					;# $COLDIR(w) is the dir w displays
  set COLDIR(A) 0			;# make sure it's an array
  
  global WINDIR				;# per-widnow list of directories
  					;# $WINDIR($count) is dir in win #count
  set WINDIR(A) 0			;# make sure it's an array
}

######################################################################
# jbr:mkcolumn w - make a directory-column frame named $w
#    TO DO: args for label and initial contents
######################################################################

proc jbr:mkcolumn {w} {
  global env HOME USER
  global J_PREFS
  if {[lsearch [array names J_PREFS] {scrollbarside}] == -1} {
    set J_PREFS(scrollbarside) right ;# make sure it's defined
  }
  
  frame $w
  set label $w.l
  set lb $w.lb
  set sb $w.sb
  
  # actually, the exact value of -width doesn't matter, as long as it doesn't
  # make the label bigger than the listbox+scrollbar, because the label will
  # expand to the size of the parent window.  but specifying -width prevents
  # the label from expanding larger than the parent window and making the
  # whole top level window resize itself.
  
  label $label -anchor center -width 10 -relief flat
  scrollbar $sb -relief flat -command "$lb yview"
  listbox $lb -borderwidth 0 -geometry 20x20 -yscroll "$sb set"
  
  pack $label [j:rule $w] -in $w -side top -fill x
  pack $sb [j:rule $w] -in $w -side $J_PREFS(scrollbarside) -fill y
  pack $lb -in $w -side $J_PREFS(scrollbarside) -fill both -expand yes
  
  return $w
}

######################################################################
# jbr:emptycolumn w - clear column w
######################################################################

proc jbr:emptycolumn {w} {
  set label $w.l
  set lb $w.lb
  
  $label configure -text " "
  $lb delete 0 end
}

######################################################################
# jbr:fill_column w loc - fill column w with contents of loc
######################################################################

proc jbr:fill_column {w loc} {
  set label $w.l
  set lb $w.lb
  set sb $w.sb
  
  set COLDIR($w) [jlist:listtopath $loc]
  
  # save current scroll value (to prevent jumping to top):
  set oldyview [lindex [$sb get] 2]
  
  $lb delete 0 end
  $lb insert end .
  $lb insert end ..
  
  set dir [jlist:listtopath $loc]
  set files [lsort [glob -nocomplain $dir/*]]
  if {1} {					;# pref. to show hidden
    eval lappend files [lsort [glob -nocomplain $dir/.*]]
  }
  
  set dirtail [jlist:tail $loc]
  if {[string length $dirtail] == 0} {
    set dirtail "/"
  }
  $label configure -text $dirtail
  
  foreach path $files {
    set file [file tail $path]
    if {"x$file" != "x." && "x$file" != "x.."} {
      $lb insert end $file
    }
  }
  # restore old scroll value (to prevent jumping to top):
  $lb yview $oldyview
}

######################################################################
# jbr:column_path w - return the path of the (single) sel'n in column $w
######################################################################

proc jbr:column_path { w } {
  global COLDIR
  
  set selindex [$w curselection]
  if {"x$selindex" == "x"} {
    # no selection, so return empty list:
    return {}
  } else {
    set filename [$w get $selindex]	;# needs to be more elaborate
  }
  
  # $w starts out being .some.path.lb; get .some.path:
  set frame [winfo parent $w]		;# breaks on main wish window
  
  set dir $COLDIR($frame)
  if {"x$dir" == "x/"} {
    return "/$filename"
  } else {
    return "$dir/$filename"
  }
}


######################################################################
# jbr:fill_window count loc - fill window no. $count with contents of loc
######################################################################

proc jbr:fill_window {count loc} {
  global WINDIR
  set WINDIR($count) [jlist:listtopath $loc]
  
  set w .browser$count
  
  set loclength [llength $loc]
  
  for {set level 0} {[winfo exists $w.col$level]} {incr level} {
    if {$level > $loclength} {
      jbr:emptycolumn $w.col$level
    } else {
      jbr:fill_column $w.col$level [jlist:ancestor $level $loc]
    }
  }
}

######################################################################
# jbr:mkwindow count loc ?columns? -
#   create a browser window at $loc with $columns columns shown
#   count is a serial number for browser windows (0 is special)
######################################################################

proc jbr:mkwindow {count loc {columns DEFAULT}} {
  global env HOME USER argv0
  global BROWSERPREFS
  global WINDIR
  set script [file tail $argv0]
  ####
  set BROWSERPREFS(0) 0
  ####
  if {[lsearch [array names BROWSERPREFS] {columns}] == -1} {
    set BROWSERPREFS(columns) 3		;# make sure it's defined
  }
  
  if {"x$columns" == "xDEFAULT"} {
    set columns $BROWSERPREFS(columns)
  }
  
  set w .browser$count
  set WINDIR($count) [jlist:listtopath $loc]
  
  toplevel $w
  if {$count != 0} {
    wm title $w "$script $count"
  } else {
    wm title $w "$script"
    wm protocol $w WM_DELETE_WINDOW {if [j:confirm -text quitting] {exit 0}}
  }
  
  jbr:mkmenu $w.menu $count
  
  for {set level 0} {$level < $columns} {incr level} {
    jbr:mkcolumn $w.col$level
    if {$level != 0} {
      pack [j:rule $w] -in $w -side right -fill y
    }
    pack $w.col$level -in $w -side right -fill y
  }
  
  jbr:mkbindings $count $w
  focus $w
  
  jbr:fill_window $count $loc
  
  return $w
}

######################################################################
# jbr:mkmenu w count - create the menu frame w for window # count
#   count is a serial number for browser windows (0 is special)
######################################################################

proc jbr:mkmenu {w count} {
  global env HOME USER argv0
  global BROWSERPREFS
  
  frame $w -borderwidth 2 -relief raised
  jbr:mkmenu:browser $w.browser $count
  jbr:mkmenu:file $w.file $count

  pack $w.browser $w.file -side left
  pack $w -side top -fill x
  
  tk_menuBar $w $w.browser $w.file
}
  
######################################################################
# jbr:mkmenu:browser w count - create browser menu w for window # count
######################################################################

proc jbr:mkmenu:browser {w count} {
  global env HOME USER
  
  menubutton $w -text {Browser} -menu $w.m
  menu $w.m
  $w.m add command -label {Help} -accelerator {[h]} \
    -command "jbr:cmd:help"
  $w.m add command -label {About the Browser . . .} \
    -command "jbr:cmd:about"
  $w.m add command -label {Global Preferences . . .} \
    -command "j:global_pref_panel; jbr:apply_prefs"
#   $w.m add command -label {Browser Preferences . . .} \
#     -command {jbr:cmd:browser_prefs; jbr:apply_prefs}
   $w.m add separator
   $w.m add command -label {Up} \
     -command "jbr:cd $count .."
  $w.m add command -label {Home} \
    -command "jbr:cd $count $HOME"
  $w.m add command -label {/} \
    -command "jbr:cd $count /"
  $w.m add command -label {Change Directory . . .} \
    -accelerator {Tab} \
    -command "jbr:cmd:cd $count"
  $w.m add separator
  $w.m add command -label {Issue Tcl Command . . .} \
    -accelerator {[T]} -command "j:prompt_tcl"
  $w.m add command -label {Issue Unix Command . . .} \
    -accelerator {[U]} -command "j:prompt_unix"
  $w.m add separator
  $w.m add command -label {New Browser} -accelerator {[b]} \
    -command "exec jbrowser &"
  $w.m add command -label {Refresh} -accelerator {^l} \
    -command "jbr:refresh $count"
  $w.m add command -label {Quit . . .} -accelerator {[q]} \
    -command "jbr:cmd:quit $count"
}
  
######################################################################
# jbr:mkmenu:file w count - create file menu w for window # count
######################################################################

proc jbr:mkmenu:file {w count} {
  global env HOME USER
  
  menubutton $w -text {File} -menu $w.m
  menu $w.m
  $w.m add command -label {View} -accelerator {CR} \
    -command "jbr:cmd:view $count"
  $w.m add command -label {Edit} -accelerator {[e]} \
    -command "jbr:cmd:edit $count"
  $w.m add command -label {Process} -accelerator {[u]} \
    -command "jbr:cmd:process $count"
  $w.m add command -label {Print} -accelerator {[p]} \
    -command "jbr:cmd:print $count"
  $w.m add separator
  $w.m add command -label {Create Directory . . .} -accelerator {[n]} \
    -command "jbr:cmd:newdir $count"
  $w.m add command -label {Move . . .} -accelerator {[m]} \
    -command "jbr:cmd:move $count"
  $w.m add command -label {Rename . . .} -accelerator {[M]} \
    -command "jbr:cmd:rename $count"
  $w.m add command -label {Duplicate} -accelerator {[d]} \
    -command "jbr:cmd:duplicate $count"
  $w.m add command -label {Destroy} -accelerator {[r]} \
    -command "jbr:cmd:destroy $count"
  $w.m add command -label {Get Info} -accelerator {[i]} \
    -command "jbr:cmd:info $count"
  $w.m add cascade -label {Misc.} -accelerator {  >} \
    -menu $w.m.misc
  
  #####
  # $w.m add command -label {Execute} -command "jbr:cmd:execute
  # $w.m add command -label {Execute in xterm} \
  #   -command "jbr:cmd:xterm_execute
  #####
  
  menu $w.m.misc
  $w.m.misc add command -label {TeX File} -accelerator {[t]} \
    -command "jbr:cmd:tex $count"
  $w.m.misc add command -label {LaTeX File} -accelerator {[l]} \
    -command "jbr:cmd:latex $count"
  $w.m.misc add command -label {Compress File} \
    -command "jbr:cmd:compress $count"
  $w.m.misc add command -label {Tar Directory} \
    -command "jbr:cmd:maketar $count"
  $w.m.misc add command -label {Run `make' in This Directory} \
    -command "jbr:cmd:make $count"
  $w.m.misc add command -label {Edit with `jedit'} \
    -command "jbr:cmd:jedit $count"
  $w.m.misc add command -label {Edit with `xedit'} \
    -command "jbr:cmd:xedit $count"
  $w.m.misc add command -label {Print with `lpr'} \
    -command "jbr:cmd:lpr $count"

}


######################################################################
# jbr:mkbindings count w - make keyboard equivalents for commands
######################################################################

proc jbr:mkbindings {count w} {
  global env HOME USER

  # change Listbox binding so you need to shift to select multiple items
  bind Listbox <1> {%W select from [%W nearest %y]}
  bind Listbox <B1-Motion> {%W select from [%W nearest %y]}
  bind Listbox <Shift-1> {%W select from [%W nearest %y]}
  bind Listbox <Shift-B1-Motion> {%W select to [%W nearest %y]}
  
#   bind .cur.list <Double-Button-1> "jbr:browse $count"
  
#   bind $w <Up>			"jbr:moveup %W"
#   bind $w <Down>		"jbr:movedown %W"
  bind $w <Left>		"jbr:cd $count .."
  bind $w <Right>		"jbr:browse $count"
  bind $w <Return>		"jbr:browse $count"
  bind $w <Tab>			"jbr:cmd:cd $count"
  bind $w <period><period>	"jbr:cmd:cd $count .."
  bind $w <Meta-b>		"exec jbrowser &"
  bind $w <Meta-d>		"jbr:cmd:duplicate $count"
  bind $w <Meta-e>		"jbr:cmd:edit $count"
  bind $w <Meta-h>		"jbr:cmd:help $count"
  bind $w <Meta-i>		"jbr:cmd:info $count"
  bind $w <Control-l>		"jbr:refresh $count"
  bind $w <Meta-l>		"jbr:cmd:latex $count"
  bind $w <Meta-m>		"jbr:cmd:move $count"
  bind $w <Meta-Key-M>		"jbr:cmd:rename $count"
  bind $w <Meta-n>		"jbr:cmd:newdir $count"
  bind $w <Meta-p>		"jbr:cmd:print $count"
  bind $w <Meta-q>		"jbr:cmd:quit $count"
  bind $w <Meta-r>		"jbr:cmd:destroy $count"
  bind $w <Meta-t>		"jbr:cmd:tex $count"
  bind $w <Meta-T>		"j:prompt_tcl"
  bind $w <Meta-u>		"jbr:cmd:process $count";# mnemonic: use
  bind $w <Meta-U>		"j:prompt_unix"
}

######################################################################
# jbr:refresh count - redisplay contents of window
#   really need to do this ONLY when necessary!  add heuristics!
######################################################################

proc jbr:refresh {count} {
  jbr:fill_window $count [jlist:pathtolist [pwd]]
}

######################################################################
# jbr:cd count dir - change directory in a window
#   just a first hack - needs to get initial directory from window
#   or column - or should it take only absolute paths?
#   more complicated, because we might be clicking on some column 
#   other than the last.
######################################################################

proc jbr:cd {count dir} {
  cd $dir
  jbr:refresh $count
}



######################################################################
#
# COMMAND ROUTINES
#
######################################################################

######################################################################
# jbr:cmd:help - this displays the help text
######################################################################

proc jbr:cmd:help {} {
# error checking!
  exec jdoc jbrowser &
}

######################################################################
# jbr:cmd:about - make the about box
######################################################################

proc jbr:cmd:about {} {
  global VERSION
  set about_browser [format {
    j:rt:hl "jbrowser"
    j:rt:cr
    j:rt:rm "by Jay Sekora, "
    j:rt:tt "js@bu.edu"
    j:rt:par
    j:rt:rm "A customisable directory browser for X Windows."
    j:rt:cr
    j:rt:rm "Version %s."
    j:rt:par
    j:rt:rm "Copyright \251 1992-1994 by Jay Sekora.  "
    j:rt:rm "All rights reserved, except that this file may be freely "
    j:rt:rm "redistributed in whole or in part for non\255profit, "
    j:rt:rm "noncommercial use."
    j:rt:par
    j:rt:rm "If you find bugs or have suggestions for improvement, "
    j:rt:rm "please let me know.  "
    j:rt:rm "Feel free to use bits of this code in your own "
    j:rt:tt "wish"
    j:rt:rm " scripts."
  } $VERSION]
  j:about .about $about_browser
  j:about:button .about {About jbrowser} $about_browser
  j:about:button .about {About the Author} [j:about_jay]
  j:about:button .about {About Tk and Tcl} [j:about_tktcl]
  
  tkwait window .about
}

######################################################################
# jbr:cmd:quit count - exit the script
######################################################################

proc jbr:cmd:quit {count} {
  if [j:confirm -text {Are you sure you want to quit?}] then {
    destroy .
    exit
  }
}



jbr:init

wm withdraw .

jbr:mkwindow 0 [jlist:pathtolist [pwd]] 3
