#
# Columns for listing CVS files and their status
#
#
# $Id: dircanvas.tcl,v 1.19 2002/01/21 08:19:58 dorothyr Exp $
#

proc DirCanvas:create {w args} {
  global cvscfg
  global cvsglb
  global arr
  global incvs

  gen_log:log T "ENTER ($w $args)"

  if {[catch "image type folder"]} {
    DirCanvas:loadimages
  }

  frame $w.filecol -relief sunken -bd 2
  frame $w.statcol -relief sunken -bd 2
  frame $w.datecol -relief sunken -bd 2
  frame $w.wrevcol -relief sunken -bd 2
  frame $w.editcol -relief sunken -bd 2

  eval canvas $w.filecol.list $args
  eval canvas $w.statcol.list $args
  eval canvas $w.datecol.list $args
  eval canvas $w.wrevcol.list $args
  eval canvas $w.editcol.list $args
  scrollbar $w.yscroll -orient vertical -command "DirCanvas:scroll_windows $w" \
    -highlightthickness 0

  pack $w.yscroll -side right -fill y

  set cvsglb(fmbg) [lindex [$w.filecol configure -background] 4]
  if {[winfo exists .workdir]} {
    set cvsglb(fg) [lindex [.workdir.top.bmodbrowse configure -foreground] 4]
    set cvsglb(dfg) [lindex [.workdir.top.bmodbrowse configure -disabledforeground] 4]
    set buttonhilite [lindex [.workdir.top.bmodbrowse configure -highlightbackground] 4]
  }
  set cvsglb(canvbg) [lindex [$w.filecol.list configure -background] 4]
  set selcolor [option get . selectColor selectColor]
  if {[string length $selcolor]} {
    set cvsglb(hlbg) $selcolor
  }
  if {$cvsglb(hlbg) == $cvsglb(canvbg)} {
    set cvsglb(hlbg) $buttonhilite
  }
  if {! [info exists cvsglb(glb_highlight]} {
    set cvsglb(glb_highlight) $cvsglb(hlbg)
  }

  DirCanvas:column $w filecol "file"
  DirCanvas:column $w statcol "status"
  DirCanvas:column $w datecol "date"
  if {$incvs} {
    DirCanvas:column $w wrevcol "revision"
    DirCanvas:column $w editcol "editors"
  }

  # Put an extra arrow on the file column for sorting by status
  set statusbutton $w.filecol.head.statbut
  button $statusbutton -image arr_dn -bg $cvsglb(fmbg) \
    -relief raised -bd 1 -highlightthickness 0
  set arr(filestatcol) $statusbutton
  pack $statusbutton -side left
  bind $statusbutton <ButtonPress-1> \
    "DirCanvas:toggle_col $w statcol"
  bind $statusbutton <ButtonPress-2> \
    "DirCanvas:sort_by_col $w statcol -decreasing"
  bind $statusbutton <ButtonPress-3> \
    "DirCanvas:sort_by_col $w statcol -increasing"

  gen_log:log D "sort_pref:  $cvsglb(sort_pref)"
  set col [lindex $cvsglb(sort_pref) 0]
  set sense [lindex $cvsglb(sort_pref) 1]
  gen_log:log D "$incvs  $col"
  if { (! $incvs )  && ( $col == "editcol" || $col == "wrevcol") } {
    gen_log:log T "setting sort to column \"filecol!\""
    set col "filecol"
    set sense "-decreasing"
  }
  if {[string match "-inc*" $sense]} {
    gen_log:log D "sort column $col -increasing"
    $arr($col) configure -image arh_up
    if {$col == "statcol"} {$arr(filestatcol) configure -image arh_up}
  } else {
    gen_log:log D "sort column $col -decreasing"
    if {[info exists arr($col)] && [winfo exists $arr($col)]} {
      $arr($col) configure -image arh_dn
    gen_log:log D "arr(col) = arr($col);  arr($col) = $arr($col)"
    }
    if {$col == "statcol"} {
      $arr(filestatcol) configure -image arh_dn
    }
  }

  focus $w.filecol.list
  if {! [winfo exists $w.paper_pop]} {
    DirCanvas:makepopup $w
  }
  gen_log:log T "LEAVE"
}

proc DirCanvas:column {w column headtext} {
  global cvscfg
  global cvsglb
  global incvs
  global arr

  gen_log:log T "ENTER ($w $column headtext)"
  gen_log:log T "showstatcol $cvscfg(showstatcol) showdatecol $cvscfg(showdatecol) econtrol $cvscfg(econtrol)"

  $w.$column.list configure -yscrollcommand "$w.yscroll set"
  bind $w.$column.list <Next>  "DirCanvas:scroll_windows $w scroll  1 pages"
  bind $w.$column.list <Prior> "DirCanvas:scroll_windows $w scroll -1 pages"
  bind $w.$column.list <Down>  "DirCanvas:scroll_windows $w scroll  1 units"
  bind $w.$column.list <Up>    "DirCanvas:scroll_windows $w scroll -1 units"
  bind $w.$column.list <B2-Motion> "DirCanvas:drag_windows $w %W %y"
  bind $w.$column.list <MouseWheel> \
      "DirCanvas:scroll_windows $w scroll \[expr {-(%D/120)*4}\] units"

  frame $w.$column.head -relief raised -bd 2
  label $w.$column.head.lbl -text "$headtext"
  button $w.$column.head.sbut -image arr_dn -relief flat \
    -highlightthickness 0 -bg $cvsglb(fmbg)
  set arr($column) $w.$column.head.sbut
  gen_log:log D "$w.$column.head.sbut"


  bind $w.$column.head.sbut <ButtonPress-1> "DirCanvas:toggle_col $w $column"
  bind $w.$column.head.sbut <ButtonPress-2> "DirCanvas:sort_by_col $w $column -decreasing"
  bind $w.$column.head.sbut <ButtonPress-3> "DirCanvas:sort_by_col $w $column -increasing"

  scrollbar $w.$column.xscroll -orient horizontal -width 10 \
     -command "$w.$column.list xview"
  pack $w.$column.xscroll -side bottom -fill x

  if {$column == "statcol"} {
    if {$cvscfg(showstatcol)} {
      DirCanvas:map_column $w statcol
    } else {
      gen_log:log T "LEAVE (skipping statcol)"
    }
    return
  }
  if {$column == "datecol"} {
    if {$cvscfg(showdatecol)} {
      DirCanvas:map_column $w datecol
    } else {
      gen_log:log T "LEAVE (skipping datecol)"
    }
    return
  }
  if {$column == "editcol"} {
    if {$incvs && $cvscfg(econtrol)} {
      DirCanvas:map_column $w editcol
    } else {
      gen_log:log T "LEAVE (skipping editcol)"
    }
    return
  }
  DirCanvas:map_column $w $column
}

proc DirCanvas:map_column {w column} {

  gen_log:log T "ENTER ($w $column)"
  set mapped_columns [pack slaves $w]
  gen_log:log D "$mapped_columns"

  if {[lsearch -exact $mapped_columns "$w.statcol"] > -1} {
    set leftcol "$w.statcol"
  } else {
    set leftcol "$w.filecol"
  }

  if {$column == "datecol"} {
    pack $w.$column -after $leftcol -side left -fill both -expand yes
  } elseif {$column == "statcol"} {
    pack $w.$column -after $w.filecol -side left -fill both -expand yes
  } elseif {$column == "editcol"} {
    pack $w.$column -after $w.wrevcol -side left -fill both -expand yes
  } else {
    pack $w.$column -side left -fill both -expand yes
  }
  pack $w.$column.head -side top -fill x -expand no
  pack $w.$column.head.sbut -side right
  pack $w.$column.head.lbl -side right -fill x -expand yes
  pack $w.$column.list -side top -fill both -ipadx 2 -expand yes
}

#
# Pass configuration options to the list widget
#
proc DirCanvas:config {w args} {
  gen_log:log T "ENTER ($w $args)"
  eval $w.filecol.list config $args
  eval $w.statcol config $args
  eval $w.datecol config $args
  eval $w.wrevcol config $args
  eval $w.editcol config $args
  gen_log:log T "LEAVE"
}

#
# Insert a new element $v into the list $w.
#
proc DirCanvas:newitem {w f} {
  global DirList
  global Filelist
  global cvsglb

  gen_log:log T "ENTER ($w $f)"

  set DirList($w:$f:name) $f
  gen_log:log D "Newitem $f status $Filelist($f:status)"
  set DirList($w:$f:status) $Filelist($f:status)
  set DirList($w:$f:date) $Filelist($f:date)
  set DirList($w:$f:sticky) $Filelist($f:stickytag)
  set DirList($w:$f:option) $Filelist($f:option)
  if { [info exists Filelist($f:editors)]} {
    set DirList($w:$f:editors) $Filelist($f:editors)
  } else {
    set DirList($w:$f:editors) ""
  }
  set DirList($w:$f:selected) 0

  DirCanvas:buildwhenidle $w
  #gen_log:log T "LEAVE"
}

proc DirCanvas:loadimages { } {
  global cvscfg

  image create photo paper \
    -format gif -file [file join $cvscfg(bitmapdir) paper.gif]
  image create photo cvsdir \
    -format gif -file [file join $cvscfg(bitmapdir) cvsdir.gif]
  image create photo folder \
    -format gif -file [file join $cvscfg(bitmapdir) folder.gif]

  image create photo stat_ques \
    -format gif -file [file join $cvscfg(bitmapdir) stat_ques.gif]
  image create photo stat_ex \
    -format gif -file [file join $cvscfg(bitmapdir) stat_ex.gif]
  image create photo stat_kb \
    -format gif -file [file join $cvscfg(bitmapdir) stat_kb.gif]
  image create photo stat_plus_kb \
    -format gif -file [file join $cvscfg(bitmapdir) stat_plus_kb.gif]
  image create photo stat_ok \
    -format gif -file [file join $cvscfg(bitmapdir) stat_ok.gif]
  image create photo stat_ood \
    -format gif -file [file join $cvscfg(bitmapdir) stat_ood.gif]
  image create photo stat_merge \
    -format gif -file [file join $cvscfg(bitmapdir) stat_merge.gif]
  image create photo stat_mod \
    -format gif -file [file join $cvscfg(bitmapdir) stat_mod.gif]
  image create photo stat_plus \
    -format gif -file [file join $cvscfg(bitmapdir) stat_plus.gif]
  image create photo stat_minus \
    -format gif -file [file join $cvscfg(bitmapdir) stat_minus.gif]
  image create photo stat_conf \
    -format gif -file [file join $cvscfg(bitmapdir) stat_conf.gif]
}

#
# Delete element $v from the list $w.
# deleted.
#
proc DirCanvas:delitem {w v} {
  gen_log:log T "ENTER ($w $v)"
  DirCanvas:buildwhenidle $w
  gen_log:log T "LEAVE"
}

proc DirCanvas:deltree {w} {
  global DirList

  catch {destroy $w.filecol}
  catch {destroy $w.statcol}
  catch {destroy $w.datecol}
  catch {destroy $w.wrevcol}
  catch {destroy $w.editcol}
  catch {destroy $w.yscroll}
  foreach t [array names DirList $w:*] {
    unset DirList($t)
  }
}

proc DirCanvas:flash {widg y} {
  global cvsglb

  $widg.tx$y configure -relief raised
}

proc DirCanvas:unflash {widg y} {
  global cvsglb

  set savfg [lindex [$widg.tx$y configure -foreground] 4]
  set savbg [lindex [$widg.tx$y configure -background] 4]
  set relief [lindex [$widg.tx$y configure -relief] 4]
  $widg.tx$y configure -fg $savfg -bg $savbg
  if {$savbg != $cvsglb(hlbg)} {
    $widg.tx$y configure -relief flat
  }
}

#
# Change the selection to the indicated item
#
proc DirCanvas:setselection {w y f} {
  global DirList
  global cvsglb

  gen_log:log T "ENTER ($w $y $f)"

  DirCanvas:unselectall $w
  gen_log:log D "adding \"$f\""
  set DirList($w:$f:selected) 1
  set DirList($w:selection) [list "$f"]
  set cvsglb(current_selection) $DirList($w:selection)
  $w.filecol.list.tx$y configure -relief raised \
    -bg $cvsglb(hlbg) -fg $cvsglb(hlfg)

  # This seems to be necessary to enable some OS's (esp. Mandrake)
  # to get a double-click
  after 250

  gen_log:log T "LEAVE"
}

proc DirCanvas:addselection {w y f} {
  global DirList
  global cvsglb

  gen_log:log T "ENTER ($w $y $f)"

  # If it's already selected, unselect it
  if { $DirList($w:$f:selected) } {
    gen_log:log D "\"$f\" was selected - unselecting"
    set DirList($w:$f:selected) 0
    set idx [lsearch -exact $DirList($w:selection) "$f"]
    if {$idx > -1} {
      gen_log:log D "found \"$f\" - removing from selection list"
      set DirList($w:selection) [lreplace $DirList($w:selection) $idx $idx]
      gen_log:log D "$DirList($w:selection)"
      $w.filecol.list.tx$y configure -relief flat \
        -bg $cvsglb(canvbg) -fg $cvsglb(fg) \
    }
  } else {
    gen_log:log D "adding \"$f\""
    $w.filecol.list.tx$y configure -relief raised \
      -bg $cvsglb(hlbg) -fg $cvsglb(hlfg)
    set DirList($w:$f:selected) 1
    lappend DirList($w:selection) "$f"
  }
  set cvsglb(current_selection) $DirList($w:selection)
  gen_log:log D "selection is \"$cvsglb(current_selection)\""
  gen_log:log T "LEAVE"
}

proc DirCanvas:addrange {w y f} {
  global DirList
  global cvsglb

  gen_log:log T "ENTER ($w $y $f)"
  if {! [info exists DirList($w:selection)] || [llength $DirList($w:selection)] < 1} {
    $w.filecol.list.tx$y configure -relief raised \
      -bg $cvsglb(hlbg) -fg $cvsglb(hlfg)
    set DirList($w:$f:selected) 1
    lappend DirList($w:selection) "$f"
    set cvsglb(current_selection) $DirList($w:selection)
    return
  }
  set sel1 [lindex $DirList($w:selection) 0]

  set iy $DirList($w:$sel1:y)
  gen_log:log D "Selection 1  :  $sel1 y=$iy"
  gen_log:log D "New Selection:  $f y=$y\n"

  if { $y > $iy } {
    foreach item [array names DirList $w:*:name] {
      set j $DirList($item)
      set jy $DirList($w:$j:y)
      if { $jy > $iy && $y > $jy} {
        gen_log:log D "$j y=$jy"
        $w.filecol.list.tx$jy configure -relief raised \
          -bg $cvsglb(hlbg) -fg $cvsglb(hlfg)
        set DirList($w:$j:selected) 1
        if {[lsearch -exact $DirList($w:selection) $j] == -1} {
          lappend DirList($w:selection) "$j"
        }
      }
    }
  } elseif {$y < $iy } {
    foreach item [array names DirList $w:*:name] {
      set j $DirList($item)
      set jy $DirList($w:$j:y)
      if { $jy < $iy && $y < $jy} {
        gen_log:log D "$j y=$jy"
        $w.filecol.list.tx$jy configure -relief raised \
          -bg $cvsglb(hlbg) -fg $cvsglb(hlfg)
        set DirList($w:$j:selected) 1
        if {[lsearch -exact $DirList($w:selection) $j] == -1} {
          lappend DirList($w:selection) "$j"
        }
      }
    }
  }

  $w.filecol.list.tx$y configure -relief raised \
    -bg $cvsglb(hlbg) -fg $cvsglb(hlfg)
  set DirList($w:$f:selected) 1
  if {[lsearch -exact $DirList($w:selection) $f] == -1} {
    lappend DirList($w:selection) "$f"
  }
  
  set cvsglb(current_selection) $DirList($w:selection)
  gen_log:log D "selection is \"$cvsglb(current_selection)\""
  gen_log:log T "LEAVE"
}

proc DirCanvas:unselectall {w} {
  global DirList
  global cvsglb

  gen_log:log T "ENTER ($w)"
  foreach s [array names DirList $w:*:name] {
    set f $DirList($s)
    set y $DirList($w:$f:y)
    set DirList($w:$f:selected) 0
    $w.filecol.list.tx$y configure -relief flat \
      -bg $cvsglb(canvbg) -fg $cvsglb(fg)
    # Prepending ./ to the filename prevents tilde expansion
    if {! [file exists ./$f]} {
      # If a file CVS knows about doesn't exist, write its name in light ink
      $w.filecol.list.tx$y configure -fg $cvsglb(dfg)
    }
  }
  set DirList($w:selection) {}
  set cvsglb(current_selection) {}
  gen_log:log T "LEAVE"
}

proc DirCanvas:areaStart {w x y} {
  global areaX1 areaY1

  gen_log:log T "ENTER ($w $x $y)"
  $w delete area
  set areaX1 [$w canvasx $x]
  set areaY1 [$w canvasy $y]
}

proc DirCanvas:areaStroke {w x y} {
  global areaX1 areaY1
  global areaX2 areaY2
  global cvsglb
  global cvscfg

  set x [$w canvasx $x]
  set y [$w canvasy $y]
  if {$areaY1 != $y && $areaX1 != $x} {
    $w delete area
    $w addtag area withtag \
      [$w create rect $areaX1 $areaY1 $x $y -outline $cvsglb(glb_highlight)]
    set areaX2 $x
    set areaY2 $y
  }
}

proc DirCanvas:areaFind {w} {
  global areaX1 areaY1
  global areaX2 areaY2
  global DirList

  gen_log:log T "ENTER ($w)"
  gen_log:log D "$areaX1 $areaY1 $areaX2 $areaY2"
  set items {}
  foreach i [$w find enclosed $areaX1 $areaY1 $areaX2 $areaY2] {
    lappend items $i
  }
  foreach i [$w find overlapping $areaX1 $areaY1 $areaX2 $areaY2] {
      lappend items $i
  }
  gen_log:log D "Items in area: $items"
  set parent [winfo parent [winfo parent $w]]
  foreach i $items {
    set itags [$w gettags $i]
    if {[lindex $itags 0] == "x"} {
      set iy [lindex $itags 1]
      gen_log:log D "$w tx$iy"
      gen_log:log D [array names DirList *$iy]
      DirCanvas:addselection $parent $iy $DirList($w:$iy)
    }
  }
  $w delete area
}

# Internal use only.
# Draw the files on the canvas
proc DirCanvas:build {w} {
  global DirList
  global Filelist
  global cvscfg
  global cvsglb
  global incvs

  gen_log:log T "ENTER ($w)"
  set flist $w.filecol.list
  foreach b [winfo children $flist] {
    destroy $b
  }
  $flist delete all
  $w.statcol.list delete all
  $w.datecol.list delete all
  $w.wrevcol.list delete all
  $w.editcol.list delete all
  catch {unset DirList($w:buildpending)}

  set x 3
  set lblx 21
  set y 20
  set imy [expr {[image height paper] + 2}]
  set fy [font metrics $cvscfg(listboxfont) -displayof $flist -linespace]
  set fy [expr {$fy + 2}]
  if {$imy > $fy} {
    set yincr $imy
    gen_log:log D "Y spacing: $y set from icon"
  } else {
    set yincr $fy
    gen_log:log D "Y spacing: $y set from font"
  }

  set maxlbl 0; set longlbl ""
  set maxstat 0; set longstat ""
  set maxdate 0; set longdate ""
  set maxtag 0; set longtag ""
  set maxed 0; set longed ""

  set sortcol [lindex $cvsglb(sort_pref) 0]
  set sortsense [lindex $cvsglb(sort_pref) 1]
  if { (! $incvs )  && ( $sortcol == "editcol" || $sortcol == "wrevcol") } {
    gen_log:log T "setting sort to column \"filecol!\""
    set sortcol "filecol"
    set sortsense "-decreasing"
  }

  gen_log:log D "sortcol=$sortcol  sortsense=$sortsense"

  set AllColumns {}
  foreach k [array names DirList $w:*:name] {
    set key $DirList($k)
    set DirList($w:$k:allcolumns) [list $key $DirList($w:$key:status) \
      $DirList($w:$key:date) $DirList($w:$key:sticky) $DirList($w:$key:editors)]
    lappend AllColumns $DirList($w:$k:allcolumns)
  }

  set itemlist {}
  switch -- $sortcol {
   "filecol" {
    set sortedlist [lsort $sortsense -index 0 $AllColumns]
   }
   "statcol" {
    set sortedlist [lsort $sortsense -index 1 $AllColumns]
   }
   "datecol" {
    set sortedlist [lsort $sortsense -index 2 $AllColumns]
   }
   "wrevcol" {
    set sortedlist [lsort -dictionary $sortsense -index 3 $AllColumns]
   }
   "editcol" {
    set sortedlist [lsort $sortsense -index 4 $AllColumns]
   }
  }

  # Create items.
  foreach item $sortedlist {
    set f [lindex $item 0]
    set flen [string length $f]
    if {$flen > $maxlbl} {
      set maxlbl $flen
      set longlbl $f
    }
    incr y -$yincr
    set lblfg $cvsglb(fg)

    # Up-to-date
    #  The file is identical with the latest revision in the repository for the
    #    branch in use
    # Locally Modified
    #  You have edited the file, and not yet committed your changes.
    # Locally Added
    #  You have added the file with add, and not yet committed your changes.
    # Locally Removed
    #  You have removed the file with remove, and not yet committed your changes
    # Needs Checkout
    #  Someone else has committed a newer revision to the repository. The name
    #    is slightly misleading; you will ordinarily use update rather than
    #    checkout to get that newer revision.
    # Needs Patch
    #  Like Needs Checkout, but the CVS server will send a patch rather than the
    #    entire file. Sending a patch or sending an entire file accomplishes
    #    the same thing.
    # Needs Merge
    #  Someone else has committed a newer revision to the repository, and you
    #    have also made modifications to the file.
    # Unresolved Conflict
    #  This is like Locally Modified, except that a previous update command gave
    #    a conflict. You need to resolve the conflict as described in section
    #    Conflicts example.
    # Unknown
    #  CVS doesn't know anything about this file. For example, you have created
    #     a new file and have not run add.

    # Prepending ./ to the filename prevents tilde expansion
    if {! [file exists ./$f]} {
      # If a file CVS knows about doesn't exist, write its name in light ink
      set lblfg $cvsglb(dfg)
    }

    switch -glob -- $DirList($w:$f:status) {
     "<file>" {
       set DirList($w:$f:icon) paper
       set DirList($w:$f:popup) paper_pop
      }
     "<directory>" {
       set DirList($w:$f:icon) folder
       if {$f == ".."} {
         set DirList($w:$f:popup) updir_pop
       } else {
         set DirList($w:$f:popup) folder_pop
       }
      }
     "<directory:CVS>" {
       set DirList($w:$f:icon) cvsdir
       set DirList($w:$f:popup) cvsdir_pop
      }
     "Up-to-date" {
       set DirList($w:$f:icon) stat_ok
       set DirList($w:$f:popup) stat_ok_pop
       if {[string match "*-kb*" $DirList($w:$f:option)]} {
         set DirList($w:$f:icon) stat_kb
       }
      }
     "Needs Checkout" {
       # Prepending ./ to the filename prevents tilde expansion
       if {[file exists ./$f]} {
         set DirList($w:$f:icon) stat_ood
        } else {
         set DirList($w:$f:icon) stat_ex
        }
        set DirList($w:$f:popup) needsupdate_pop
      }
      "Needs Patch" {
       set DirList($w:$f:icon) stat_ood
       set DirList($w:$f:popup) needsupdate_pop
      }
      "Needs Merge" {
       set DirList($w:$f:icon) stat_merge
       set DirList($w:$f:popup) stat_merge_pop
      }
      "Locally Modified" {
       set DirList($w:$f:icon) stat_mod
       set DirList($w:$f:popup) stat_mod_pop
      }
      "Locally Added" {
       set DirList($w:$f:icon) stat_plus
       set DirList($w:$f:popup) stat_plus_pop
       if {[string match "*-kb*" $DirList($w:$f:option)]} {
         set DirList($w:$f:icon) stat_plus_kb
       }
      }
      "Locally Removed" {
       set DirList($w:$f:icon) stat_minus
       set DirList($w:$f:popup) stat_plus_pop
      }
      "*onflict*" {
       set DirList($w:$f:icon) stat_conf
       set DirList($w:$f:popup) stat_conf_pop
      }
      "?" {
       set DirList($w:$f:icon) stat_ques
       set DirList($w:$f:popup) paper_pop
      }
      default {
       set DirList($w:$f:icon) paper
       set DirList($w:$f:popup) paper_pop
      }
    }

    # Easy way to unselect everything by clicking in a blank area
    bind $flist <1> "DirCanvas:unselectall $w"
    bind $flist <Shift-1> " "

    bind $flist <2> "DirCanvas:areaStart $flist %x %y"
    bind $flist <B2-Motion> "DirCanvas:areaStroke $flist %x %y; \
                             DirCanvas:drag_windows $w %W %y"
    bind $flist <ButtonRelease-2> "DirCanvas:areaFind $flist"
    bind $flist <3> "DirCanvas:unselectall $w; \
                     DirCanvas:areaStart $flist %x %y"
    bind $flist <Shift-3> "DirCanvas:areaStart $flist %x %y"
    bind $flist <B3-Motion> "DirCanvas:areaStroke $flist %x %y; \
                             DirCanvas:drag_windows $w %W %y"
    bind $flist <ButtonRelease-3> "DirCanvas:areaFind $flist"
    # In the bindings, filenames need any single percents replaced with
    # double to avoid interpretation as an event field
    regsub -all {\%} $f {%%} fn
    regsub -all {\$} $fn {\$} fn

    # The "x" tag is used for area selection.
    # Draw the icon
    label $flist.ic$y -image $DirList($w:$f:icon) -padx 0 -pady 0 \
      -borderwidth 1 -height $yincr -bg $cvsglb(canvbg)
    $flist create window $x $y -window $flist.ic$y \
      -anchor w -tags [list x $y]
    bind $flist.ic$y <1> "DirCanvas:setselection $w $y \"$fn\""
    bind $flist.ic$y <Enter> "DirCanvas:flash $flist $y"
    bind $flist.ic$y <Leave> "DirCanvas:unflash $flist $y"
    bind $flist.ic$y <Shift-1> "DirCanvas:addrange $w $y \"$fn\""
    bind $flist.ic$y <Control-1> "DirCanvas:addselection $w $y \"$fn\""
    bind $flist.ic$y <Double-1> {workdir_edit_file [workdir_list_files]}
    bind $flist.ic$y <2> "DirCanvas:popup $flist $y %X %Y \"$fn\""
    bind $flist.ic$y <3> "DirCanvas:popup $flist $y %X %Y \"$fn\""

    # Draw the label
    label $flist.tx$y -text $f -font $cvscfg(listboxfont) -padx 0 -pady 0 \
      -borderwidth 1 -anchor w -bg $cvsglb(canvbg) -fg $lblfg
    $flist create window $lblx $y -window $flist.tx$y \
      -anchor w -tags [list x $y lbl]

    bind $flist.tx$y <1> "DirCanvas:setselection $w $y \"$fn\""
    bind $flist.tx$y <Enter> "DirCanvas:flash $flist $y"
    bind $flist.tx$y <Leave> "DirCanvas:unflash $flist $y"
    bind $flist.tx$y <Shift-1> "DirCanvas:addrange $w $y \"$fn\""
    bind $flist.tx$y <Control-1> "DirCanvas:addselection $w $y \"$fn\""
    bind $flist.tx$y <Double-1> { workdir_edit_file [workdir_list_files] }
    bind $flist.tx$y <2> "DirCanvas:popup $flist $y %X %Y \"$fn\""
    bind $flist.tx$y <3> "DirCanvas:popup $flist $y %X %Y \"$fn\""
    set DirList($w:$f:y) $y
    set DirList($flist:$y) $f

    set status $DirList($w:$f:status)
    set k [$w.statcol.list create text 8 $y -fill $cvsglb(fg) \
       -text $status -font $cvscfg(listboxfont) -anchor w]
    set slen [string length $status]
    if {$slen > $maxstat} {
      set maxstat $slen
      set longstat $status
    }

    set date $DirList($w:$f:date)
    set k [$w.datecol.list create text 4 $y -fill $cvsglb(fg) \
       -text $date -font $cvscfg(listboxfont) -anchor w]
    set dlen [string length $date]
    if {$dlen > $maxdate} {
      set maxdate $dlen
      set longdate $date
    }

    if {[info exists DirList($w:$f:sticky)]} {
      set tag $DirList($w:$f:sticky)
      set k [$w.wrevcol.list create text 4 $y -fill $cvsglb(fg) \
         -text $tag -font $cvscfg(listboxfont) -anchor w]
      set tlen [string length $tag]
      if {$tlen > $maxtag} {
        set maxtag $tlen
        set longtag $tag
      }
    }

    set editors $DirList($w:$f:editors)
    set k [$w.editcol.list create text 4 $y -fill $cvsglb(fg) \
       -text $editors -font $cvscfg(listboxfont) -anchor w]
    set edlen [string length $editors]
    if {$edlen > $maxed} {
      set maxed $edlen
      set longed $editors
    }
  }

  # See which optional columns we need to draw
  if {$incvs && $cvscfg(showstatcol)} {
    if {! [winfo ismapped $w.statcol]} {
      DirCanvas:map_column $w statcol
    }
  } else {
    pack forget $w.statcol
  }

  if {$cvscfg(showdatecol)} {
    if {! [winfo ismapped $w.datecol.list]} {
      DirCanvas:map_column $w datecol
    }
  } else {
    pack forget $w.datecol
  }

  if {$incvs && $cvscfg(econtrol)} {
    if {! [winfo ismapped $w.editcol.list]} {
      DirCanvas:map_column $w editcol
    }
  } else {
    pack forget $w.editcol
  }

  set wid [font measure $cvscfg(listboxfont) -displayof $w.filecol $longlbl]
  set DirList($w:filecolwidth) [expr {$x + $wid + $lblx + 8}]
  gen_log:log D "width of $flist $maxlbl chars ($wid)"
  # Set a minimum width for the labels.  Otherwise ".." can be hard to select.
  set minlabel 6
  foreach labl [$flist find withtag lbl] {
    set itags [$flist gettags $labl]
    set iy [lindex $itags 1]
    if {[string length $DirList($flist:$iy)] < $minlabel} {
      $flist.tx$iy configure -width $minlabel
    }
  }
  # Don't let the column be too wide.
  set maxcolwid 200
  gen_log:log D "filecol width $DirList($w:filecolwidth)"
  if {$wid < $maxcolwid} {
    $flist configure -width $DirList($w:filecolwidth) 
  } else {
    gen_log:log D "Reducing filecol width from $wid to $maxcolwid"
    $flist configure -width $maxcolwid
  }
  $flist configure -xscrollcommand "$w.filecol.xscroll set"

  if {$cvscfg(showstatcol)} {
    set wid [font measure $cvscfg(listboxfont) -displayof $w.statcol $longstat]
    set DirList($w:statcolwidth) [expr {$wid + 6}]
    gen_log:log D "statcol width $DirList($w:statcolwidth)"
    if {$wid < $maxcolwid} {
      $w.statcol.list configure -width $DirList($w:statcolwidth)
    } else {
      gen_log:log D "Reducing statcol width from $wid to $maxcolwid"
      $w.statcol.list configure -width $maxcolwid
    }
    $w.statcol.list configure -xscrollcommand "$w.statcol.xscroll set"
  }

  if {$cvscfg(showdatecol)} {
    set wid [font measure $cvscfg(listboxfont) -displayof $w.datecol $longdate]
    set DirList($w:datecolwidth) [expr {$wid + 6}]
    if {$wid < $maxcolwid} {
      $w.datecol.list configure -width $DirList($w:datecolwidth) 
    } else {
      gen_log:log D "Reducing datecol width from $wid to $maxcolwid"
      $w.datecol.list configure -width $maxcolwid
    }
    $w.datecol.list configure -xscrollcommand "$w.datecol.xscroll set"
  }


  if {$incvs} {
    set wid [font measure $cvscfg(listboxfont) -displayof $w.wrevcol $longtag]
    set DirList($w:revcolwidth) [expr {$wid + 6}]
    gen_log:log D "width of $w.wrevcol $maxtag chars ($wid)"
    # Don't let the column be too wide.
    if {$wid < $maxcolwid} {
      $w.wrevcol.list configure -width $DirList($w:revcolwidth)
    } else {
      gen_log:log D "Reducing wrevcol width from $wid to $maxcolwid"
      $w.wrevcol.list configure -width $maxcolwid
    }
    $w.wrevcol.list configure -xscrollcommand "$w.wrevcol.xscroll set"

    if {$cvscfg(econtrol)} {
      set wid [font measure $cvscfg(listboxfont) -displayof $w.editcol $longed]
      set DirList($w:edcolwidth) [expr {$wid + 6}]
      gen_log:log D "width of $w.editcol $maxed chars ($wid)"
      if {$wid < $maxcolwid} {
        $w.editcol.list configure -width $DirList($w:edcolwidth)
      } else {
        gen_log:log D "Reducing editcol width from $wid to $maxcolwid"
        $w.editcol.list configure -width $maxcolwid
      }
      $w.editcol.list configure -xscrollcommand "$w.editcol.xscroll set"
    }
  }

  update idletasks

  # Scroll to the top of the lists
  set fbbox [$flist bbox all]
  #gen_log:log D "fbbox   \"$fbbox\""
  if {[llength $fbbox] == 4} {
    set botx [lindex $fbbox 0]
    set boty [lindex $fbbox 1]
    set topx [lindex $fbbox 2]
    set topy [lindex $fbbox 3]

    $flist config -scrollregion $fbbox
    $flist yview moveto 0
    
    if {[winfo ismapped $w.statcol.list]} {
      $w.statcol.list config -scrollregion \
        [list $botx $boty [expr {$botx + $DirList($w:statcolwidth)}] $topy]
      $w.statcol.list yview moveto 0
    }
    if {[winfo ismapped $w.datecol.list]} {
      $w.datecol.list config -scrollregion \
        [list $botx $boty [expr {$botx + $DirList($w:datecolwidth)}] $topy]
      $w.datecol.list yview moveto 0
    }
    if {[winfo ismapped $w.wrevcol.list]} {
      $w.wrevcol.list config -scrollregion \
        [list $botx $boty [expr {$botx + $DirList($w:revcolwidth)}] $topy]
      $w.wrevcol.list yview moveto 0
    }
    if {[winfo ismapped $w.editcol.list]} {
      $w.editcol.list config -scrollregion \
        [list $botx $boty [expr {$botx + $DirList($w:edcolwidth)}] $topy]
      $w.editcol.list yview moveto 0
    }
  }
  update idletasks
  if {[llength $fbbox] == 4} {
    set whigh [expr abs([lindex $fbbox 1] - [lindex $fbbox 3])]
    set wview [winfo height $flist]
    set frac [expr ($wview * 1.0) / ($whigh * 1.0)]
    update idletasks
    $w.yscroll set 0 $frac
  }
  gen_log:log D "[array names DirList $w:*:selected]"

  gen_log:log T "LEAVE"
}

# Internal use only
# Call DirCanvas:build the next time we're idle
proc DirCanvas:buildwhenidle {w} {
  global DirList

  if {![info exists DirList($w:buildpending)]} {
    set DirList($w:buildpending) 1
    after idle "DirCanvas:build $w"
  }
}

proc DirCanvas:scroll_windows {w args} {
# Scrolling caused by arrow or page keys

  #gen_log:log T "ENTER ($w $args)"
  set way [lindex $args 1]
  set units [lindex $args 2]
  set yget [$w.yscroll get]
  set first [lindex $yget 0]
  set last [lindex $yget 1]
  # If you dont do this, the scrollbar fills the whole trough when
  # you page past the top or bottom with the arrow keys
  case $units {
    {units pages} {
      if {$way < 0} {
        if {$first == 0} {
          return
        }
      } else {
        if {$last == 1} {
          return
        }
      }
    }
  }
  eval $w.filecol.list yview $args
  if {[winfo ismapped $w.statcol.list]} {
    eval $w.statcol.list yview $args
  }
  eval $w.datecol.list yview $args
  eval $w.wrevcol.list yview $args
  if {[winfo ismapped $w.editcol.list]} {
    eval $w.editcol.list yview $args
  }
}

proc DirCanvas:drag_windows {w W y} {
#Scrolling caused by dragging

  set height [$W cget -height]
  #gen_log:log D "$w %y $height"
  if {$y < 0} {set y 0}
  if {$y > $height} {set y $height}
  set yfrac [expr {double($y) / $height}]

  eval $w.filecol.list yview moveto $yfrac
  eval $w.statcol.list yview moveto $yfrac
  eval $w.datecol.list yview moveto $yfrac
  eval $w.wrevcol.list yview moveto $yfrac
  eval $w.editcol.list yview moveto $yfrac
}

proc DirCanvas:sort_by_col {w col sense} {
  global DirList
  global cvsglb
  global arr

  gen_log:log T "ENTER ($w $col $sense)"
  foreach a [array names arr] {
    catch "$arr($a) configure -image arr_dn"
  }
  set cvsglb(sort_pref) [list $col $sense]

  if {[string match "-inc*" $sense]} {
    gen_log:log D "sort column $col -increasing"
    $arr($col) configure -image arh_up
    if {$col == "statcol"} {$arr(filestatcol) configure -image arh_up}
  } else {
    gen_log:log D "sort column $col -decreasing"
    $arr($col) configure -image arh_dn
    if {$col == "statcol"} {$arr(filestatcol) configure -image arh_dn}
  }
  if {$col != "statcol"} {
    $arr(filestatcol) configure -image arr_dn
  }

  gen_log:log D "  $cvsglb(sort_pref)"

  DirCanvas:build $w
  gen_log:log T "LEAVE"
}

proc DirCanvas:toggle_col {w col} {
  global cvsglb

  gen_log:log T "ENTER ($col)"
  set cur_col [lindex $cvsglb(sort_pref) 0]
  set cur_sense [lindex $cvsglb(sort_pref) 1]

  if {$col == $cur_col} {
    # if it's the currently sorted column, reverse the direction.
    if {[string match "-incr*" $cur_sense]} {
      set sense "-decreasing"
    } else {
      set sense "-increasing"
    }
  } else {
    # Otherwise, default to decreasing (down)
    set sense "-decreasing"
  }

  DirCanvas:sort_by_col $w $col $sense
  gen_log:log D "sort column $col $sense"

  gen_log:log T "LEAVE"
}

proc DirCanvas:makepopup {w} {
#
# Context-sensitive popups for list items
#
  gen_log:log T "ENTER ($w)"

  # For plain files
  menu $w.paper_pop -tearoff 0
  $w.paper_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.paper_pop add command -label "Add" \
    -command { add_dialog [workdir_list_files] }
  $w.paper_pop add command -label "Delete" \
    -command { workdir_delete_file [workdir_list_files] }

  # For plain directories
  menu $w.folder_pop -tearoff 0
  $w.folder_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.folder_pop add command -label "Add Recursively" \
    -command { addir_dialog [workdir_list_files] }
  $w.folder_pop add command -label "Delete" \
    -command { workdir_delete_file [workdir_list_files] }

  # For CVS directories
  menu $w.cvsdir_pop -tearoff 0
  $w.cvsdir_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.cvsdir_pop add command -label "Check Directory" \
     -command { cvs_check [workdir_list_files] }
  $w.cvsdir_pop add command -label "Remove Recursively" \
    -command { subtractdir_dialog [workdir_list_files] }
  $w.cvsdir_pop add command -label "Update" \
    -command { \
        cvs_update {BASE} {Normal} {Remove} {No} { } [workdir_list_files] }
  $w.cvsdir_pop add command -label "Update with Options" \
    -command update_run
  $w.cvsdir_pop add command -label "Release" \
    -command { cvs_release [workdir_list_files] }

  # For CVS files
  menu $w.stat_ok_pop -tearoff 0
  $w.stat_ok_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.stat_ok_pop add command -label "Log Browse" \
    -command { cvs_logcanvas [workdir_list_files] }
  $w.stat_ok_pop add command -label "Annotate" \
    -command { cvs_annotate [workdir_list_files] }
  $w.stat_ok_pop add command -label "Remove" \
    -command { subtract_dialog [workdir_list_files] }

  # For CVS files that are not up-to-date
  menu $w.needsupdate_pop -tearoff 0
  $w.needsupdate_pop add command -label "Update" \
    -command { \
        cvs_update {BASE} {Normal} {Remove} {No} { } [workdir_list_files] }
  $w.needsupdate_pop add command -label "Update with Options" \
    -command update_run

  # For CVS files that need merging
  menu $w.stat_merge_pop -tearoff 0
  $w.stat_merge_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.stat_merge_pop add command -label "Diff" \
    -command { cvs_diff [workdir_list_files] }
  $w.stat_merge_pop add command -label "Annotate" \
    -command { cvs_annotate [workdir_list_files] }
  $w.stat_merge_pop add command -label "Log Browse" \
    -command { cvs_logcanvas [workdir_list_files] }

  # For CVS files that are modified
  menu $w.stat_mod_pop -tearoff 0
  $w.stat_mod_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.stat_mod_pop add command -label "Diff" \
    -command { cvs_diff [workdir_list_files] }
  $w.stat_mod_pop add command -label "Commit" \
    -command { commit_run }

  # For CVS files that have been added or removed but not commited
  menu $w.stat_plus_pop -tearoff 0
  $w.stat_plus_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.stat_plus_pop add command -label "Commit" \
    -command { commit_run }

  # For CVS files with conflicts
  menu $w.stat_conf_pop -tearoff 0
  $w.stat_conf_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }
  $w.stat_conf_pop add command -label "Merge Conflict" \
    -command { cvs_merge_conflict [workdir_list_files] }
  $w.stat_conf_pop add command -label "Annotate" \
    -command { cvs_annotate [workdir_list_files] }
  $w.stat_conf_pop add command -label "Log Browse" \
    -command { cvs_logcanvas [workdir_list_files] }

  menu $w.updir_pop -tearoff 0
  $w.updir_pop add command -label "Open" \
    -command { workdir_edit_file [workdir_list_files] }

  gen_log:log T "LEAVE"
}

proc DirCanvas:popup {w y X Y f} {
  global DirList

  gen_log:log T "ENTER ($w $y $X $Y $f)"
  set parent [winfo parent [winfo parent $w]]
  DirCanvas:setselection $parent $y $f
  tk_popup $parent.$DirList($parent:$f:popup) $X $Y
  gen_log:log T "LEAVE"
}

