# 
# ftoc.tcl
#
# Folder table of contents display.
#
# Copyright (c) 1993 Xerox Corporation.
# Use and copying of this software and preparation of derivative works based
# upon this software are permitted. Any distribution of this software or
# derivative works must comply with all applicable United States export
# control laws. This software is made available AS IS, and Xerox Corporation
# makes no warranty about the software, its performance or its conformity to
# any specification.

proc Ftoc_Init {} {
    global ftoc
    set ftoc(displayValid) 0		;# 1 => pick results, not full scan
    set ftoc(displayDirty) 0		;# 1 => display differs from cache
    set ftoc(mono) [string match mono* [tk colormodel .]]
    # Parameters to the Next button
    Preferences_Add "Scan Listing" \
"These settings affect the behavior of Exmh as you move through the scan listing to view and mark messages.
While the default for Auto Commit is OFF, I suggest you try it out.
Messages are still temporarily marked, but the commit is done when you need it." {
    {exwin(ftextLines)	ftextLines 15	{Scan listing lines}
"Lines in the Scan listing window, which is
also called Folder-Table-Of-Contents (FTOC)."}
    {ftoc(implied) impliedDirection ON "Implied Direction"
"If set, Exmh will remember your current direction,
next or previous, and go that way after you mark a
message for deletion or refiling."}
    {ftoc(nextGuard) nextGuard OFF "Next Guard"
"If set, Exmh will warn you that you are about to
change folders when you hit Next.  This means you
end up hitting Next twice to chain the to next
folder with unseen messages."}
    {ftoc(autoCommit) autoCommit OFF "Auto Commit"
"If set, Exmh will invoke the Commit operation to
commit deletions and refiles when it would otherwise
just complain that such a commit is required."}
    {ftoc(commitDialog) commitDialog ON "Commit Dialog"
"If set, you get a confirmation dialog when exmh wants
you to commit pending changes.  Otherwise you just
get a warning message and have to hit the Commit button."}
    {ftoc(autoPack) autoPack OFF "Auto Pack"
"If set, Exmh will pack the folder every time a commit is performed."}
    {ftoc(autoSort) autoSort OFF "Auto Sort"
"If set, Exmh will sort the folder every time you change into it"}
    {ftoc(autoSortType) autoSortType {CHOICE date subject sender custom} {Sorting criterion}
"Sort by Date:/Subject:/From: or user-defined fields. Uses MH sortm command."}
    {ftoc(autoSortCrit) autoSortCrit {-textfield keywords} {Custom criterion}
"Custom parameters for sortm"}
    {ftoc(showNew) ftocShowNew OFF "Show New Messages"
"If set, Exmh will scroll the FTOC display to show
new message that arrive because of an Inc."}
    {ftoc(linkAdvance) advanceOnLink OFF "Advance after Link"
"If set, Exmh will advance to the next message after a link."}
    {ftoc(skipMarked) skipMarked ON "Next/Prev skip marked msgs"
"If set, Next and Prev will skip over messages that have
been marked for move or delete."}
    {flist(cycleBack)	cycleBack ON	"Cycle back to first"
"If there are no folders with unseen messages, then this
option causes you to change to the first folder given by your
Folder-Order MH profile entry."}
    {ftoc(scanWidth) scanWidth 100 "Default scan width"
"This value is passed as the -width argument to scan and in."}
    {ftoc(scanSize) scanSize 100 "Default amount to scan"
"Only the last N messages are scanned when you first enter a folder.
The number is controlled by this setting."}
    }
}
proc Ftoc_Reset { numMsgs msgid folder } {
    global ftoc exwin
    Exmh_Debug Ftoc_Reset $folder has $numMsgs msgs
    set ftoc(numMsgs) $numMsgs		;# num msgs in the scan listing
    set ftoc(changed) 0			;# Number of moves/deletes marked
    set ftoc(lineset) {}		;# set of selected messages
    set ftoc(pickone) 1			;# lineset is empty
    set ftoc(folder) $folder		;# Currently displayed folder
    set ftoc(direction) next		;# assumed next direction
    set ftoc(softChange) [expr {! $ftoc(nextGuard)}]
    set ftoc(lasthit) {}		;# search anchor
    if {$msgid == {}} {
	set ftoc(curLine) {}		;# current display line number
    } else {
	set ftoc(curLine) {}		;# Set later in Msg_Change ?
    }
}
proc Ftoc_Update { numMsgs folder } {
    # Update size of message list after inc'ing into current folder
    global ftoc
    Exmh_Debug Ftoc_Update $folder has $numMsgs msgs
    set ftoc(numMsgs) $numMsgs
}

proc Ftoc_Bindings { w } {
    # Bindings for the ftoc text widget
    global tk_version
    if {$tk_version >= 4.0} {
	# The TScroll binding to too general.
	# We'll do our own scroll bindings here.
	bindtags $w [list $w]
    }

    # Button-1 starts selection range
    bind $w <Button-1> {
	FtocRangeStart [lindex [split [%W index current] .] 0]
	Exmh_Focus
    }
    bind $w <Shift-Button-1> {
	FtocRangeAdd [lindex [split [%W index current] .] 0]
	Exmh_Focus
    }
    bind $w <B1-Motion> {
	FtocRangeExtendXY %x %y
    }
    bind $w <Shift-B1-Motion> {
	FtocRangeExtendXY %x %y
    }
    bind $w <Any-ButtonRelease-1> {
	FtocRangeEnd [lindex [split [%W index current] .] 0] 0
    }
    bind $w <Shift-ButtonRelease-1> {
	FtocRangeEnd [lindex [split [%W index current] .] 0] 1
    }
    bind $w <Button-3> {
	set lineNumber [lindex [split [%W index current] .] 0]
	Msg_Pick $lineNumber noshow
	Exmh_Focus
    }
    bind $w <Double-Button-1> { }
    bind $w <Triple-Button-1> { }

    bind $w <Button-2> {WidgetTextMark %W %y}
    bind $w <B2-Motion> {WidgetTextDragto %W %y $exwin(scrollSpeed)}

    Drag_Attach $w FtocDragSelect Shift 3
}
proc FtocRangeStart { line } {
    # For normal button-down "start a selection"
    global ftoc
    Ftoc_RangeUnHighlight
    set ftoc(pickstart) $line
    set ftoc(pickend) $line
    set ftoc(pickstate) new
    set ftoc(extend) 0
    Ftoc_RangeHighlight $line $line
}
proc FtocRangeAdd { line } {
    # For shift-select "add to selection"
    global ftoc
    set ftoc(pickstart) $line
    set ftoc(pickend) $line
    set ftoc(pickstate) invert
    set ftoc(extend) 0
    FtocRangeInvert $line $line
}
proc FtocRangeEnd { {line {}} {addcurrent 0} } {
    # For end of button sweep
    global ftoc exwin
    catch {unset ftoc(extend)}
    if ![info exists ftoc(pickend)] {
	# Spurious button-release event
	return
    }
    if {($line == $ftoc(pickstart)) && !$addcurrent} {
	# regular button click optimization
	unset ftoc(pickend)
	Msg_Pick $line show
	return
    }
    if {$line != {}} {
	FtocRangeExtend $line
    }
    FtocPickRange $addcurrent
    catch {unset ftoc(pickend)}
}
proc Ftoc_PickMsgs { ids addtosel } {
    # For adding to the selection by message number
    global ftoc
    Exmh_Status "Marking [llength $ids] hits"
    set lines {}
    for {set L 0} {$L <= $ftoc(numMsgs)} {incr L} {
	set ix [lsearch $ids [Ftoc_MsgNumber $L]]
	if {$ix >= 0} {
	    lappend lines $L
	    set ids [lreplace $ids $ix $ix]
	    if {[string length $ids] == 0} {
		break
	    }
	}
    }
    Ftoc_LinesHighlight $lines
    FtocPickRange $addtosel
}
proc FtocRangeExtendXY { x y } {
    global ftoc exwin widgetText

    if ![info exists ftoc(extend)] {
	return
    }
    set active $ftoc(extend)

    set h [winfo height $exwin(ftext)]
    if {$y > $h} {
	set ftoc(extend) [expr $y-$h]
    } else {
	if {$y < 0} {
	    set ftoc(extend) $y
	} else {
	    set ftoc(extend) 0
	}
    }
    
    if {$ftoc(extend) == 0} {
	FtocRangeExtend [lindex [split [$exwin(ftext) index @$x,$y] .] 0]
    } else {
	if {! $active} {
	    set ftoc(lastmark) [lindex [ split [$exwin(ftext) index @$x,$y] .] 0]
	    after $widgetText(selectDelay) [list FtocSelExtend]
	}
    }
}
proc FtocSelExtend {} {
    global ftoc exwin widgetText
    set w $exwin(ftext)
    if {![info exists ftoc(extend)] ||
	($ftoc(extend) == 0)} {
	return
    }
    catch {
	set delta [expr {$ftoc(extend) / 16}]
	if {$delta == 0} {
	    set delta [expr { ($ftoc(extend) < 0) ? -1 : 1 }]
	}
	set newmark [expr {$ftoc(lastmark) + $delta}]
	FtocRangeExtend $newmark
	set ftoc(lastmark) $newmark
	$w yview -pickplace $newmark.0
	after $widgetText(selectDelay) [list FtocSelExtend]
    }
}
proc FtocRangeExtend { line } {
    global ftoc
    if ![info exists ftoc(pickend)] {
	return
    }
    if {$line <= 0} {
	set line 1
    }
    if {$line > $ftoc(numMsgs)} {
	set line $ftoc(numMsgs)
    }
    if {$line == $ftoc(pickend)} {
	# Invariant, previously defined selection is fine.
	return
    }
    if {$line == 0} {
	# no messages in folder
	return
    }
    if {$ftoc(pickstate) != "invert"} {
	if {$ftoc(pickstart) < $ftoc(pickend)} {
	    # Growing downward
	    if {$line > $ftoc(pickend)} {
		Ftoc_RangeHighlight [expr $ftoc(pickend)+1] $line
	    } else {
		if {$line < $ftoc(pickstart)} {
		    if {$ftoc(pickstart) != $ftoc(pickend)} {
			# Change direction
			FtocRangeClear [expr $ftoc(pickstart)+1] \
					$ftoc(pickend)
		    }
		    Ftoc_RangeHighlight [expr $ftoc(pickstart)-1] $line
		} else {
		    # Shrink selection
		    FtocRangeClear [expr $line+1] $ftoc(pickend)
		}
	    }
	} else {
	    # Growing upward
	    if {$line < $ftoc(pickend)} {
		Ftoc_RangeHighlight [expr $ftoc(pickend)-1] $line
	    } else {
		if {$line > $ftoc(pickstart)} {
		    if {$ftoc(pickstart) != $ftoc(pickend)} {
			# Change direction
			FtocRangeClear [expr $ftoc(pickstart)-1] \
					$ftoc(pickend)
		    }
		    Ftoc_RangeHighlight [expr $ftoc(pickstart)+1] $line
		} else {
		    # Shrink selection
		    FtocRangeClear [expr $line-1] $ftoc(pickend)
		}
	    }
	}
    } else {
	if {$ftoc(pickstart) < $ftoc(pickend)} {
	    # Growing downward
	    if {$line > $ftoc(pickend)} {
		FtocRangeInvert [expr $ftoc(pickend)+1] $line
	    } else {
		if {$line < $ftoc(pickstart)} {
		    if {$ftoc(pickstart) != $ftoc(pickend)} {
			# Change direction
			FtocRangeInvert [expr $ftoc(pickstart)+1] \
					$ftoc(pickend)
		    }
		    FtocRangeInvert [expr $ftoc(pickstart)-1] $line
		} else {
		    # Shrink selection
		    FtocRangeInvert [expr $line+1] $ftoc(pickend)
		}
	    }
	} else {
	    # Growing upward
	    if {$line < $ftoc(pickend)} {
		FtocRangeInvert [expr $ftoc(pickend)-1] $line
	    } else {
		if {$line > $ftoc(pickstart)} {
		    if {$ftoc(pickstart) != $ftoc(pickend)} {
			# Change direction
			FtocRangeInvert [expr $ftoc(pickstart)-1] \
					$ftoc(pickend)
		    }
		    FtocRangeInvert [expr $ftoc(pickstart)+1] $line
		} else {
		    # Shrink selection
		    FtocRangeInvert [expr $line-1] $ftoc(pickend)
		}
	    }
	}
    }
    set ftoc(pickend) $line
}
proc FtocRangeInvert { start end } {
    global exwin
    set win $exwin(ftext)
    if {$start > $end} {
	set tmp $start ; set start $end ; set end $tmp
    }
    for {set line $start} {$line <= $end} {incr line} {
	catch {
	    set newtag range
	    set oldtag {}
	    set nuke 0
	    foreach tag [$win tag names $line.0] {
		case $tag {
		    deleted { set newtag drange ; set oldtag $tag ; break; }
		    moved { set newtag mrange ; set oldtag $tag ; break; }
		    range { set newtag {} ; set oldtag $tag ; break; }
		    drange { set newtag deleted ; set oldtag $tag ;
			    set nuke 1; break}
		    mrange { set newtag moved ; set oldtag $tag ;
			    set nuke 1; break; }
		}
	    }
	    if {$nuke} {
		set ix [lsearch $ftoc(lineset) $line]
		if {$ix >= 0} {
		    set ftoc(lineset) [lreplace $ftoc(lineset) $ix $ix]
		}
	    }
	    if {$oldtag != {}} {
		$win tag remove $oldtag $line.0 $line.end
	    }
	    if {$newtag != {}} {
		$win tag add $newtag $line.0 $line.end
	    }
	}
    }
}
proc Ftoc_RangeHighlight { start end } {
    global exwin
    set win $exwin(ftext)
    if {$start > $end} {
	set tmp $start ; set start $end ; set end $tmp
    }
    for {set line $start} {$line <= $end} {incr line} {
	set newtag range
	foreach tag [$win tag names $line.0] {
	    case $tag {
		{drange deleted} { set newtag drange ;  break; }
		{mrange moved} { set newtag mrange ;  break; }
	    }
	}
	$win tag add $newtag $line.0 $line.end
    }
}
proc Ftoc_LinesHighlight { lines } {
    global exwin
    set win $exwin(ftext)
    if {$lines == {}} {
	return
    }
    WidgetTextYview $exwin(ftext) -pickplace [lindex $lines 0].0
    update idletasks
    foreach line $lines {
	set newtag range
	foreach tag [$win tag names $line.0] {
	    case $tag {
		{drange deleted} { set newtag drange ;  break; }
		{mrange moved} { set newtag mrange ;  break; }
	    }
	}
	$win tag add $newtag $line.0 $line.end
    }
}
proc FtocRangeClear { start end } {
    global exwin
    set win $exwin(ftext)
    if {$start > $end} {
	set tmp $start ; set start $end ; set end $tmp
    }
    for {set line $start} {$line <= $end} {incr line} {
	catch {
	    set newtag {}
	    set oldtag range
	    foreach tag [$win tag names $line.0] {
		case $tag {
		    drange { set newtag deleted ; set oldtag drange; break; }
		    mrange { set newtag moved ; set oldtag mrange; break; }
		    range { break }
		}
	    }
	    $win tag remove $oldtag $line.0 $line.end
	    if {$newtag != {}} {
		$win tag add $newtag $line.0 $line.end
	    }
	}
    }
}
proc Ftoc_RangeUnHighlight { } {
    global exwin
    set win $exwin(ftext)
    foreach tag {range drange mrange} {
	foreach range [FtocMakePairs [$win tag ranges $tag]] {
	    eval $win tag remove $tag $range
	    if {$tag == "drange"} {
		eval $win tag add deleted $range
	    }
	    if {$tag == "mrange"} {
		eval $win tag add moved $range
	    }
	}
    }
}

# For user programming
proc Ftoc_BindDouble { cmd } {
    global exwin
    bind $exwin(ftext) <Double-1> $cmd
}
proc Ftoc_BindRight { cmd } {
    global exwin
    bind $exwin(ftext) <3> $cmd
}

proc Ftoc_FindMsg { msgid {line {}} } {
    global ftoc
    if {$line != {}} {
	switch -glob -- $line {
	    first  {return 1}
	    last   {return $ftoc(numMsgs)}
	    [0-9]* {
		if {$line > $ftoc(numMsgs)} {
		    return $ftoc(numMsgs)
		} else {
		    return $line
		}
	    }
	    default {return {}}
	}
    }
    if {$msgid == {}} {
	return {}
    }
#
# Linear search for pick and thread FTOCs (pseudo-displays)
#
    if !$ftoc(displayValid) {
        for {set L 1} {$L <= $ftoc(numMsgs)} {incr L} {
            if {[Ftoc_MsgNumber $L] == $msgid} {
                return $L
            }
        }
        return {}
    }

    set min 1
    set max $ftoc(numMsgs)	;# Ignore trailing blank line
    while (1) {
	set m1 [Ftoc_MsgNumber $min]
	if {$msgid == $m1} {
	    return $min
	}
	set m2 [Ftoc_MsgNumber $max]
	if {$msgid == $m2} {
	    return $max
	}
	if {$msgid > $m2 || $msgid < $m1} {
	    Exmh_Status "Cannot find $msgid ($m1,$m2)" warn
	    return "" ;# new message not listed
	}
	if {$max == $min} {
	    return ""	;# not found
	}
	set next [expr int(($max+$min)/2)]
	set m3 [Ftoc_MsgNumber $next]
	if {$m3 > $msgid} {
	    set max $next
	} elseif {$min == $next} {
	    Exmh_Status "Cannot find $msgid" warn
	    return "" ;# new message not listed
	} else {
	    set min $next
	}
    }
    # not reached
}
proc Ftoc_MsgNumber { L } {
    global exwin
    if [catch {$exwin(ftext) get $L.0 $L.end} line] {
	return ""
    }
    return [Ftoc_MsgNumberRaw $line]
}
proc Ftoc_MsgNumberRaw { line } {
    if [regexp {( *)([0-9]+)} $line foo foo2 number] {
	return $number
    } else {
	return ""
    }
}
proc FtocPickRange { {addcurrent 0} } {
    # Select a range of messages, or add to the current range
    # Because of toggle/inverted selections, we pretty much
    # have to recompute the select set from range tags
    global exwin ftoc
    set lineset {}
    if {$ftoc(curLine) != {}} {
	if {$addcurrent} {
	    Ftoc_RangeHighlight $ftoc(curLine) $ftoc(curLine)
	}
	Ftoc_ClearCurrent
	Msg_ClearCurrent
	set ftoc(curLine) {}
    }
    foreach range [concat \
	[FtocMakePairs [$exwin(ftext) tag ranges range]] \
	[FtocMakePairs [$exwin(ftext) tag ranges drange]] \
	[FtocMakePairs [$exwin(ftext) tag ranges mrange]]] {
	set mark1 [lindex $range 0]
	set line [lindex [split $mark1 .] 0]
	lappend lineset $line
    }
    if {$lineset == {}} {
	return			;# spurious <ButtonRelease-1> events
    }
    set ftoc(lineset) $lineset
    set ftoc(pickone) 0
    if {[llength $ftoc(lineset)] == 1} {
	# This calls Msg_Change,
	# which calls Ftoc_ClearCurrent, which sets pickone to 1,
	# and calls Ftoc_Change, which sets curline
	Msg_Pick [lindex $ftoc(lineset) 0] show
    } else {
	Buttons_Range	;# Enable actions on ranges
    }
}
proc Ftoc_PickSize {} {
    global ftoc
    set len [llength $ftoc(lineset)]
    if {$len == 0} {
	return [llength $ftoc(curLine)]
    } else {
	return $len
    }
}
proc Ftoc_NewFtoc {} {
    global ftoc
    set ids {}
    foreach l $ftoc(lineset) {
	lappend ids [Ftoc_MsgNumber $l]
    }
    if {[llength $ids] <= 1} {
	Exmh_Status "Select more than one message first" warn
	return
    }
    if {[Ftoc_Changes "new ftoc"] == 0} {
	Exmh_Status $ids
	Scan_ProjectSelection $ids
    }
}

# Ftoc_ClearCurrent and Ftoc_Change are two parts of
# dinking the ftoc display when advancing a message.

proc Ftoc_ClearCurrent {} {
    # Clear display of current message
    global ftoc exwin
    set ftoc(pickone) 1
    set ftoc(lineset) {}

    if {$ftoc(curLine) != {}} {
	$exwin(ftext) tag remove current $ftoc(curLine).0 $ftoc(curLine).end
	$exwin(ftext) tag remove currentBg $ftoc(curLine).0 $ftoc(curLine).end
	Ftoc_RescanLine $ftoc(curLine)
    }
    return $ftoc(curLine)
}
proc Ftoc_Change { msgid line {show show} } {
    global ftoc exwin
    set ftoc(curLine) [Ftoc_FindMsg $msgid $line]
    if {$ftoc(curLine) == {}} {
	set ok 0
    } else {
	if {$show == "show"} {
	    $exwin(ftext) tag remove unseen $ftoc(curLine).0 $ftoc(curLine).end
	}
	Ftoc_RescanLine $ftoc(curLine) +
	$exwin(ftext) tag add current $ftoc(curLine).0 $ftoc(curLine).end
	$exwin(ftext) tag add currentBg $ftoc(curLine).0 $ftoc(curLine).end
	set top [$exwin(ftext) index @0,4]
	if [catch {expr {$top+1}}] {set top 0}	;# trap 100.-1 format, iconic
	if {$ftoc(curLine) == $top ||
	    $ftoc(curLine) == $top+$exwin(ftextLines)-1} {
	    WidgetTextYview $exwin(ftext) [expr $ftoc(curLine)-$exwin(ftextLines)/2].0
	} else {
	    WidgetTextYview $exwin(ftext) -pickplace $ftoc(curLine).0
	}
	set ok 1
    }
    return $ok
}
proc Ftoc_ShowUnseen { folder } {
    global exwin flist
    set unseen [Flist_UnseenMsgs $folder]
    if {[llength $unseen] > 0} {
	set end [$exwin(ftext) index end]
	set line [lindex [split $end .] 0]
	set msgNum 0
	for {} {$line > 0} {incr line -1} {
	    set msgNum [Ftoc_MsgNumber $line]
	    set i [lsearch $unseen $msgNum]
	    if {$i >= 0} {
		$exwin(ftext) tag add unseen $line.0 $line.end
		set unseen [lreplace $unseen $i $i]
		if {[llength $unseen] == 0} {
		    return 1
		}
	    }
	}
	# Repair bogus unseen sequences
	# msgNum is the smallest message number, but it might not be
	# the first message in the folder because of short scans
	# Anything in the unseen sequence above msgNum is probably wrong
	# and can result from races with the background process
	foreach id $unseen {
	    if {$id > $msgNum} {
		Flist_MsgSeen $id
	    }
	}
    } else {
	Flist_SeenAll $folder	;# clear highlighting
	return 0
    }
}
proc Ftoc_MarkSeen { ids } {
    global exwin ftoc

    set ids [lsort -integer -decreasing $ids]
    for {set L $ftoc(numMsgs)} {$L > 0} {incr L -1} {
	set ix [lsearch $ids [Ftoc_MsgNumber $L]]
	if {$ix >= 0} {
	    $exwin(ftext) tag remove unseen $L.0 $L.end
	    set ids [lreplace $ids $ix $ix]
	    if {[llength $ids] == 0} {
		break
	    }
	}
    }
}
proc Ftoc_RescanLine { ix {plus none} } {
    global exmh exwin ftoc
    if [catch {
	set text [$exwin(ftext) get ${ix}.0 ${ix}.end]
	set ok 0
	case $plus {
	    "none" {
		# Replace + (current marker) with blank
		set ok [regsub {( *[0-9]+)(\+)} $text {\1 } newtext]
	    }
	    "+" {
		# Stick a + after the number, if needed
		if ![regexp {( *)([0-9]+)(\+)} $text] {
		    set ok [regsub {( *[0-9]+)( )} $text {\1+} newtext]
		}
	    }
	    "dash" {
		# Stick a - after the number, if needed
		if ![regexp {( *)([0-9]+).-} $text] {
		    set ok [regsub {( *[0-9]+.)(.)} $text {\1-} newtext]
		}
		# Annotations result in writes to the directory.
		# Here we mark the display dirty to force an update
		# of the cache and prevent later rescans.
		set ftoc(displayDirty) 1
	    }
	}
	if {$ok} {
	    set tags [$exwin(ftext) tag names ${ix}.0]
	    $exwin(ftext) configure -state normal
	    $exwin(ftext) delete ${ix}.0 ${ix}.end
	    $exwin(ftext) insert ${ix}.0 $newtext
	    $exwin(ftext) configure -state disabled
	    foreach tag $tags {
		$exwin(ftext) tag add $tag ${ix}.0 ${ix}.end
	    }
	}
    } msg] {
	Exmh_Error "FtocRescanLine $ix : $msg"
    }
}
proc Ftoc_NextImplied { {show show} {implied implied} } {
    global ftoc
    if {$ftoc(implied) && $ftoc(direction) == "prev"} {
	Ftoc_Prev $show
    } else {
	Ftoc_Next $show $implied
    }
}
proc Ftoc_Next { show {implied no} } {
    # Go to the next message in the scan display
    global exmh flist ftoc

    set ftoc(direction) "next"
    if {$ftoc(curLine) == {}} {
	if [Msg_ShowUnseen] {
	    return
	}
    }
    set next [FtocSkipMarked $ftoc(curLine) 1]
    if {($ftoc(curLine) == $next) || \
	($ftoc(curLine) >= $ftoc(numMsgs)) || \
	($ftoc(curLine) <= 0)} {
	# End of folder
	Ftoc_NextFolder $implied
    } else {
	# Simple case - go to the next message.
	Msg_Pick $next $show
    }
}
proc Ftoc_Prev { {show show} } {
    global ftoc

    Exmh_Debug Ftoc_Prev
    if {$ftoc(curLine) == {}} {
	if {$ftoc(numMsgs) > 0} {
	    Msg_Pick $ftoc(numMsgs) $show
	}
	return
    }
    if {$ftoc(curLine) > 1} then {
	set ftoc(direction) "prev"
	Msg_Pick [FtocSkipMarked $ftoc(curLine) -1] $show
    } else {
	Ftoc_Next $show implied
    }
}
proc Ftoc_NextFolder { {implied no} } {
    global ftoc
    # Try to chain to the next folder with unread messages.
    if {$implied != "no"} {
	# Implied - chained with some other operation - be lenient
	if {$ftoc(changed) > 0} {
	    # Dirty folder - do not change.
	    # If on last message, clear display because the
	    # message is moved or deleted
	    if {$ftoc(curLine) != {}} {
		Ftoc_ClearCurrent
		Msg_ClearCurrent
	    }
	    Exmh_Status ""
	    Exmh_Status "Changes pending; End of folder" warn
	    return
	}
    }
    set f [Flist_NextUnseen]
    if {[string length $f] != 0} {
	if {$ftoc(softChange)} {
	    Folder_Change $f Msg_ShowUnseen
	    return
	} else {
	    set ftoc(softChange) 1
	    Ftoc_ClearCurrent
	    Msg_ClearCurrent
	    Exmh_Status ""
	    Exmh_Status "End of folder; <Next> => $f" warn
	    return
	}
    }
    Exmh_Status ""
    Exmh_Status "End of folder" warn
}
proc Ftoc_PrevMarked { {show show} } {
    global ftoc
    set skip $ftoc(skipMarked)
    set ftoc(skipMarked) 0
    Ftoc_Prev $show
    set ftoc(skipMarked) $skip
}
proc Ftoc_Marked { id } {
    global ftoc exwin
    if {$ftoc(skipMarked) == 0} {
	return 0	;# Pretend it isn't marked
    }
    set i [Ftoc_FindMsg $id]
    if {[string length $i] == 0} {
	return 1	;# Can't find it, pretend it's marked
    }
    set marked 0
    foreach tag [$exwin(ftext) tag names $i.0] {
	if [regexp {(deleted|moved|drange|mrange)} $tag] {
	    set marked 1 ; break;
	}
    }
    return $marked
}
proc FtocSkipMarked {start inc} {
    global exwin ftoc

    if {$start == {}} {
	return {}
    }
    for {set i [expr $start+$inc]} {$i > 0 && $i <= $ftoc(numMsgs)} {incr i $inc} {
	if {$ftoc(skipMarked) == 0} {
	    return $i
	}
	set marked 0
	foreach tag [$exwin(ftext) tag names $i.0] {
	    if [regexp {(deleted|moved|drange|mrange)} $tag] {
		set marked 1 ; break;
	    }
	}
	if {! $marked} {
	    return $i
	}
    }
    return $start
}

proc Ftoc_Changes {type {allowAuto 1} } {
    global ftoc

    if {$ftoc(changed) != 0} then {
	Exmh_Debug Ftoc_Changes $type
	if {("$allowAuto" == "1") && $ftoc(autoCommit)} {
	    Folder_CommitType $type
	    return 0
	}
	if {$type != {}} {
	    if {[string compare $type iconified] == 0} {
		set msg "$ftoc(changed) changes pending"
	    } else {
		if {$ftoc(commitDialog) &&
		    [FtocDialog $ftoc(changed) $type]} {
		    Folder_CommitType $type
		    Exmh_Focus
		    return 0
		} else {
		    set msg "$ftoc(changed) changes pending: $type cancelled"
		}
	    }
	    Exmh_Focus
	    Exmh_Status $msg warn
	    Sound_Error
	} else {
	    Exmh_Status "Oops, $ftoc(changed) left over changes" error
	    set ftoc(changed) 0
	    return 1
	}
    } else {
	Msg_CheckPoint		;# Sync unseen and cur message state.
    }
    return $ftoc(changed)
}
proc FtocDialog { changes type } {
    global exwin ftoc
    if [winfo exists $exwin(mtext).commit] {
	destroy $exwin(mtext).commit
    }
    set f [frame $exwin(mtext).commit -class Dialog -bd 4 -relief ridge]
    set blurb [expr {($changes > 1) ? "are $changes changes" : "is one change"}]
    Widget_Message $f msg -text \
"There $blurb pending.
(Press Return to Commit)
(Press <Control-c> to Cancel)" -aspect 1000
    set but [Widget_Frame $f but Dialog {top expand fill} -bd 10]
    set ftoc(okToCommit) 0
    Widget_AddBut $but cancel "Cancel" {set ftoc(okToCommit) 0}
    Widget_AddBut $but ok "Commit and $type" {set ftoc(okToCommit) 1}
    focus $but
    bind $but <Return> "$but.ok flash ; $but.ok invoke"
    bind $but <Control-c> "$but.cancel flash ; $but.cancel invoke"
    Widget_PlaceDialog $exwin(mtext) $exwin(mtext).commit
    Visibility_Wait $but
    catch {grab $but}
    tkwait variable ftoc(okToCommit)
    catch {grab release $but}
    destroy $exwin(mtext).commit
    return $ftoc(okToCommit)
}

proc Ftoc_Iterate { lineVar body } {
    global ftoc
    upvar $lineVar line
    catch {
	if {$ftoc(pickone)} {
	    if {$ftoc(curLine) != {}} {
		set line $ftoc(curLine)
		uplevel 1 $body
	    }
	} else {
	    foreach line $ftoc(lineset) {
		uplevel 1 $body
	    }
	}
    }
}
proc Ftoc_Unmark {} {
    global ftoc

    set hits 0
    Ftoc_Iterate line {
	if [FtocUnmarkInner $line] { incr hits }
    }
    Exmh_Status "Unmarked $hits msgs"
    incr ftoc(changed) -$hits
}
proc FtocUnmarkInner { line {all 0}} {
    global exwin
    set res 0
    if {$all} {
	set pat (deleted|moved|drange|mrange|copied)
    } else {
	set pat (deleted|moved|drange|mrange)
    }
    foreach tag [$exwin(ftext) tag names $line.0] {
	if [regexp $pat $tag] {
	    $exwin(ftext) tag remove $tag $line.0 $line.end
	    if [regexp {(drange|mrange|crange)} $tag] {
		eval $exwin(ftext) tag add range $line.0 $line.end
	    }
	    set res 1
	}
    }
    return $res
}
proc Ftoc_Delete { line msgid } {
    global exwin ftoc
    $exwin(ftext) configure -state normal
    $exwin(ftext) delete $line.0 "$line.end + 1 chars"
    $exwin(ftext) configure -state disabled
    set ftoc(displayDirty) 1
}
proc Ftoc_RemoveMark { line msgid } {
    # Flag the current message(s) for deletion
    global ftoc exwin
    if ![FtocUnmarkInner $line 1] {
	incr ftoc(changed)
    }

    if {$ftoc(pickone)} {
	$exwin(ftext) tag add deleted $line.0 $line.end
    } else {
	$exwin(ftext) tag remove range $line.0 $line.end
	$exwin(ftext) tag add drange $line.0 $line.end
    }
}
proc Ftoc_MoveMark { line msgid } {
    global ftoc exwin exmh
    if ![FtocUnmarkInner $line] {
	incr ftoc(changed)
    }
    # This tag records the target folder
    $exwin(ftext) tag add [list moved $exmh(target)] $line.0 $line.end

    if {$ftoc(pickone)} {
	$exwin(ftext) tag add moved $line.0 $line.end
    } else {
	$exwin(ftext) tag remove range $line.0 $line.end
	$exwin(ftext) tag add mrange $line.0 $line.end
    }
}
proc Ftoc_CopyMark { line msgid } {
    global ftoc exwin exmh
    if ![FtocUnmarkInner $line] {
	incr ftoc(changed)
    }
    # This tag records the target folder
    $exwin(ftext) tag add [list copied $exmh(target)] $line.0 $line.end

    if {$ftoc(pickone)} {
	$exwin(ftext) tag add moved $line.0 $line.end
    } else {
	$exwin(ftext) tag remove range $line.0 $line.end
	$exwin(ftext) tag add mrange $line.0 $line.end
    }
}
proc Ftoc_Commit { rmmCommit moveCommit copyCommit } {
    global ftoc exwin

    # Disable operations on ranges
    Ftoc_RangeUnHighlight
    if {! $ftoc(pickone)} {
	Buttons_Range 0
	set ftoc(lineset) {}
	set ftoc(pickone) 1
    }

    Exmh_Status "Committing $ftoc(changed) changes..."
    $exwin(ftext) configure -state normal
    FtocCommit deleted $rmmCommit
    FtocCommit moved $moveCommit $copyCommit
    $exwin(ftext) configure -state disabled
    set l $ftoc(curLine)
    if {$l == {}} {
	set l $ftoc(numMsgs)
    }
    if {$l > 0} {
	WidgetTextYview $exwin(ftext) $l
    }
    if {! [Ftoc_Changes {} noautocommit]} {
	Exmh_Status "ok"
    }
}
proc FtocCommit {tagname commitProc {copyCommitProc {}} } {
    global ftoc exmh exwin msg

    set delmsgs {}
    set curid [file tail $msg(path)]
    set pairs [FtocMakeReversePairs [$exwin(ftext) tag ranges $tagname]]
    foreach range $pairs {
	set c0 [lindex $range 0]
	set ce [lindex $range 1]
	scan $c0 "%d" L
	set msgid [Ftoc_MsgNumber $L]
	set F {}
	set delline 0	;# Nuke display line
	foreach tag [$exwin(ftext) tag names $c0] {
	    if {([llength $tag] == 2) && ([lindex $tag 0] == "moved")} {
		set F [lindex $tag 1]
		# Build up a list of moved messages
		# Note that the original order of the messages is maintained,
		# (We are going from bottom to top thru the display.)
		# The scan lines are reversed, which is handled by Scan_Move.
		if ![info exists movemsgs($F)] {
		    set movemsgs($F) $msgid
		} else {
		    set movemsgs($F) [concat $msgid " " $movemsgs($F)]
		}
		lappend movescan($F) [$exwin(ftext) get $c0 "$ce + 1 chars"]
		set delline 1
	    }
	    if {([llength $tag] == 2) && ([lindex $tag 0] == "copied")} {
		set F [lindex $tag 1]
		# Build up a list of moved messages
		# Note that the original order of the messages is maintained,
		# (We are going from bottom to top thru the display.)
		# The scan lines are reversed, which is handled by Scan_Move.
		if ![info exists copymsgs($F)] {
		    set copymsgs($F) $msgid
		} else {
		    set copymsgs($F) [concat $msgid " " $copymsgs($F)]
		}
		lappend movescan($F) [$exwin(ftext) get $c0 "$ce + 1 chars"]
	    }
	}
	if {$tagname == "deleted"} {
	    # Batch up deletes
	    lappend delmsgs $msgid
	    set delline 1
	}
	Flist_MsgSeen $msgid	;# in case deleted or moved w/out viewing
	if {$delline} {
	    Msg_UnSeen $msgid	;# avoid MH mark bug
	    $exwin(ftext) delete $c0 "$ce + 1 chars"
	    set ftoc(displayDirty) 1
	    if {$msgid == $curid} {
		Ftoc_ClearCurrent
		Msg_ClearCurrent
	    }
	    if {$L == $ftoc(curLine)} {
		set ftoc(curLine) {}
	    } elseif {$ftoc(curLine) != {}} {
		if {$L < $ftoc(curLine)} {
		    incr ftoc(curLine) -1
		    if {$ftoc(curLine) == 0} {
			set ftoc(curLine) {}
		    }
		}
	    }
	    incr ftoc(numMsgs) -1
	} else {
	    FtocUnmarkInner $L
	}
	incr ftoc(changed) -1
    }
    if {$delmsgs != {}} {
	Exmh_Status "$commitProc $delmsgs"
	if [catch {
	    BgAction "Rmm $exmh(folder)" $commitProc $exmh(folder) $delmsgs
	} err] {
	    Exmh_Status $err error
	}
    }
    # Do copies before links so you can both move and copy a message.
    if {[catch {array names copymsgs} flist] == 0} {
	foreach f $flist {
	    Exmh_Status "Copying to $f, $copymsgs($f)"
	    if [catch {
		BgAction "Refile $f" $copyCommitProc $exmh(folder) $copymsgs($f) $f
	    } err] {
		Exmh_Status $err error
	    }
	}
    }
    if {[catch {array names movemsgs} flist] == 0} {
	foreach f $flist {
	    Exmh_Status "Refiling to $f, $movemsgs($f)"
	    if [catch {
		BgAction "Refile $f" $commitProc $exmh(folder) $movemsgs($f) $f
	    } err] {
		Exmh_Status $err error
	    }
	}
    }
}
proc FtocMakePairs { list } {
    set result {}
    for {set i 0} {$i < [expr [llength $list]-1]} {incr i +2} {
	set first [lindex $list $i]
	set second [lindex $list [expr $i+1]]
	lappend result [list $first $second]
    }
    if {$result == {}} {
	return $list
    } else {
	return $result
    }
}
proc FtocMakeReversePairs { list } {
    set result {}
    for {set i [expr [llength $list]-1]} {$i >= 0} {incr i -2} {
	set second [lindex $list $i]
	set first [lindex $list [expr $i-1]]
	lappend result [list $first $second]
    }
    if {$result == {}} {
	return $list
    } else {
	return $result
    }
}

proc Ftoc_MoveFeedback { msgid line } {
    global exwin ftoc
    set msg [Exmh_OldStatus]
    if {[string compare $line "last"] == 0} {
	set line $ftoc(numMsgs)
    } elseif {[string compare $line first] == 0} {
	set line 1
    }
    foreach tag [$exwin(ftext) tag names $line.0] {
	if [regexp {moved (.+)} $tag match folder] {
	    Exmh_Status "$msgid => +$folder"
	    return
	} elseif [regexp deleted $tag] {
	    Exmh_Status "$msgid Pending Delete"
	    return
	}
    }
    Exmh_Status $msg
}
proc Ftoc_FindNext {} {
    Find_It forw
}
proc Ftoc_FindPrev {} {
    Find_It back
}
proc Ftoc_FindAll {string} {
    global exwin find ftoc
    if {[string length $string] == 0} {
	Exmh_Status "No search string" warn
	return -1
    }
    set msgids {}
    for {set L 1} 1 {incr L} {
	if [$exwin(ftext) compare $L.end >= end] {
	    break
	}
	if [catch {$exwin(ftext) get $L.0 $L.end} text] {
	    break
	}
	if [regexp -nocase -- $string $text] {
	    lappend msgids [Ftoc_MsgNumberRaw $text]
	}
    }
    if {[llength $msgids] == 0} {
	Exmh_Status "No match" warn
	return 0
    } else {
	Ftoc_PickMsgs $msgids 0
	return 1
    }

}
proc Ftoc_FindMatch {L string} {
    global exwin ftoc
    if {$L == $ftoc(lasthit)} {
	return 0
    }
    if [catch {$exwin(ftext) get $L.0 $L.end} text] {
	return -1	;# off the end or beginning
    }
    if [regexp -nocase -- $string $text] {
	set ftoc(lasthit) $L
	Msg_Pick $L show
	return 1
    }
    return 0
}
proc Ftoc_Yview {args} {
    global exwin
    eval {WidgetTextYview $exwin(ftext)} $args
}
proc Ftoc_Advance { advance } {
    global ftoc
    if {[string compare $advance "advance?"] == 0} {
	return $ftoc(linkAdvance)
    } else {
	return $advance
    }
}
proc Ftoc_PageUp {} {
    global exwin
    Widget_TextPageUp $exwin(ftext)
}
proc Ftoc_PageDown {} {
    global exwin
    Widget_TextPageDown $exwin(ftext)
}

proc Ftoc_Sort {} {
    global ftoc
    case $ftoc(autoSortType) {
	{date} { Folder_Sort -datefield date }
	{subject} { Folder_Sort -textfield subject }
	{sender} { Folder_Sort -textfield from }
	{custom} { eval Folder_Sort $ftoc(autoSortCrit) }
    }
}
proc Ftoc_SelectAll {} {
    global ftoc
    FtocRangeStart 1
    FtocRangeEnd $ftoc(numMsgs)
}
proc Ftoc_SelectAllToEnd {} {
    global ftoc
    if {$ftoc(curLine) != {}} {
      FtocRangeStart $ftoc(curLine)
    } else {
      if {$ftoc(direction) == "next"} {
        FtocRangeStart 1
      } else {
        FtocRangeStart $ftoc(numMsgs)
      }
    }
    if {$ftoc(direction) == "next"} {
      FtocRangeEnd $ftoc(numMsgs)
    } else {
      FtocRangeEnd 1
    }
}
proc Ftoc_CatchUp {} {
    global ftoc
    Ftoc_SelectAll
    Msg_Remove
    Folder_Commit
    Msg_PageOrNext
}
proc Ftoc_CatchUpToEnd {} {
    global ftoc
    Ftoc_SelectAllToEnd
    Msg_Remove
    Folder_Commit
    Msg_PageOrNext
}
#
# Interface to Drag & Drop
#
set ftocDrag(types) {foldermsg}
set ftocDrag(formats) {string filename}
set ftocDrag(format,foldermsg) string
set ftocDrag(format,filename) string
set ftocDrag(type,string) foldermsg

# Drag Selected
proc FtocDragSelectOld {w x y wx wy} {
	global exmh ftoc

        set folder $ftoc(folder)
        if !$ftoc(displayValid) {
            set folder $exmh(folder)
        }
	set line [lindex [split [$w index current] .] 0]
	set msg [Ftoc_MsgNumber $line]
	if {$msg == {} || $msg == 0} return

	# Hand off to Drag code
	global ftocDrag mhProfile
	set ftocDrag(source) $w
	set ftocDrag(data,foldermsg) "+$folder $msg"
	set ftocDrag(data,filename) $mhProfile(path)/$folder/$msg
	Drag_Source ftocDrag $x $y
}
proc FtocDragSelect {w x y wx wy} {
    global exmh ftoc ftocDrag mhProfile

    set folder $ftoc(folder)
    if !$ftoc(displayValid) {
        set folder $exmh(folder)
    }
    set msgs {}
    if $ftoc(pickone) {
	set line [lindex [split [$w index current] .] 0]
	set msgs [Ftoc_MsgNumber $line]
	if {$msgs == {} || $msgs == 0} return
	set ftocDrag(data,filename) $mhProfile(path)/$folder/$msgs
    } else {
	foreach line $ftoc(lineset) {
	    set msgid [Ftoc_MsgNumber $line]
	    if {$msgid != {}} {
		lappend msgs $msgid
	    }
	}
    	catch {unset ftocDrag(data,filename)}
    }

    # Hand off to Drag code
    set ftocDrag(source) $w
    set ftocDrag(data,foldermsg) "+$folder $msgs"
    Drag_Source ftocDrag $x $y
  }

