# -*- tcl -*-

# $Id: builder.tcl,v 0.5 1998/07/16 17:07:07 chris Exp $

# Cbrowser is a C/C++ source code indexing, querying and browsing tool
# Copyright (C) 1998  B. Christopher Felaco

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

# For more information about Cbrowser and its author see:
#    URL:http://www.ziplink.net/~felaco/cbrowser/
#
# Feel free to contact me at URL:mailto:felaco@ziplink.net with enhancements
# or suggestions.

# $Log: builder.tcl,v $
# Revision 0.5  1998/07/16 17:07:07  chris
# Build_dialog now prompts for filename first, so it needs the parent of the
# file dialog.
# Magic scrollbars for directory list.
# Minsize for dialogs.
# Use tk_messageBox instead of tk_dialog.
#
# Revision 0.4  1998/06/27 22:35:41  chris
# Initial revision, synched with cbrowser v0.4
#

#----------------------------------------------------------------------------
# This file contains functions that implement the database builder portions
# of cbrowser.  The functions cannot operate unless called from within
# cbrowser.
#----------------------------------------------------------------------------

##############################################################################
#
#  Purpose    : Create or raise a dialog to get information about building
#               a database.
#
#  Parameters : toplevel - the toplevel build dialog to create
#               parent - the parent window of the file selection dialog
#
#  Result     : NONE
#
##############################################################################

proc build_dialog {toplevel parent} {
  global env which_list source_list include_list build_status

  if {[strcmp $build_status "none"] != 0} {
    tk_messageBox -parent $toplevel -title "Error" -type ok -icon error \
        -message "A build is already in progress."
    return
  }

  set new_dbase [select_database $parent 1]
  if {[strlen $new_dbase] == 0} {
    return
  }

  set source_list [file dirname $new_dbase]
  set include_list [file dirname $new_dbase]
  catch {
    set source_list [split $env(SOURCEDIRS) ":"]
    set include_list [split $env(INCLUDEDIRS) ":"]
  }

  set which_list source

  if [winfo exists $toplevel] {

    # Reconfigure the OK button for the new database.  Yeah it's stupid...
    $toplevel.ok configure -command "wm withdraw $toplevel;
       build_database $toplevel.progress $new_dbase \$build_force($toplevel)"

    wm deiconify $toplevel
    catch {raise $toplevel}

    build_switch $toplevel source
    return
  }

  toplevel $toplevel

  # Row 1 contains only the radio buttons
  frame $toplevel.row1
  pack $toplevel.row1 -side top ; #-fill x -expand yes

  radiobutton $toplevel.source -variable which_list -text "Source" \
      -value source  -command "build_switch $toplevel source"
  radiobutton $toplevel.include -variable which_list -text "Include" \
      -value include -command "build_switch $toplevel include"

  pack $toplevel.source $toplevel.include -in $toplevel.row1 -side left

  # Next row contains the entry field
  entry $toplevel.entry -width 30
  bind $toplevel.entry <Return> [list $toplevel.add invoke]
  pack $toplevel.entry -side top -fill x -padx 5

  # Next row contains the Add / Remove buttons
  frame $toplevel.addremove
  pack $toplevel.addremove -side top

  button $toplevel.add -text "Add" -command "build_add $toplevel" -width 10
  button $toplevel.remove -text "Remove" -command "build_remove $toplevel" -width 10

  pack $toplevel.add    -in $toplevel.addremove -side left -padx 5
  pack $toplevel.remove -in $toplevel.addremove -side left -padx 5

  # Next row contains the listbox
  frame $toplevel.list_row
  pack $toplevel.list_row -expand yes -fill both -side top

  listbox $toplevel.list \
      -selectmode extended \
      -exportselection false

  magic_scroll $toplevel.list $toplevel.list_row

  bind $toplevel.list <Delete> [list $toplevel.remove invoke]
  bind $toplevel.list <BackSpace> [list $toplevel.remove invoke]
  bind $toplevel.list <Button-1> \
      "$toplevel.entry delete 0 end
       $toplevel.entry insert 0 \[ %W get \[%W nearest %y\]\]
      "

  # The next row is for the forced rebuild
  checkbutton $toplevel.force -text "Force Rebuild" \
      -variable build_force($toplevel)
  pack  $toplevel.force -side top

  # The bottom row should be the buttonbar
  frame $toplevel.buttonbar
  pack  $toplevel.buttonbar -side top;

  button $toplevel.ok -text OK -width 8 \
      -command "wm withdraw $toplevel;
                build_database $toplevel.progress $new_dbase \$build_force($toplevel)"
  button $toplevel.cancel -text Cancel -width 8 \
      -command "wm withdraw $toplevel; set build_status none"
  global button_defaults
  if {$button_defaults} {
    $toplevel.ok configure -default active
    $toplevel.cancel configure -default normal
  }
  # The build_status is set so that another procedure can wait on the variable
  # to detect completion of the build.
  button $toplevel.help -text Help -width 8 -command {help_proc building}
  pack $toplevel.ok $toplevel.cancel $toplevel.help -side left \
      -in $toplevel.buttonbar -padx 10

  bind $toplevel <<Cancel>> "$toplevel.cancel invoke"
  bind $toplevel <<Help>> "$toplevel.help invoke"

  wm title $toplevel "CSCOPE Build Dialog"
  wm geometry $toplevel 450x350

  # Call build_switch to initialize the listbox
  build_switch $toplevel source
}

##############################################################################
#
#  Purpose    : Perform a database build.
#
#  Parameters : toplevel - the toplevel build progress dialog
#               new_dbase - the new database file to construct
#               unconditional - boolean to force rebuild of database
#
#  Result     : NONE
#
##############################################################################

proc build_database {toplevel new_dbase unconditional} {
  global source_list include_list env build_status query_backend database_file

  if {[strcmp $new_dbase $database_file] == 0} {
    global query_pipe
    if [info exists query_pipe] {
      catch {close $query_pipe}
      unset query_pipe
    }
  }

  set env(SOURCEDIRS) [join $source_list ":"]
  set env(INCLUDEDIRS) [join $include_list ":"]

  if {![winfo exists $toplevel]} {
    toplevel $toplevel
    label $toplevel.label -text "Build Progress"
    label $toplevel.line -width 50 -relief sunken
    pack $toplevel.label $toplevel.line -side top -fill x
    button $toplevel.abort -text Abort
    pack $toplevel.abort -side top
    wm title $toplevel "Build Progress"
  } else {
    wm deiconify $toplevel
    catch {raise $toplevel}
  }

  # Set an initial value for the progress field.
  $toplevel.line configure -text "(waiting for response)"

  # Switch to the directory of the database so relative paths are set properly
  set cwd [pwd]
  cd [file dirname $new_dbase]

  # Start the build process.  When building, status lines are written to
  # stderr, so the output must be redirected into one stream.
  set cmd "|$query_backend -b -f $new_dbase"
  if {$unconditional} {
    lappend cmd "-u"
  }
  lappend cmd "|&" "cat"
  set f [open $cmd "r"]
  cd $cwd
  if {[strlen $f] == 0} {
    error
  }

  # Disable submit temporarily
  #.submit configure -state disabled

  # Register the handler for the input pipe
  fileevent $f readable "build_handler $toplevel $f"
  set build_status "building $new_dbase"

  update idletasks

  # Set up the abort button on the dialog.  This must be done each time so
  # that the file argument is updated.
  $toplevel.abort configure -command [list build_abort $f]

  # Process file and user events until done or user presses Abort
  tkwait variable build_status

  catch {close $f; fileevent $f readable ""}

  $toplevel.line configure -text "Build Complete"
  after 1000 "wm withdraw $toplevel"

  # Clear the build status flag
  set build_status none
}

##############################################################################
#
#  Purpose    : Add directories matching wildcard pattern to the list of
#                included directories for a database build.
#
#  Parameters : toplevel - the toplevel build dialog
#
#  Result     : NONE
#
##############################################################################

set build_status "none"

proc build_add {toplevel} {
  global which_list

  upvar ${which_list}_list the_list

  set pattern [$toplevel.entry get]
  if [catch {glob $pattern} dir_list] {
    tk_messageBox -parent $toplevel -title "Error" -type ok -icon error \
        -message "No directories match the pattern:\n$pattern"
    return
  }
  set junk_list ""
  foreach dir $dir_list {
    if {[file isdirectory $dir] &&
        [lsearch $the_list $dir] < 0 &&
        [lsearch $the_list "$dir/"] < 0} {
      $toplevel.list insert end $dir
      lappend the_list $dir
    } else {
      lappend junk_list $dir
    }
  }
  if {[strlen $junk_list] > 0} {
    tk_messageBox -parent $toplevel -title "Error" -type ok -icon error \
        -message "The following are not directories:\n[join $junk_list \n]"
  }
}

##############################################################################
#
#  Purpose    : Remove an item from the directory list.
#
#  Parameters : toplevel - the toplevel build dialog
#
#  Result     : NONE
#
##############################################################################

proc build_remove {toplevel} {
  global which_list

  upvar ${which_list}_list the_list

  foreach entry [$toplevel.list curselection] {
    set value [$toplevel.list get $entry]

    # Find the index of the entry
    set index [lsearch -exact $the_list $value]
    # Remove the entry
    set the_list [lreplace $the_list $index $index]
  }

  # Clear the listbox and re-add the elements
  $toplevel.list delete 0 end
  eval $toplevel.list insert 0 $the_list
}

##############################################################################
#
#  Purpose    : Switch the list to the source directories or include dirs.
#
#  Parameters : toplevel - the toplevel build dialog
#               new_value - the new value, either "source" or "include"
#
#  Result     : 
#
##############################################################################

proc build_switch {toplevel new_value} {
  upvar ${new_value}_list the_list

  $toplevel.list delete 0 end
  eval $toplevel.list insert 0 $the_list
}

##############################################################################
#
#  Purpose    : Handle status output from a database build.
#
#  Parameters : toplevel - the toplevel build dialog
#               pipe - the file handle of the input pipe
#
#  Result     : NONE
#
##############################################################################

proc build_handler {toplevel pipe} {
  global current_results

  if [eof $pipe] {
    catch {close $pipe}
    global build_status
    set build_status "done"
    return
  }

  gets $pipe line

  $toplevel.line configure -text $line
  update
}

##############################################################################
#
#  Purpose    : Abort a running database build.
#
#  Parameters : pipe - the file handle of the input pipe
#
#  Result     : NONE
#
##############################################################################

proc build_abort {pipe} {
  global build_status

  # The process must be killed, or the file close will hang
  foreach pid [pid $pipe] {
    exec kill $pid
  }
  set build_status "abort"
}


