#!/usr/local/bin/wish -f
# 
# jhelp - multi-font help display

global VERSION
set VERSION {3.6/2.0}

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

# TO DO:
#   alternate definitions of all the section, rm, tt, etc. procedures to
#     allow man pages, PostScript, and TeX to be generated from the same
#     source (in jlibrary)

# CHANGES
#   NO LONGER allow `jhelp edit.tk', `jhelp jlibrary', etc.; help file
#     must match exactly with '.jhelp' suffix (no longer .tkhelp)
#   list help topics if no topic specified
#   `topic' menu
#   added `tcl command' entry

##############################################################################
# section - section title (stick in menu)
#   this is used in the help files (scripts)
##############################################################################

proc section {title body} {
  .menu.sections.m add command -label $title -command [format {
    global CURRENTSECTION
    set CURRENTSECTION {%s}
    .t configure -state normal
    j:rt text .t
    hl $CURRENTSECTION
    par
    eval {%s}
    .t configure -state disabled
    j:rt:done
  } $title $body]
}

##############################################################################
# qsection - section title (stick in menu)
#   this is used in the help files (scripts)
#   this is the `quick' section command, which is less human-readable
##############################################################################

proc qsection {title text state} {
  .menu.sections.m add command -label $title -command [format {
    global CURRENTSECTION
    .t configure -state normal
    set title {%s}
    set CURRENTSECTION $title
    j:rt text .t
    .t insert 1.0 %s
    jhelp:set_state .t {%s}
    .t mark set insert 1.0
    hl $title
    par
    .t configure -state disabled
    j:rt:done
  } $title $text $state]
}

##############################################################################
# jhelp:init - basic initialisation
##############################################################################

proc jhelp:init {} {
  catch {tk colormodel . color}		;# colour even on a 2-bit display
  
  global tk_library
  global env
  
  global NAME; set NAME $env(USER)
  global HOME; set HOME $env(HOME)
  
  # check for $HOME/.tk/jlibrary.tcl and read it in if it exists.
  # this contains library procedures.  it would normally be in $tk_library,
  # but we check for its presence here for people who can't put things in
  # $tk_library.
  #
  if {[file isfile "$HOME/.tk/jlibrary.tcl"]} then {
    source "$HOME/.tk/jlibrary.tcl"
  }
  # now check for some other library files which might be there (again,
  # these would normally be in $tk_library):
  j:source_config jrichtext.tcl
  j:source_config jabout.tcl
  j:rt:mkabbrevs			;# make abbrevs, e.g. rm for j:rt:rm:
  
  global PREFS				;# general jstools user preferences
  global HELPPREFS			;# user preferences for jhelp
  global HELPPATH
  set HELPPATH "
    .
    [glob -nocomplain ~/tk/jhelp]
    [glob -nocomplain ~/.tk/jhelp]
    $tk_library/jhelp
    $tk_library/jstools/jhelp
  "
  # read in user's text bindings.
  #   (not terribly useful in this application, but might define
  #   scrolling behaviour)
  j:source_config -directory $HOME .textbindings.tcl $HOME
}

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

proc jhelp:cmd:about {} {
  global VERSION
  set about_help [format {
    j:rt:hl "jhelp"
    j:rt:cr
    j:rt:rm "by Jay Sekora, "
    j:rt:tt "js@bu.edu"
    j:rt:par
    j:rt:rm "An X Windows application for viewing help files."
    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_help
  j:about:button .about {About jhelp} $about_help
  j:about:button .about {About The Author} [j:about_jay]
  j:about:button .about {About Tk and Tcl} [j:about_tktcl]
  
  tkwait window .about
}

##############################################################################
# jhelp:cmd:help - get help on jhelp
##############################################################################

proc jhelp:cmd:help {} {
  exec jhelp jhelp &
}

##############################################################################
# jhelp:cmd:help_prefs - preferences panel
##############################################################################

proc jhelp:cmd:help_prefs {} {
  global HELPPREFS
  
  set w .help_prefs
  toplevel $w
  wm title $w "Help Viewer Preferences"

  frame $w.size
  label $w.size.wl -text {Width:}
  entry $w.size.we -relief sunken -width 3 \
    -textvariable HELPPREFS(textwidth)
  label $w.size.hl -text {Height:}
  entry $w.size.he -relief sunken -width 3 \
    -textvariable HELPPREFS(textheight)

  j:colour_chooser $w.textbg -variable HELPPREFS(textbg) \
    -label "Normal Background:"
  j:colour_chooser $w.textfg -variable HELPPREFS(textfg) \
    -label "Normal Foreground:"
  j:colour_chooser $w.textsb -variable HELPPREFS(textsb) \
    -label "Selected Background:"
  j:colour_chooser $w.textsf -variable HELPPREFS(textsf) \
    -label "Selected Foreground:"
  
  label $w.textsbw-label -text "Selection Border Width:" -anchor w
  scale $w.textsbw -from 0 -to 25  -orient horizontal \
    -command {set HELPPREFS(textsbw)}
  $w.textsbw set $HELPPREFS(textsbw)
  
  label $w.textbw-label -text "Text Border Width:" -anchor w
  scale $w.textbw -from 0 -to 50  -orient horizontal \
    -command {set HELPPREFS(textbw)}
  $w.textbw set $HELPPREFS(textbw)
  
  j:buttonbar $w.b -default save -buttons [format {
    { 
      save Save {
        jhelp:configure_text .t
        j:write_prefs -array HELPPREFS -file jhelp-defaults
        destroy %s
      }
    } {
      done Done {
        jhelp:configure_text .t
        destroy %s
      }
    }
  } $w $w]
  
  pack $w.size.wl $w.size.we [j:filler $w.size] $w.size.hl $w.size.he \
    -in $w.size -side left

  pack \
    $w.size \
    [j:rule $w] \
    $w.textbg \
    [j:rule $w] \
    $w.textfg \
    [j:rule $w] \
    $w.textsb \
    [j:rule $w] \
    $w.textsf \
    [j:rule $w] \
    $w.textsbw-label $w.textsbw \
    [j:filler $w] \
    $w.textbw-label $w.textbw \
    [j:rule $w] \
    $w.b \
    -in $w -side top -fill x

  j:dialogue $w		;# position in centre of screen

  focus $w
  j:default_button $w.b.save $w.size.we $w.size.he $w
  j:tab_ring $w.size.we $w.size.he
  bind $w <Key-Tab> "focus $w.size.we"
}

##############################################################################
# jhelp:cmd:quit - quit
##############################################################################

proc jhelp:cmd:quit {} {
  if [j:confirm -text "Are you sure you want to quit?"] {
    destroy .
  }
}

##############################################################################
# jhelp:cmd:select_topic - prompt user to select topic from a list
##############################################################################

proc jhelp:cmd:select_topic {} {
  global HELPPREFS HELPPATH
  
  if [winfo exists .topics] {destroy .topics}
  toplevel .topics
  label .topics.l -text "Double\255click on a topic:"
  listbox .topics.lb -relief flat -geometry 30x20 \
    -yscroll {.topics.sb set} -exportselection false
  scrollbar .topics.sb -command {.topics.lb yview}
  pack append .topics \
    .topics.l {top fillx pady 10} \
    [j:rule .topics] {top fillx} \
    .topics.lb {left expand fill} \
    [j:rule .topics] {left filly} \
    .topics.sb {left filly}

  set topics {}
  
  foreach dir $HELPPATH {
    foreach file [glob -nocomplain $dir/*.jhelp] {
      set topic [file rootname [file tail $file]]
      if {[lsearch -exact $topics $topic] == -1} {
        lappend topics $topic
      }
    }
  }
  if {"x$topics" == "x"} {
    j:alert -text "Unable to find any help files."
    destroy .topics
    return
  }
  foreach topic [lsort $topics] {
    .topics.lb insert end $topic
  }
  bind .topics.lb <Double-Button-1> {
    jhelp:load_topic [%W get [%W nearest %y]]
    destroy .topics
  }
  j:dialogue .topics
}

##############################################################################
# jhelp:x_topic w x y - view help topic clicked on (hypertext)
##############################################################################

proc jhelp:x_topic { {w} {x} {y} } {
  set tags [$w tag names @$x,$y]
  
  foreach tag $tags {				;# find "TOPIC:..." tag
    if [string match "TOPIC:*" $tag] {
      set topic [lindex [split $tag ":"] 1]	;# get topic name
      exec jhelp $topic &
      break
    }
  }
}

##############################################################################
# jhelp:x_section w x y - go to section name clicked on (hypertext)
##############################################################################

proc jhelp:x_section { {w} {x} {y} } {
  set tags [$w tag names @$x,$y]
  
  foreach tag $tags {				;# find "SECTION:..." tag
    if [string match "SECTION:*" $tag] {
      set section [lindex [split $tag ":"] 1]	;# get section name
      .menu.sections.m invoke $section
      break
    }
  }
}

##############################################################################
# jhelp:set_state t state - set non-text content of a file
### DOCUMENT "state"
### should be shared with 
##############################################################################

proc jhelp:set_state { {t} {state} } {
  set tags [lindex $state 0]
  set marks [lindex $state 1]
  foreach pair $marks {
    $t mark set [lindex $pair 0] [lindex $pair 1]
  }
  foreach pair $tags {
    set tag [lindex $pair 0]
    set ranges [lindex $pair 1]
    set length [llength $ranges]
    for {set i 0; set i1 1} {$i1 < $length} {incr i 2; incr i1 2} {
      $t tag add $tag [lindex $ranges $i] [lindex $ranges $i1]
    }
  }
  $t yview -pickplace insert
}

##############################################################################
# jhelp:load_topic - find and load a help file
#   the contents of the help file will set up the Sections menu
##############################################################################

proc jhelp:load_topic { topic } {
  global HELPPREFS HELPPATH
  
  .menu.sections.m delete 0 last
  
  set FOUND 0
  set filename "$topic.jhelp"
  foreach dir $HELPPATH {
    if [file exists $dir/$filename] then {
      set FOUND 1
      source $dir/$filename
      break
    }
  }
  
  if {!$FOUND} then {
    .t configure -state normal
    j:rt text .t
    j:rt:hl "Can't find a help file for `$topic'."
    j:rt:par
    j:rt:rm "The requested help file was not found.  "
    j:rt:rm "It may not have been installed at your site."
    j:rt:done
    .t configure -state disabled
    tkwait window .
    exit 1
  }
  
  wm title . "Help for `$topic'"
  wm iconname . "$topic"
  
  jhelp:first_section
}

##############################################################################
# jhelp:first_section - display the first section
##############################################################################

proc jhelp:first_section {} {
  .menu.sections.m invoke 0
}

##############################################################################
# jhelp:next_section - go forward one section
##############################################################################

proc jhelp:next_section {} {
  global CURRENTSECTION
  set currentnumber [.menu.sections.m index $CURRENTSECTION]
  incr currentnumber
  .menu.sections.m invoke $currentnumber
}

##############################################################################
# jhelp:back_section - go backward one section
##############################################################################

proc jhelp:back_section {} {
  global CURRENTSECTION
  set currentnumber [.menu.sections.m index $CURRENTSECTION]
  incr currentnumber -1
  if {!($currentnumber < 0)} {
    .menu.sections.m invoke $currentnumber
  }
}

##############################################################################
##############################################################################
###   interface elements
##############################################################################
##############################################################################

##############################################################################
# jhelp:mkmenus - menu bar
##############################################################################

proc jhelp:mkmenus {} {
  frame .menu -borderwidth 2 -relief raised
  
  jhelp:mkmenu:help
  jhelp:mkmenu:topics
  jhelp:mkmenu:sections
  
  pack .menu -in . -side top -fill x
  
  tk_menuBar .menu .menu.help .menu.topics .menu.sections
}

##############################################################################
# jhelp:mkmenu:help - help menu
##############################################################################

proc jhelp:mkmenu:help {} {
  menubutton .menu.help -text {Help} -menu .menu.help.m
  pack .menu.help -in .menu -side left

  menu .menu.help.m
  .menu.help.m add command -label {Help with Help} -accelerator {[h]} \
    -command {jhelp:cmd:help}
  .menu.help.m add command -label {About Help . . .} \
    -command {jhelp:cmd:about}
  .menu.help.m add command -label {Global Preferences . . .} \
    -command {j:global_pref_panel}
  .menu.help.m add command -label {Help Viewer Preferences . . .} \
    -command {jhelp:cmd:help_prefs}
  .menu.help.m add separator
  .menu.help.m add command -label {Issue Tcl Command . . .} \
    -accelerator {[T]} -command {j:prompt_tcl}
  .menu.help.m add command -label {Issue Unix Command . . .} \
    -accelerator {[U]} -command {j:prompt_unix}
  .menu.help.m add separator
  .menu.help.m add command -label {Quit} -accelerator {[q]} \
    -command {destroy .}
}

##############################################################################
# jhelp:mkmenu:topics - topics menu
##############################################################################

proc jhelp:mkmenu:topics {} {
  menubutton .menu.topics -text {Topics} -menu .menu.topics.m
  menu .menu.topics.m
  .menu.topics.m add command -label {Select a Topic . . .} \
    -accelerator {[l]} -command {jhelp:cmd:select_topic}
  pack .menu.topics -in .menu -side left

}

##############################################################################
# jhelp:mkmenu:sections - sections menu
##############################################################################

proc jhelp:mkmenu:sections {} {
  menubutton .menu.sections -text {Sections} -menu .menu.sections.m
  pack .menu.sections -in .menu -side left

  menu .menu.sections.m
}

##############################################################################
# jhelp:mkbuttons - buttons at bottom
##############################################################################

proc jhelp:mkbuttons {} {
  j:buttonbar .b -buttons {
    {done Done {destroy .}}
    {next "Next" {jhelp:next_section}}
    {back "Back" {jhelp:back_section}}
  }
  pack .b [j:rule .] -in . -side bottom -fill x
  uplevel #0 {trace variable CURRENTSECTION w jhelp:enable_buttons}
}

##############################################################################
# jhelp:enable_buttons - enable/disable "Next" and "Back" buttons based
#   on CURRENTSECTION
##############################################################################

proc jhelp:enable_buttons {args} {
  global CURRENTSECTION
  if {[.menu.sections.m index $CURRENTSECTION] == 0} {
    .b.back configure -state disabled
  } else {
    .b.back configure -state normal
  }
  if {[.menu.sections.m index $CURRENTSECTION] ==
      [.menu.sections.m index last]} {
    .b.next configure -state disabled
  } else {
    .b.next configure -state normal
  }
}
  
##############################################################################
# jhelp:mktext - text widget
##############################################################################

proc jhelp:mktext {} {
  global PREFS
  if {[lsearch [array names PREFS] {scrollbarside}] == -1} {
    set PREFS(scrollbarside) right ;# make sure it's defined
  }
  text .t -yscrollcommand {.s set} \
    -width 70 -height 18 -borderwidth 4 \
    -setgrid true -wrap word
  scrollbar .s -relief flat -command {.t yview}
  pack .s [j:rule .] -in . -side $PREFS(scrollbarside) -fill y
  pack .t -in . -side $PREFS(scrollbarside) -fill y
  jhelp:configure_text .t
  
  focus .t
  focus default .t				;# get focus when dialogs die
  tk_bindForTraversal .t
}

##############################################################################
# jhelp:configure_text w - text widget configuration
##############################################################################

proc jhelp:configure_text { { w .t } } {
  global HELPPREFS
  
  if {$HELPPREFS(textwidth) < 20} {set HELPPREFS(textwidth) 20}
  if {$HELPPREFS(textheight) < 4} {set HELPPREFS(textheight) 4}
  
  # fonts:
  j:rt text .t				;# let j:rt set default font
  j:rt:done

  # hypertext styles and bindings:
  $w tag configure xref-topic -underline 1
  $w tag configure xref-section -underline 1
  $w tag bind xref-topic <ButtonRelease-1> {jhelp:x_topic %W %x %y}
  $w tag bind xref-section <ButtonRelease-1> {jhelp:x_section %W %x %y}
  
  # other configuration
  catch {.t configure -width $HELPPREFS(textwidth)}
  catch {.t configure -height $HELPPREFS(textheight)}
  catch {.t configure -background $HELPPREFS(textbg)}
  catch {.t configure -foreground $HELPPREFS(textfg)}
  catch {.t configure -borderwidth $HELPPREFS(textbw)}
  catch {.t configure -selectbackground $HELPPREFS(textsb)}
  catch {.t configure -selectforeground $HELPPREFS(textsf)}
  catch {.t configure -selectborderwidth $HELPPREFS(textsbw)}
}

##############################################################################
# jhelp:mkbindings widgets - text widget command bindings
##############################################################################

proc jhelp:mkbindings { { widgets .t } } {
  foreach w $widgets {
    bind $w <Meta-h> {jhelp:cmd:help}
    bind $w <Meta-l> {jhelp:cmd:select_topic}
    bind $w <Meta-q> {jhelp:cmd:quit}
    bind $w <Meta-T> {j:prompt_tcl}
    bind $w <Meta-U> {j:prompt_unix}
  }
}

##############################################################################
# jhelp:first_help_file - read first help file from command line
##############################################################################

proc jhelp:first_help_file {} {
  global argc argv
  
  if {$argc > 1} then {
    wm withdraw .
    j:alert -text \
      "jhelp called with too many arguments.\nUsage: jhelp <topic>"
    exit 1
  }
  
  if {$argc != 1} then {
    update
    jhelp:cmd:select_topic
  } else {
    set topic [lindex $argv 0]
    jhelp:load_topic $topic
  }
}

##############################################################################
##############################################################################
###   end of procedure definitions
##############################################################################
##############################################################################


##############################################################################
# FINAL SETUP
##############################################################################

jhelp:init

# read in user's .tk/jhelprc.tcl and .tk/jhelpprefs.tcl files:
#
j:source_config jhelprc.tcl		;# just source the file, if any
j:read_standard_prefs			;# get defaults from ~/.tk/defaults
j:read_prefs -array HELPPREFS -file jhelp-defaults {
  {textwidth 70}
  {textheight 24}
  {textwrap char}
  {textbg white}
  {textfg black}
  {textsb black}
  {textsf white}
  {textbw 2}
  {textsbw 2}
}

jhelp:mkmenus
jhelp:mkbuttons
jhelp:mktext					;# (sets focus)
jhelp:mkbindings .t
jhelp:first_help_file				;# read in first help file
