#
# TCL Library for TkCVS
#

#
# $Id: logcanvas.tcl,v 1.30 2002/01/21 06:57:49 dorothyr Exp $
#
# Contains procedures used for the log canvas for tkCVS.
# this version attempts to implement a bounding-box scenario
# for each revision, and modifies logcanvas_rectangle to look for
# bounding-box collisions.
# Leif E.
#

set const(boxx) 80
set const(xfactor) 14
set const(boxy) 30
set const(spacex) 60
set const(spacey) 16
set const(textheight) 12
set cvscanv ""

proc new_logcanvas {localfile filelog} {
  #
  # Creates a new log canvas.  filelog must be the output of a cvs
  # log or rlog command.  If localfile is not "no file" then it is
  # the file name in the local directory that this applies to.
  #
  global cvscfg
  global cvs
  global revdate
  global revwho
  global revcomment
  global revbranches
  global revlist
  global cvscanv
  global tags
  global bndbox
  global const

  gen_log:log T "ENTER ($localfile <filelog suppressed>)"
  unset cvscanv
  # Height and width to draw boxes
  set cvscanv(boxx) $const(boxx)
  set cvscanv(boxy) $const(boxy)
  # Gaps between boxes
  set cvscanv(gapx) [expr {$cvscanv(boxx) + $const(spacex)}]
  set cvscanv(gapy) [expr {$cvscanv(boxy) + $const(spacey)}]
  # Indent at top left of canvas
  set cvscanv(indx) 5
  set cvscanv(indy) 5
  # Static type variables used while drawing on the canvas.
  set cvscanv(xhigh) 0
  set cvscanv(yhigh) 0
  set cvscanv(xlow)  0
  set cvscanv(ylow)  0

  if {[info exists revlist]} { unset revlist }
  if {[info exists revdate]} { unset revdate }
  if {[info exists revwho]} { unset revwho }
  if {[info exists revbranches]} { unset revbranches }
  if {[info exists revcomment]} { unset revcomment }
  if {[info exists tags]} { unset tags }
  if {[info exists bndbox]} { unset bndbox }

  static {canvasnum 0}

  # Make the canvas

  incr canvasnum
  set logcanvas ".logcanvas$canvasnum"
  toplevel $logcanvas

  frame $logcanvas.up -relief groove -border 2
  label $logcanvas.up.lfname -text "RCS File" \
     -width 12 -anchor w
  entry $logcanvas.up.rfname
  pack $logcanvas.up -side top -fill x
  pack $logcanvas.up.lfname -side left
  pack $logcanvas.up.rfname -side left -fill x -expand 1

  set textfont [$logcanvas.up.rfname cget -font]

  foreach fm {A B} {
 
    frame $logcanvas.up$fm -relief groove -border 2
    frame $logcanvas.up$fm.rev
    label $logcanvas.up$fm.rev.lvers -text "Revision $fm" \
       -width 12 -anchor w
    entry $logcanvas.up$fm.rev.rvers
    pack $logcanvas.up$fm -side top -fill x
    pack $logcanvas.up$fm.rev -side top -fill x -expand 1
    pack $logcanvas.up$fm.rev.lvers \
      -side left
    pack $logcanvas.up$fm.rev.rvers \
      -side left -fill x -expand 1
  
    frame $logcanvas.up$fm.bydate
    label $logcanvas.up$fm.bydate.ldate -text "Committed" \
       -width 12 -anchor w
    label $logcanvas.up$fm.bydate.rdate -text "--" \
       -anchor w
    label $logcanvas.up$fm.bydate.lwho -text " by " \
       -anchor w
    label $logcanvas.up$fm.bydate.rwho -text "--" \
       -anchor w -font $textfont
    pack $logcanvas.up$fm.bydate -side top -fill x -expand 1
    pack $logcanvas.up$fm.bydate.ldate \
         $logcanvas.up$fm.bydate.rdate \
         $logcanvas.up$fm.bydate.lwho \
         $logcanvas.up$fm.bydate.rwho \
      -side left

    frame $logcanvas.up$fm.log
    label $logcanvas.up$fm.log.lcomment -text "Log" \
       -width 12 -anchor w
    frame $logcanvas.up$fm.log.rlogfm -bd 3 -bg $cvscfg(colour$fm)
    text  $logcanvas.up$fm.log.rlogfm.rcomment -height 5 -width 75 \
       -yscrollcommand "$logcanvas.up$fm.log.rlogfm.yscroll set"
    scrollbar $logcanvas.up$fm.log.rlogfm.yscroll \
       -command "$logcanvas.up$fm.log.rlogfm.rcomment yview"
    pack $logcanvas.up$fm.log -side top -fill x -expand 1
    pack $logcanvas.up$fm.log.lcomment -side left
    pack $logcanvas.up$fm.log.rlogfm -side left -fill x -expand 1
    pack $logcanvas.up$fm.log.rlogfm.rcomment \
       -side left -fill x -expand 1
    pack $logcanvas.up$fm.log.rlogfm.yscroll \
       -side left -fill y
  }

  # Pack the bottom before the middle so it doesnt disappear if
  # the window is resized smaller
  frame $logcanvas.down -relief groove -border 2
  pack $logcanvas.down -side bottom -fill x

  # This is a hidden entry that stores the local file name.
  entry $logcanvas.tlocalfile
  $logcanvas.tlocalfile delete 0 end
  $logcanvas.tlocalfile insert end "$localfile"

  # The canvas for the big picture
  canvas $logcanvas.canvas -relief sunken -border 2 \
    -height 300 \
    -yscrollcommand "$logcanvas.yscroll set" \
    -xscrollcommand "$logcanvas.xscroll set"
  scrollbar $logcanvas.xscroll -relief sunken -orient horizontal \
    -command "$logcanvas.canvas xview"
  scrollbar $logcanvas.yscroll -relief sunken \
    -command "$logcanvas.canvas yview"

  #
  # Create buttons
  #
  button $logcanvas.help -text "Help" \
    -padx 0 -pady 0 \
    -command log_browser
  button $logcanvas.view -image Fileview \
    -command "logcanvas_view $logcanvas"
  button $logcanvas.diff -image Diff \
    -command "logcanvas_diff $logcanvas"
  button $logcanvas.join -image Mergebranch \
      -command "logcanvas_join \"$localfile\" $logcanvas"
  button $logcanvas.delta -image Mergediff \
      -command "logcanvas_delta \"$localfile\" $logcanvas"
  button $logcanvas.viewtags -image Tags -command "nop"
  button $logcanvas.quit -text "Close" \
    -padx 0 -pady 0 \
    -command "destroy $logcanvas"

  pack $logcanvas.help \
       $logcanvas.view \
       $logcanvas.diff \
       $logcanvas.join \
       $logcanvas.delta \
       $logcanvas.viewtags \
    -in $logcanvas.down -side left \
    -ipadx 1 -ipady 1 -fill both -expand 1
  pack $logcanvas.quit \
    -in $logcanvas.down -side right \
    -ipadx 1 -ipady 1 -fill both -expand 1

  if {$localfile == "no file"} {
    $logcanvas.view configure -state disabled
    $logcanvas.join configure -state disabled
    $logcanvas.delta configure -state disabled
  }

  set_tooltips $logcanvas.view \
     {"View a version of the file"}
  set_tooltips $logcanvas.diff \
     {"Compare two versions of the file"}
  set_tooltips $logcanvas.join \
     {"Merge branch to head"}
  set_tooltips $logcanvas.delta \
     {"Merge changes to head"}
  set_tooltips $logcanvas.viewtags \
     {"List all the file\'s tags"}

  #
  # Put the canvas on to the display.
  #
  pack $logcanvas.xscroll -side bottom -fill x -padx 1 -pady 1
  pack $logcanvas.yscroll -side right -fill y -padx 1 -pady 1
  pack $logcanvas.canvas -fill both -expand 1

  $logcanvas.canvas delete all

  #
  # Window manager stuff.
  #
  wm minsize $logcanvas 1 1

  # Collect the history from the RCS log
  parse_cvslog $logcanvas $filelog
  # After we've parsed the log, we know the name of the file
  set fname [file tail [$logcanvas.up.rfname get]]
  set fname [string trimright $fname ",v"]
  wm title $logcanvas "CVS Log: $fname"

  # Sort it into order - this makes drawing the tree much easier
  set revlist [lsort -command sortrevs [array names revdate]]
 #puts $revlist

  # Now draw the revision tree
  set n 0
  foreach rev $revlist {
    set revprev [lindex $revlist [expr {$n-1}]]
    logcanvas_draw_box $logcanvas $rev $revprev
    incr n
  }
  # Find stray tags that aren't attached to revisions
  stray_tags $logcanvas

  scrollbindings Canvas
  focus $logcanvas.canvas
  $logcanvas.canvas yview moveto 0
  #bind_show $logcanvas.canvas

  gen_log:log T "LEAVE"
  return $logcanvas
}

proc stray_tags { w } {
  global revdate
  global revlist
  global tags
  global cvscanv
  global const

  set sortags [lsort -command sortrevs [array names tags]]
  foreach t $sortags {
    set foundowner 0
    if {! [info exists revdate($t)]} {
      # If its revision doesn't exist, it may be a sticky tag
      #puts "$t    $tags($t) has no revision"
      set pospart [split $t "."]
      set poslen [llength $pospart]
      set nextolast [expr {[llength $pospart] - 2}]
      set num_nextolast [lindex $pospart $nextolast]
      if {$num_nextolast != 0} {
        # Forget it, it's just an orphaned tag
        #puts " not interested in $t because $num_nextolast is not 0"
        continue
      }
      set revb [expr {[lindex $pospart [expr {[llength $pospart] - 1}]]}]
      set pospart [lreplace $pospart $nextolast $nextolast $revb]
      set pospart [lreplace $pospart end end 1]
      set pospart [join $pospart "."]
      #puts " mangled brachrev $pospart"
      if {! [info exists revdate($pospart)]} {
        # A branch for this sticky doesn't seem to exist
        set pospart [split $t "."]
        set trunk [join [lrange $pospart 0 [expr {$nextolast - 1}]] "."]
        set stub "$trunk.$revb"
        #puts " => $tags($t) looks like a branch bud coming off $trunk"
        if {[info exists cvscanv(posx$trunk)] && \
             [info exists cvscanv(posx$trunk)]} {
          set xbegin [expr {$cvscanv(posx$trunk) + $cvscanv(${trunk}boxwidth)}]
          incr xbegin 2
          set ybegin [expr {$cvscanv(posy$trunk) + $cvscanv(boxy)}]
          set xend [expr {$xbegin + 25}]
          set yend [expr {$ybegin + 0}]
          if {[info exists lasttrunk]} {
            if {$trunk == $lasttrunk} {
              if {[info exists score]} {
                incr score
              } else {
                set score 1
              }
              incr yend [expr {$const(textheight) * $score}]
            } else {
              set score 0
            }
          }
          $w.canvas create line \
            $xbegin $ybegin $xend $yend \
            -fill red3
          $w.canvas create oval \
            $xend [expr {$yend - 3}] \
            [expr {$xend + 6}] [expr {$yend + 3}] \
            -outline red3
          $w.canvas create text \
            [expr {$xend + 10}] $yend \
            -text "$tags($t)" \
            -font {Helvetica -11 bold} \
            -anchor w -fill red3
          set lasttrunk $trunk
        }
      }
    }
  }
}

proc sortrevs {a b} {
  #
  # Proc for lsort -command, to sort revision numbers
  # Return -1 if a<b, 0 if a=b, and 1 if a>b
  # Leif E. I have modified the sort to operate thus
  # If two revisions are the same length, then their ascii collating sequence
  # is fine. If the length(a) < length(b), return -1, length(a) > length(b)
  # return 1.

  set alist [split $a "."]
  set blist [split $b "."]
  set alength [llength $alist]
  set blength [llength $blist]

  # if two revisions are the same length, return their ascii sort order
  if {$alength == $blength} {
    for {set i 0} {$i <= $alength} {incr i} {
      set A [lindex $alist $i]
      set B [lindex $blist $i]
      if {$A < $B} { return -1}
      if {$A > $B} { return 1}
    }
  }

  # Draw the parents before the children
  if {$alength < $blength} {
    return -1
  } elseif {$alength > $blength} {
    return 1
  } else {
    return 0
  }
}

proc ldelete { list value } {
  set ix [lsearch -exact $list $value]
  while {$ix >= 0} {
    set list [lreplace $list $ix $ix]
    set ix [lsearch -exact $list $value]
  }
  return $list
}

proc logcanvas_draw_box {logcanvas rev revprev} {
  #
  # Draws a box containing a revision of a file.
  #
  global cvscanv
  global const
  global tags
  global bndbox
  global revwho
  global revbranches
  global revlist

  gen_log:log T "ENTER ($logcanvas $rev $revprev)"

  # The semantic of max and min are the bottom (relative to the screen) left
  # most point of all text, version boxes, labels, tags etc (in other words the
  # lowest left most point of all graphical symbols associated with a revision)
  # is (xmin, ymin).
  # The upper right is (xmax, ymax). Thus, because in our (tkcvs) universe
  # things drawn higher up in the display are increasingly y-negative,
  # if (ymin < y), then ymin is reset to y, to make ymin reflect the new lower
  # (on the display) element.
  # If (ymax > y), ymax is set to y to reflect the new higher element.
  # xmin and xmax follow the usual semantics ( if (xmin < x) then xmin really
  # is smaller (more left) than x, so no change is required.
  # This single axis logic reversal will eventually freak you out.
  # Leif E.

  set parts [split $rev "."]
  set depth [llength $parts]
  set branchn [expr {$depth - 2}]
  set prevparts [split $revprev "."]
  set prevdepth [llength $prevparts]

  # Default - if theres no previous revision, put it at the beginning index
  set parent ""
  # lift it off the bottom
  set y [expr {-1 * $cvscanv(indy)}]
  set x $cvscanv(indx)
  # update bndbox coord's
  update_bndbox $rev $x $y origin
  set leaf [lindex $parts [expr {$depth - 1}]]
  set branch [join [lrange $parts 0 $branchn] "."]
  set prevbranch [join [lrange $prevparts 0 [expr {$prevdepth - 2}]] "."]
  set branchroot [join [lrange $parts 0 [expr {$depth - 3}]] "."]
  set branchnum [lrange $parts $branchn $branchn]
  set prevroot [join [lrange $prevparts 0 [expr {$prevdepth - 3}]] "."]
  gen_log:log D "$rev\tleaf $leaf\tbranch $branch\troot $branchroot"

  # Else, find the parent and place this one after it
  if {[info exists cvscanv(posy$revprev)]} {
    if {$depth <= $prevdepth} {
      if {[string compare $branch $prevbranch] != 0} {
        # We're on a different branch now, so the parent isn't revprev.
        for {set int [expr {$leaf - 1}]} {$int >= 0} {incr int -1} {
          foreach r $revlist {
            #puts "comparing $r with $branch.$int"
            if {[string compare $r "$branch.$int"] == 0} {
              #puts "Found parent $r on same branch"
              set parent $r
              set x $cvscanv(posx$parent)
              set y [expr {$cvscanv(posy$parent) - $cvscanv(gapy)}]
              update_bndbox $rev $x $y origin
              break
            }
          }
          if {$parent != ""} {
            break
          }
        }
        if {$parent == ""} {
          # Check our list of branches and see if this matches one of them.
          foreach br [array names revbranches] {
            foreach b $revbranches($br) {
              if {[string compare $b $branch] == 0} {
                set parent $br
                #puts "  $b matched $branch, so parent is $br"
                set x [expr {$cvscanv(posx$parent) + $cvscanv(gapx)}]
                set off [make_space_for_tags $parent]
                set y [expr {$y - (12 * $off)}]

                # For visual preference reasons, we increase the slope for
                # branches off second generation branches (eg 1.9.2.2 is a
                # second generation branch, 1.9 is a first generation,
                # 1.9.2.2.2.1 is third generation.
                # Deeper branching scheme need another method.
                set ylist [split $parent .]
                set ytail [lindex $ylist end]
                set ytail [expr {$ytail % 5}]
                if {$ytail != 0} {
                  set y [expr {$cvscanv(posy$parent) - [expr {$ytail * $cvscanv(gapy)}]}]
                } else {
                  # avoids the descending arrow syndrome, where a branch slopes
                  # "downwards" on the canvas display. Leif E.
                  set y [expr {$cvscanv(posy$parent) - $cvscanv(gapy)}]
                }
                update_bndbox $rev $x $y origin
              }
            }
          }
          # We're on an already-existing branch although it's not the
          # same as the immediately preceding revision.  Look for
          # a parent at the same branching level.
          if {$parent == ""} {
            set shortlist ""
            foreach r $revlist {
              set l [llength [split $r "."]]
              if {$l == $depth} {
                lappend shortlist $r
              }
            }
            #puts "trying to find a parent for $rev from $shortlist"
            foreach item $shortlist {
              if {[sortrevs $item $rev] < 0} {
                set parent $item
                set x $cvscanv(posx$parent)
                set y [expr {$cvscanv(posy$parent) - $cvscanv(gapy)}]
                update_bndbox $rev $x $y origin
                continue
              }
            }
          }
        }
        if {$parent == ""} {
          #puts "Didn't find a parent for $rev"
        }
      } else {
        # branch is the same as previous, so parent is $revprev
        set parent $revprev
        set x $cvscanv(posx$parent)
        set y [expr {$cvscanv(posy$parent) - $cvscanv(gapy)}]
        # If the parent has more than two tags, it needs more vertical space
        # this has been moved from after the check_xspace, as we weren't
        # letting that proc know the real position we wanted to draw in
        # Leif E.
        set off [make_space_for_tags $parent]
        set y [expr {$y - (12 * $off)}]
        update_bndbox $rev $x $y origin

        #puts "  parent $parent is the previous rev"
      }
    } else {
      # $depth > $prevdepth, meaning we have a new branch
      # Find parent by simply truncating rev number
      set parent [join [lrange $parts 0 [expr {$depth - 3}]] "."]
      #puts "  new branch, found parent $parent by truncation"
      # If it's on the main trunk, it doesn't need any special processing
      if {$depth == 2} {
        set x $cvscanv(posx$parent)
        set y [expr {$cvscanv(posy$parent) - $cvscanv(gapy)}]
        update_bndbox $rev $x $y origin
      } else {
        # Else, maybe theres a branch already occupying this position
        # so we have to offset it
        set x [expr {$cvscanv(posx$parent) + $cvscanv(gapx)}]
        set y [expr {$cvscanv(posy$parent) - $cvscanv(gapy)}]
        # If the parent has more than two tags, it needs more vertical space
        # this has been moved from after the check_xspace, as we weren't
        # letting that proc know the real position we wanted to draw in
        # Leif E.
        set off [make_space_for_tags $parent]
        set y [expr {$y - (12 * $off)}]
        update_bndbox $rev $x $y origin
      }
    }
  }
  #if {$parent != ""} {
   #puts " parent $parent"
  #}

  # draw the box and remember its position
  logcanvas_rectangle $logcanvas $rev $x $y $parent
  set cvscanv(posx$rev) $x
  set cvscanv(posy$rev) $y
  # Draw incrementally, just to give the user something to watch.
  # This has been known to cause Windows programs to hog the processor
  # by redrawing when running tkcvs in EXceed.
  # It can be commented out with no harm done.
  update idletasks
  $logcanvas.canvas yview moveto 0
  gen_log:log T "LEAVE"
}

proc make_space_for_tags {parent} {
  # If the parent has more than two tags, it needs more vertical space
  # We count how many tags the parent has. If this is greater than the tagdepth
  # the user wishes to use, we reset it to tagdepth. If we have limited the
  # number of tags to display, we need to check to see if there are any with
  # colour set (to other than black), and incr the number of tags to display for  # each coloured one.
  # Finally, as we always have space to display 2 tags, we only need to make
  # space for the final number - 2

  global tags
  global cvscfg
  set extratags 0
  if {[info exists tags($parent)]} {
    set ntags [llength $tags($parent)]
    if {[info exists cvscfg(tagdepth)] && ($cvscfg(tagdepth) != 0)} {
      if {$cvscfg(tagdepth) < $ntags} {
        set ntags $cvscfg(tagdepth)
	foreach tag $tags($parent) {
	  if {[info exists cvscfg(tagcolour,$tag)]} {
	    incr ntags
	  }
	}
      }
    }
    if {$ntags > 2} {
      set extratags [expr {$ntags - 2}]
    }
  }
  return $extratags
}

proc logcanvas_rectangle {logcanvas rev x y revprev} {
  #
  # Breaks out some of the code from the logcanvas_draw_box procedure.
  # Change the order sightly. Work out the width of the text to go in the box
  # first, then draw a box high and wide enough. Update bndbox.
  #
  global cvscanv
  global cvscfg
  global const
  global tags
  global bndbox
  global revwho
  global revdate
  global revcomment
  global revlist
  upvar x xpos

  gen_log:log T "ENTER ($rev $x $y $revprev)"

  set parts [split $rev "."]
  set prevparts [split $revprev "."]

  # To make the drawing of the version boxes more regular, we split the version
  # number using the period as the tokeniser, and obtain the length of the
  # resulting list.  We then multiply by a fixed factor.  This way, all x.x
  # revsions get a rectangle 2 standard factors wide, x.x.x.x revisions get a
  # box 4 standard factors wide. The factor is stored in const(xfactor).  If the
  # whorev_len is longer than the revision number length * it factor, the box is
  # made the who len wide. This may cause some slight visual coarseness. Leif E.

  set versnum_len [llength [split $rev "."]]

  set revwho_len [font measure {Helvetica 12} \
    -displayof $logcanvas.canvas $revwho($rev)]

  # set width of text box to widest string
  if {$revwho_len < [expr {$versnum_len * $const(xfactor)}]} {
    set box_width [expr {$versnum_len * $const(xfactor)}]
  } else {
    set box_width $revwho_len
  }

  # save width of this revsions box
  set cvscanv(${rev}boxwidth) $box_width

  # Put the version number and user in the box.
  $logcanvas.canvas create text \
    [expr {$x + 4}] [expr {$y + 2}] \
    -text $rev \
    -anchor nw \
    -tags [list b$rev v$rev]

  $logcanvas.canvas create text \
    [expr {$x + 4}] [expr {$y + 14}] \
    -text $revwho($rev) \
    -anchor nw \
    -font {Helvetica -12} \
    -tags [list b$rev v$rev]

  # draw the box
  set boxid [$logcanvas.canvas create rectangle \
    $x $y \
    [expr {$x + $cvscanv(${rev}boxwidth)}] [expr {$y + $cvscanv(boxy)}] \
    -width 3 \
    -fill gray90 \
    -tags [list b$rev v$rev]]
  # Drop the fill color below the text so the text isn't hidden
  $logcanvas.canvas lower $boxid

  # draw the tags. Limited to the number set in cvscfg(tagdepth).
  if {[info exists tags($rev)]} {
    set n 0
    set tagcount 0
    set taglimit [llength $tags($rev)]
    set dotreq "false"
    if {[info exists cvscfg(tagdepth)] && ($cvscfg(tagdepth) < $taglimit)} {
      if {$cvscfg(tagdepth) != 0} {
        set taglimit $cvscfg(tagdepth)
	set dotreq "true"
      }
    }
    set blackcount 0
    foreach tag $tags($rev) {
      if [info exists cvscfg(tagcolour,$tag)] {
        set tagcolour $cvscfg(tagcolour,$tag)
      } else {
        # only increment the counter if the tag is black. This means that if a
        # user has gone to the trouble of configuring a colour for a CVS tag,
        # we will draw it and not count it in the limit of tags to display.
	# Included at Dorothy R's request.  # Leif E.
	incr blackcount
        set tagcolour black
      }
      if {$tagcolour != "black" || $blackcount <= $taglimit} {
        $logcanvas.canvas create text \
          [expr {$x + $cvscanv(${rev}boxwidth) + 2}] \
          [expr {$y + $cvscanv(boxy) - $n}] \
          -text $tag \
          -anchor sw -fill $tagcolour \
          -tags v$rev

        set tagwidth [font measure {Helvetica 12} \
          -displayof $logcanvas.canvas $tag]
        incr n $const(textheight)

      }
    }
    if {$dotreq == "true"} {
      set morex [expr {$x + $cvscanv(${rev}boxwidth) + 2}]
      set morey [expr {$y + $cvscanv(boxy) - $n}]
      $logcanvas.canvas create text \
        $morex $morey \
        -text "more..." \
        -anchor sw -fill black \
        -font {Helvetica -12 bold} \
        -tags [list t$rev v$rev]
      # if they click on the word "more...", display all the tags for that rev
      $logcanvas.canvas bind t$rev <Button-1> "log_tagpop $logcanvas.canvas $rev %X %Y"
      set tagwidth [font measure {Helvetica 12 bold} \
          -displayof $logcanvas.canvas "more..."]
    }
  }
  # Mangle the rev number to become the branch number
  set pospart [split $rev "."]
  # do this only for the first revision on a branch
  if {[lindex $pospart end] == 1} {
    gen_log:log D "***** Beginning a branch at r$rev *****"
    set poslen [llength $pospart]
    set revb [lindex $pospart [expr {[llength $pospart] - 2}]]
    set pospart [lreplace $pospart end end $revb]
    set pospart [lreplace $pospart [expr {$poslen - 2}] [expr {$poslen - 2}]  0]
    # revision list is now the branch number, restore to a string
    set pospart [join $pospart "."]
    # arbitrary constant chosen to push sticky branch label under the box
    # there is almost certainly a better way (an array var somewhere ??)
    set n 15
    foreach tag [array names tags] {
      if {[string match $pospart $tag]} {
        set parts  [split $tag "."]
        # Rip last number off revision string
        set stub [join [lreplace [split $rev "."] end end] "."]
        # For each sticky tag for this branch, write it on the canvas
        foreach t $tags($tag) {
          set tagtext "($stub) $t"
          $logcanvas.canvas create text \
            $x \
            [expr {$y + $n + $cvscanv(boxy)}] \
            -text "$tagtext" \
            -anchor sw -fill blue \
            -font {Helvetica -12 bold} \
            -tags v$rev
	  set tagwidth [font measure {Helvetica 12 bold} \
            -displayof $logcanvas.canvas $tagtext]
          incr n $const(textheight)
        }
      }
    }
  }

  # now calculate the bndbox using the canvas bbox function
  set bbox [$logcanvas.canvas bbox v$rev]

  update_bndbox $rev [lindex $bbox 0] [lindex $bbox 1] upleft
  update_bndbox $rev [lindex $bbox 2] [lindex $bbox 1] upright
  update_bndbox $rev [lindex $bbox 0] [lindex $bbox 3] loleft
  update_bndbox $rev [lindex $bbox 2] [lindex $bbox 3] loright

  # draw the bounding box, if you want to see the collision regions.
  # set/append Marcel's $cvscfg(log_classes) to contain a [dD] .
  #if {$cvscfg(logging) && [regexp -nocase {d} $cvscfg(log_classes)]} {
    #$logcanvas.canvas create rectangle \
    #$bndbox(${rev}xmin) $bndbox(${rev}ymax) \
    #$bndbox(${rev}xmax) $bndbox(${rev}ymin) \
    #-width 1 -outline red \
    #-tags v$rev
  #}


  # check if we overlap or underlie anybody.
  # we will loop through overlaps and overhead collisions until neither moves
  set overmove  1
  set undermove 1
  while {$overmove || $undermove} {

    if {![regexp -nocase {\.1$} $rev]} {
      # only attempt comparisons for parent, as children follow without question
      break
    }
    set overmove 0
    set bbox [$logcanvas.canvas bbox v$rev]
    gen_log:log D "Moved box for v$rev to \{$bbox\}"
    set overlist [eval {$logcanvas.canvas find overlapping } $bbox]
    # if we overlap anyone, analyse the overlaps. If overlist is empty, skip
    set we_moved 0
    foreach overindex $overlist {
      # foreach one we overlie, move if req'd
      # get the tags for the item we overlay
      set taglist [ $logcanvas.canvas gettags $overindex]
      # remove ourselves from the list of tags, so that any remaining tags
      # belong to other revisions.
      set taglist [ldelete $taglist v$rev]
      # if we have tags, they are for a revision other than us, and we must
      # move 5 pixels to the right of the conflict.
      foreach tagindex $taglist {
        # not all tags start with a v now, so check it. Leif E.
        if {[string index $tagindex 0] == "v"} {
          set tagindex [string trimleft $tagindex v]
          set bbox [$logcanvas.canvas bbox v$tagindex]
          # work out how far to go to the right
          set xoffset [expr {$bndbox(${tagindex}xmax) - $x}]
          incr xoffset 5; # just to avoid problems
          incr x $xoffset
          # reset return value
 	  set xpos $x
          # record our new position
          update_bndbox $rev $x $y translate $xoffset
          # move there
          $logcanvas.canvas move v$rev $xoffset 0
          # cause we moved, set flag and exit loop
          set overmove 1
          break
        }
      }
      # if we moved for one of the overlays, break out and look for
      # underlying conflictions
      if {$overmove} {
        break
      }
    }
    # we may underlie someone above us.  check if anybody above us would
    # conflict with our children, resulting in unevenness
    set undermove 0
    foreach revitem $revlist {
      if {$rev == $revitem} {
        break;
      }
      set move_req 0
      if {$bndbox(${rev}ymin) < $bndbox(${revitem}ymin)} {
        continue;
      }
      if {$bndbox(${revitem}xmin) >= $bndbox(${rev}xmin) && \
          $bndbox(${revitem}xmin) < $bndbox(${rev}xmax)} {
        set move_req 1
      }
      if {$bndbox(${revitem}xmax) >= $bndbox(${rev}xmin) && \
          $bndbox(${revitem}xmax) < $bndbox(${rev}xmax)} {
        set move_req 1
      }
      if {$bndbox(${revitem}xmin) <= $bndbox(${rev}xmin) && \
          $bndbox(${revitem}xmax) > $bndbox(${rev}xmax)} {
        set move_req 1
      }
      if {$move_req}	{
        set  xoffset [expr {$bndbox(${revitem}xmax) - $x}]
        incr xoffset 5
        # perform the move
        $logcanvas.canvas move v$rev $xoffset 0
        incr x $xoffset
        #reset return value
        set xpos $x
        update_bndbox $rev $x $y translate $xoffset
        set undermove 1
      }
    }
  }

  # draw connecting line
  if {[info exists cvscanv(posx$revprev)]} {
    if {[llength $parts] > [llength $prevparts]} {
      set xbegin [expr {$cvscanv(posx$revprev) + $cvscanv(${revprev}boxwidth)}]
      # end line a standard distance from the bottom left edge
      # of the prev revision box
      set xend $xpos
      set ybegin [expr {$cvscanv(posy$revprev) + ($cvscanv(boxy)/2)}]
    } else {
      # create line a standard distance from the top left edge
      # of the revision box
      set xbegin [expr {$x + $const(xfactor)}]
      set ybegin $cvscanv(posy$revprev)
      # end line a standard distance from the bottom left edge
      # of the prev revision box
      set xend [expr {$x + $const(xfactor)}]
    }
   set yend [expr {$y + $cvscanv(boxy)}]
   # add arrowhead
   $logcanvas.canvas create line $xbegin $ybegin $xend $yend -arrow last
  }

  # stretch canvas if reqd
  if {$cvscanv(xlow) >  $bndbox(${rev}xmin)} {
    set cvscanv(xlow) $bndbox(${rev}xmin)
    incr cvscanv(xlow) -5
  }
  if {$cvscanv(xhigh) <  $bndbox(${rev}xmax)} {
    set cvscanv(xhigh) $bndbox(${rev}xmax)
    incr cvscanv(xhigh) 5
  }

  # ylow is the top edge of the canvas. It is the most negative (least) value.
  if {$cvscanv(ylow) >  $bndbox(${rev}ymax)} {
    set cvscanv(ylow) $bndbox(${rev}ymax)
    incr cvscanv(ylow) -5
  }
  # yhigh is the bottom edge of the canvas. It is the least negative (most)
  # value.
  if {$cvscanv(yhigh) <  $bndbox(${rev}ymin)} {
    set cvscanv(yhigh) $bndbox(${rev}ymin)
    incr cvscanv(yhigh) 5
  }

  $logcanvas.canvas configure \
    -scrollregion "$cvscanv(xlow) $cvscanv(ylow) \
    $cvscanv(xhigh) $cvscanv(yhigh)"

  # Bind button-presses to the rectangles.
  $logcanvas.canvas bind b$rev <ButtonPress-1> "hilight_rectangle $logcanvas $rev A"
  # Tcl/TK for Windows doesn't do Button 3, so we duplicate it on Button 2
  $logcanvas.canvas bind b$rev <ButtonPress-2> "hilight_rectangle $logcanvas $rev A"
  $logcanvas.canvas bind b$rev <ButtonPress-3> "hilight_rectangle $logcanvas $rev B"

  gen_log:log T "LEAVE"
}

proc logcanvas_view {logcanvas} {
  #
  # Views the selected version.
  #
  gen_log:log T "ENTER ($logcanvas)"
  set ver1 [$logcanvas.upA.rev.rvers get]
  set localfile [$logcanvas.tlocalfile get]
  cvs_view_r $ver1 $localfile
  gen_log:log T "LEAVE"
}

proc logcanvas_diff {logcanvas} {
  #
  # Diffs two versions.
  #
  gen_log:log T "ENTER ($logcanvas)"
  set ver1 [$logcanvas.upA.rev.rvers get]
  set ver2 [$logcanvas.upB.rev.rvers get]
  set localfile [join [$logcanvas.tlocalfile get]]
  if {$localfile != "no file"} {
    cvs_diff_r $ver1 $ver2 $localfile
  } else {
    set fname [$logcanvas.up.rfname get]
    rcs_filediff $fname $ver1 $ver2
  }
  gen_log:log T "LEAVE"
}

proc logcanvas_join {localfile logcanvas} {
  #
  # Joins a branch version to the head version.
  #
  gen_log:log T "ENTER ($localfile $logcanvas)"
  set ver1 [$logcanvas.upA.rev.rvers get]
  set versions [split $ver1 "."]
  set depth [llength $versions]
  if {$depth < 4} {
    cvsfail "Please select a branch version for this function!"
    return 1
  }

  cvs_join $localfile $ver1
  gen_log:log T "LEAVE"
}

proc logcanvas_delta {localfile logcanvas} {
  #
  # Merges changes in the delta between two versions to the head
  # version.
  #
  gen_log:log T "ENTER ($localfile $logcanvas)"
  set ver1 [$logcanvas.upA.rev.rvers get]
  set ver2 [$logcanvas.upB.rev.rvers get]

  cvs_delta $localfile $ver1 $ver2
  gen_log:log T "LEAVE"
}

proc parse_cvslog {logcanvas filelog} {
  #
  # Splits the rcs file up and parses it using a simple state machine.
  #
  global revdate
  global revwho
  global revcomment
  global tags
  global revbranches

  gen_log:log T "ENTER ($logcanvas <filelog suppressed>)"
  set loglist [split $filelog "\n"]
  set logstate "rcsfile"
  foreach logline $loglist {
    switch -exact -- $logstate {
      "rcsfile" {
        # Look for the first text line which should give the file name.
        set fileline [split $logline]
        if {[lindex $fileline 0] == "RCS"} {
          $logcanvas.up.rfname delete 0 end
          $logcanvas.up.rfname insert end [join [lrange $fileline 2 end]]
          set logstate "tags"
          set taglist ""
          continue
        }
      }
      "tags" {
        # Any line with a tab leader is a tag
        if { [string index $logline 0] == "\t" } {
          set taglist "$taglist$logline\n"
          set tagitems [split $logline ":"]
          set tagstring [string trim [lindex $tagitems 0]]
          set tagrevision [string trim [lindex $tagitems 1]]
          lappend tags($tagrevision) $tagstring
        } else {
          if {$logline == "description:"} {
            # No more tags after this point
            $logcanvas.viewtags configure \
              -command "view_output Tags \"$taglist\""
            set logstate "searching"
            continue
          }
          if {$logline == "----------------------------"} {
            # Oops, missed something.
            $logcanvas.viewtags configure \
              -command "view_output Tags \"$taglist\""
            set logstate "revision"
            continue
          }
        }
      }
      "searching" {
        # Look for the line that starts a revision message.
        if {$logline == "----------------------------"} {
          set logstate "revision"
          continue
        }
      }
      "revision" {
        # Look for a revision number line
        set revline [split $logline]
        set revnum [lindex $revline 1]
        set logstate "date"
      }
      "date" {
        # Look for a date line.  This also has the name of the author.
        set dateline [split $logline]
        set revdate($revnum) [lindex $dateline 1]

        set who  [lindex $dateline 5]
        set revwho($revnum) [string range $who 0 [expr {[string length $who] - 2}]]
        set revcomment($revnum) ""
        set revbranches($revnum) ""
        set logstate "logmessage"
      }
      "logmessage" {
        # See if there are branches off this revision
        if {[string match "branches:*" $logline]} {
          set br [split $logline]
          set branches [lrange $logline 1 [llength $logline]]
          foreach br $branches {
            lappend revbranches($revnum) [string trimright $br ";"]
          }
          continue
        }
        # Read the log message which follows the date line.
        if {$logline == "----------------------------"} {
          set logstate "revision"
          continue
        } elseif {[regexp {^={77}$} $logline]} {
          set logstate "terminated"
          continue
        }
        # Process a revision log line
        # This protects $'s from being evaluated
        regsub -all "\"" $logline "'" newline
        # This prevents an error if there are brackets in the comment.
        # The inserted backslash shows, though, which is not good.
        regsub -all "\{" $newline "\\{" newline
        regsub -all "\}" $newline "\\}" newline
        set revcomment($revnum) "$revcomment($revnum)$newline\n"
      }
      "terminated" {
        # ignore any further lines
        continue
      }
    }
  }
}

proc update_bndbox {rev x y vertex {offset 0} } {
  # update_bndbox checks the current limits of rev's bounding-box, and if the
  # values of the corner dictated by vertex indicate they require adjustment,
  # we do so.  origin sets the origin of our bounding box for this revision
  # Leif E.

  global bndbox

  #gen_log:log T "ENTER (r$rev $x $y $vertex $offset)"

  switch -exact -- $vertex {
    origin  { ; #reset origin of bounding box
              set bndbox(${rev}xmin) $x
              set bndbox(${rev}xmax) $x
              set bndbox(${rev}ymin) $y
              set bndbox(${rev}ymax) $y
            }
    loleft { ; # we adjust xmin if x < xmin, and ymin if y > ymin
              if { $x < $bndbox(${rev}xmin)} {set bndbox(${rev}xmin) $x}
              if { $y > $bndbox(${rev}ymin)} {set bndbox(${rev}ymin) $y}
            }
    upleft  { ; # we adjust xmin if x < xmin, and ymax if y < ymax
              if { $x < $bndbox(${rev}xmin)} {set bndbox(${rev}xmin) $x}
              if { $y < $bndbox(${rev}ymax)} {set bndbox(${rev}ymax) $y}
            }
    upright { ; # we adjust xmax if x > xmax, and ymax if y < ymax
              if { $x > $bndbox(${rev}xmax)} {set bndbox(${rev}xmax) $x}
              if { $y < $bndbox(${rev}ymax)} {set bndbox(${rev}ymax) $y}
            }
    loright { ; # we adjust xmax if x > xmax, and ymin if y > ymin
              if { $x > $bndbox(${rev}xmax)} {set bndbox(${rev}xmax) $x}
              if { $y > $bndbox(${rev}ymin)} {set bndbox(${rev}ymin) $y}
            }
    translate { ; #move the xmin and max settings by offset
                set bndbox(${rev}xmin) [expr {$bndbox(${rev}xmin) + $offset}]
                set bndbox(${rev}xmax) [expr {$bndbox(${rev}xmax) + $offset}]
            }
    default { ; # egad's!!
              error "bogus vertex value: $vertex"
            }
  } ; # switch
  #gen_log:log T "LEAVE"
}

proc hilight_rectangle {c rev AorB} {
  global cvscfg
  global revdate
  global revwho
  global revcomment

  gen_log:log T "ENTER ($c $rev $AorB)"

  catch {$c.canvas itemconfigure $AorB -outline black}
  $c.canvas dtag $AorB
  $c.up$AorB.rev.rvers delete 0 end
  $c.up$AorB.rev.rvers insert end $rev
  $c.up$AorB.bydate.rwho configure -text $revwho($rev)
  $c.up$AorB.bydate.rdate configure -text $revdate($rev)
  $c.up$AorB.log.rlogfm.rcomment delete 1.0 end
  $c.up$AorB.log.rlogfm.rcomment insert end $revcomment($rev)
  foreach item [$c.canvas find withtag b$rev] {
    if {[string compare [$c.canvas type $item] "rectangle"] == 0} {
      $c.canvas itemconfigure $item -outline $cvscfg(colour$AorB)
      $c.canvas addtag $AorB withtag b$rev
    }
  }
}

proc filter_tags {rev}  {
  # Find the tags for one revision
  global tags

  set filtered ""

  foreach tag $tags($rev) {
    append filtered "$tag\n"
  }
  return $filtered
}

proc log_tagpop {logcanvas rev x y} {
#
# Pop up a transient window with a listbox of the tags for a specific revision
#
  global cvscfg

  set urev [join [split $rev "."] ""]
  set mname ".$urev"

  if {[winfo exists $mname]} {
    # Don't let them hit the button twice
    return
  }
  set taglist [filter_tags $rev]

  toplevel $mname
  #wm overrideredirect $mname 1
  wm transient $mname $logcanvas
  wm title $mname r$rev

  listbox $mname.lbx -font $cvscfg(listboxfont)

  set ntags [llength $taglist]
  set entryheight [font metrics $cvscfg(listboxfont) -displayof $mname.lbx -linespace]
  incr entryheight 2
  set listheight [expr {$ntags * $entryheight}]
  # Ballpark guess about what might be the tallest window we might want
  set maxheight 500
  $mname.lbx configure -selectborderwidth 0
  $mname.lbx configure -exportselection 1
  $mname.lbx configure -height $listheight

  set listwidth 0
  set taglist [filter_tags $rev]
  foreach tag $taglist {
    set tlen [font measure $cvscfg(listboxfont) -displayof $mname.lbx "$tag"]
    if {$tlen > $listwidth} {set listwidth $tlen}
    $mname.lbx insert end $tag
  }
  set listwidth [expr {$listwidth + 8}]
  if {$listheight < $maxheight} {
    # Make the window tall enough for all the entries
    wm geometry $mname ${listwidth}x${listheight}
    set newy [expr {$y - ($listheight / 2)}]
    wm geometry $mname +${x}+${newy}
  } else {
    # Oh-oh, we need to scroll
    scrollbar $mname.scroll -command "$mname.lbx yview"
    $mname.lbx configure -yscroll "$mname.scroll set"
    pack $mname.scroll -side right -fill y
    set newy [expr {$y - ($maxheight / 2)}]
    # Make room for scrollbar
    incr listwidth 19
    wm geometry $mname +${x}+${newy}
    wm geometry $mname ${listwidth}x${maxheight}
  }
  #bind $mname <Button-1> "destroy $mname"
  pack $mname.lbx -expand y -fill both
}

