###########################################################################
#                                                                         #
# Procedures to define the GUI for the tged widget.                       #
#                                                                         #
# This file is part of the tged package.                                  #
#                                                                         #
###########################################################################

# Copyright (c) 2000-2003, JYL Software Inc.
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# NOTE about the format of IDs of nodes in the tged widget:
#
# IDs representing top level or detached tgraph nodes have the form n.<id>
# IDs representing multiple links to tgraph nodes have the form l.<id>.<int>
# IDs representing vertices in tgraph nodes have the form v.<id>
#
# This is unfortunately needed because the bwidget tree widget does not
# accept IDs with spaces in them (like lists have). Therefore the code is
# littered with "split $id ." expressions.

# Create a read-only tged widget:

proc ::tged::view {{win ""} {storage ""} args} {
    coreWidget $win $storage 1 $args
}

# Create a new editable tged widget:

proc ::tged::tged {{win ""} {storage ""} args} {
    coreWidget $win $storage 0 $args
}

# Create the widget; this is where all the work is actually done:

proc ::tged::coreWidget {win storage readonly argl} {
    variable data

    # If the window name is "", invent a new top-level window for
    # the widget:

    if {[string equal "" $win]} {
	for {set i $data(gd,toplevelCounter)} \
		{[string equal "" ""]} \
		{incr i} {
	    if {[winfo exists ".tged$i"]} {
		continue
	    }
	    break
	}
	set usewin [toplevel ".tged$i"]
	if {[string equal "" $storage]} {
	    wm title $usewin "TGed #$i"
	} else {
	    wm title $usewin "TGed [$storage name]"
	}
	set data(gd,toplevelCounter) [expr $i+1]
	set data(c,$usewin,tgedSerialID) $i

	wm minsize $usewin 400 200
	wm maxsize $usewin 400 200
    } else {
	set usewin $win
    }

    # Set up the widget window inside the given window.
    #
    # Left pane displays the tree of nodes.
    # Right pane displays the attributes in the selected node.

    set pw [PanedWindow $usewin.pw \
		-side top \
		-weights available \
		-activator button]
    pack $pw -fill both -expand yes

    set lp [$pw add -weight 1]
    set rp [$pw add -weight 2]

    set treetf [TitleFrame $lp.tf -text "Tree" -font ::tged::Frames]
    set sw [ScrolledWindow [$treetf getframe].sw -relief sunken -borderwidth 2]
    set tree [Tree $sw.tree]
    $sw setwidget $tree
    pack $sw -fill both -expand yes
    pack $treetf -fill both -expand yes

    set tf [TitleFrame $rp.tf -text "Attributes" -font ::tged::Frames]
    set sw [ScrolledWindow [$tf getframe].sw -relief sunken -borderwidth 2]
    set text \
	[text $sw.text -font Values \
		       -wrap none \
		       -tabs {2c left 4c left} \
		       -cursor arrow]
    $sw setwidget $text
    pack $sw -fill both -expand yes
    pack $tf -fill both -expand yes

    # Configure the tree widget to react to selections and
    # opening/closing of nodes:

    $tree configure \
	-opencmd "::tged::openNode $text $tree" \
	-closecmd "::tged::closeNode $text $tree" \
	-selectcommand "::tged::selectNode $text"

    # Make the widget either editable or read-only based on the
    # value of the readonly argument.
    
    if {$readonly == 0} {
	addTreeEditBindings $tree
	addValueEditBindings $text
    } else {
	addValueNoEditBindings $text
    }

    # Configure the container window with the given arguments:

    if {![string equal $argl ""]} {
	eval $usewin configure $argl
    }

    # If the user supplied a storage, set the storage into this
    # editor.

    if {$storage != ""} {
	setStorage $usewin $storage
    }

    # Make associations between the text, tree, tree titleframe and hull
    # widgets:

    associateWidgets $tree $text $treetf $usewin

    # Return the hull window name.

    return $usewin
}

# Helper procedure to associate a storage with an existing editor:

proc ::tged::setStorage {hull storage} {
    variable data

    # Compute the names of several widgets from the name of the hull:

    set pw $hull.pw
    set lp [$pw getframe 0]
    set rp [$pw getframe 1]

    set treetf $lp.tf
    set tree [$treetf getframe].sw.tree

    set texttf $rp.tf
    set text [$texttf getframe].sw.text

    # Remove old callbacks if any:

    removeCallbacks $tree

    # Remove all previous state associated with this editor, if any:

    array unset data i,$tree,*
    array unset data i,$text,*

    # Initialize the callbacks for this storage:

    initCallbacks $tree $text $storage

    # Initialize the widget to display the root node of
    # the given storage, if any:

    initDisplay $text $tree $storage

    # Make associations between the tree, text, tree title frame and hull:

    associateWidgets $tree $text $treetf $hull

    # Set the title of the storage editor to reflect the name of the
    # storage and whether the storage is dirty:

    set title "Tree"
    if {$storage != ""} {
	set title [$storage name]
	if {![$storage isstable]} {
	    append title " (*)"
	}
    }
    $treetf configure -text $title
}

# Make associations between the text, tree, tree titleframe and hull
# widgets:

proc ::tged::associateWidgets {tree text treetf hull} {
    variable data

    # Make a link from the tree to the text and vice-versa, and a link
    # from the tree to its title frame. Also associate the hull with
    # this tree widget.

    set data(i,$tree,text) $text
    set data(i,$text,tree) $tree
    set data(i,$tree,titleframe) $treetf
    set data(i,$tree,hull) $hull
}

# Initialize a tree:

proc ::tged::initDisplay {text tree storage} {
    variable data

    # Empty out the tree and text widgets:

    emptyTree $tree
    emptyText $text

    # No storage specified? Punt:

    if {[string equal "" $storage]} {
	return
    }

    # Record that this tree displays the passed storage:

    set data(i,$tree,storage) $storage

    # Grab the root node:

    set root [$storage root]

    # Initialize the widget to display the root node:

    initValues $text

    # Set root node in the tree:

    initTree $tree $root
}

# Empty the tree display:

proc ::tged::emptyTree {tree} {
    $tree selection clear
    $tree delete [$tree nodes root]
}

# Empty the text widget:

proc ::tged::emptyText {text} {
    $text delete 1.0 end

    # Hide the insertion cursor:

    $text configure -insertwidth 0

}

# Re-initialize the tree display:

proc ::tged::initTree {tree root} {
    variable data

    # Clean any remaining contents of the tree and display a single node,
    # the root:

    set rid [$root id]
    $tree insert end root n.$rid \
	-text root \
	-font ::tged::Keys \
	-drawcross [chooseCrossDrawingForNode $root] \
	-image [Bitmap::get folder]

    # Clean up node data remaining, if any, about a previously displayed
    # tree, and record that we're displaying the root:

    array unset data i,$tree,node,*
    set data(i,$tree,node,$rid) n.$rid
}

# Create the values tags:

proc ::tged::setupValuesTags {text} {
    $text tag configure NAME	-font ::tged::ValName
    $text tag configure TYPE	-font ::tged::ValType
    $text tag configure VALUE	-font ::tged::ValValue
    $text tag configure TNAME	-font ::tged::TitName
    $text tag configure TTYPE	-font ::tged::TitType
    $text tag configure TVALUE	-font ::tged::TitValue
    $text tag configure PARENT	-font ::tged::ValName -background red
    $text tag configure ROOT	-font ::tged::ValName -background green
}

# Re-initialize the values display:

proc ::tged::initValues {text} {
    $text insert insert "Name\t" TNAME "Type\t" TTYPE "Value" TVALUE "\n\n"
}

# Open a node:

proc ::tged::openNode {text tree id} {
    variable data

    # A regular node, ensure it is filled out:

    fillOutNode $text $tree $id

    # Show the appropriate folder type:

    if {[llength [$tree nodes $id]]} {
	$tree itemconfigure $id -image [Bitmap::get openfold]
    } else {
	$tree itemconfigure $id -image [Bitmap::get folder]
    }

    set data(i,$tree,$id,expanded) 1
}

# Close a node:

proc ::tged::closeNode {text tree id} {
    variable data

    # Indicate this node is closed.

    $tree itemconfigure $id -image [Bitmap::get folder]

    catch {unset data(i,$tree,$id,expanded)}
}

# Update the display to expand the contents of the given vertex:

proc ::tged::fillOutNode {text tree id} {
    variable data

    # If it was already filled out don't do anything.

    if {[info exists data(i,$tree,$id,filledOut)]} {
	return
    }

    # Extract a node from the display ID, if it's not an ID for a node,
    # punt out.

    set n [getNodeFromDisplayID $tree $id]
    if {$n == ""} {
	return
    }

    # Remember that it was expanded:

    set data(i,$tree,$id,filledOut) 1

    # Expand the tree:

    $n foreach vertex vv -type node {
	set sid v.[$vv id]
	set nid [[$vv get] id]

	# If the node is already displayed, it means that we're encountering
	# another parent of the same node. In that case, make an item
	# which, when selected, will jump to the child node.

	if {[info exists data(i,$tree,node,$nid)]} {
	    set sid l.[$vv id].[genint]
	    $tree insert end $id $sid \
		-text "[$vv name](*)" \
		-font ::tged::Keys \
		-drawcross never \
		-image [Bitmap::get folder]

	    continue	    
	}

	# First time for this node, just insert it.

	$tree insert end $id $sid \
	    -text [$vv name] \
	    -font ::tged::Keys \
	    -drawcross [chooseCrossDrawingForNode [$vv get]] \
	    -image [Bitmap::get folder]

	# Record that this node is already being displayed in the
	# label $sid

	set data(i,$tree,node,$nid) $sid
    }

    # Since it was expanded, we're changing the cross drawing to auto
    # because now we can rely on the tree widget's notion of whether
    # this node has children:

    $tree itemconfigure $id -drawcross auto

    # Ensure it is visible:

    $tree see $id
}

# Show the contents of a node:

proc ::tged::selectNode {text tree id} {
    variable data

    # If the id is empty then punt out

    if {$id == ""} {
	return
    }

    # First of all ensure that the selected node is visible:

    $tree see $id

    # If this is a link, jump to the linked-to node:

    set sid [split $id .]
    if {[string equal [lindex $sid 0] l]} {
	set vid v.[lindex $sid 1]

	# This causes another call to ::tged::selectNode:

	$tree selection set $vid

	return
    }

    # Extract a node from the display ID, if it's not an ID for a node,
    # punt out.

    set n [getNodeFromDisplayID $tree $id]
    if {$n == ""} {
	return
    }

    # If the current selection is the same as that already displayed
    # then punt.

    if {([info exists data(i,$tree,selection)]) && \
	    ([string equal $id $data(i,$tree,selection)])} {
	return
    }

    # Store the current selection and the ID of the selected node:

    set data(i,$tree,selection) $id
    set data(i,$tree,selectednode) [$n id]

    # Refresh the text pane

    refreshTextPane $text $n
}

proc ::tged::refreshTextPane {text n} {
    variable data

    # Empty the text pane, and insert the text title line.

    $text delete 1.0 end
    $text insert insert \
	"Name\t" TNAME "Type\t" TTYPE "Value" TVALUE "\n\n"

    # Clear any previous line->vertex id mapping:

    array unset data i,$text,linetovertex,*
    array unset data i,$text,vertextoline,*

    # If the node given is "" then punt.

    if {$n == ""} {
	return
    }

    # Iterate over all scalars and update the text display:

    set line 3
    $n foreach vertex vv {
	if {[$vv type] == "node"} {
	    continue
	}
	$text insert insert \
	    "[displayName $vv]\t" NAME \
	    "[$vv type]\t" TYPE \
	    "[displayValue $vv]" VALUE "\n"

	set data(i,$text,linetovertex,$line) $vv
	set data(i,$text,vertextoline,$vv) $line

	incr line
    }

    # Remember how many lines there are being displayed:

    set data(i,$text,linecount) $line

    # Select the first value (which is on line 3).

    if {$line > 3} {
	selectDisplayedValueByLine $text 3
    }

    # Hide the insertion cursor:

    $text configure -insertwidth 0
}

# Recreate the line to vertex ID mapping after a significant change (insert,
# or delete of vertex) in the displayed node. Used by event handlers that
# change the contents of the value display window.

proc ::tged::recreateLineToVertexMapping {text node} {
    variable data

    array unset data i,$text,linetovertex,*
    array unset data i,$text,vertextoline,*

    set line 3

    # Iterate over all scalars:

    $node foreach vertex vv {
	if {[string equal node [$vv type]]} {
	    continue
	}
	set data(i,$text,linetovertex,$line) $vv
	set data(i,$text,vertextoline,$vv) $line

	incr line
    }

    # Remember how many lines there are being displayed:

    set data(i,$text,linecount) $line
}

# Parse a display ID and extract a node from it:

proc ::tged::getNodeFromDisplayID {tree id} {
    variable data

    # If the ID is empty then punt. This can happen when the
    # selection is set to empty, e.g. when the entire tree is
    # deleted from the display.

    if {[string equal "" $id]} {
	return
    }

    # Parse the ID and extract the storage.

    set s $data(i,$tree,storage)
    set sid [split $id .]

    # If this is a node ID, just grab the node out
    # of the storage:

    if {[string equal n [lindex $sid 0]]} {
	return [$s get node [lindex $sid 1]]
    }

    # No, it's a vertex ID. Get the vertex and
    # then its node value:

    set v [$s get vertex [lindex $sid 1]]
    if {![string equal [$v type] node]} {
	return ""
    }
    return [$v get]
}

# Compute whether to draw a cross, depending on whether there are
# sub-vertices:

proc ::tged::chooseCrossDrawingForNode {n} {
    if {[$n vertexcount] > 0} {
	return "allways"
    }
    return "never"
}

# Display the name of a vertex (if it's too long use elipsis):

proc ::tged::displayName {v} {
    set name [$v name]
    if {[string length $name] > 7} {
	return "[string range $name 0 6].."
    }
    return $name
}

# Display the value of a vertex in an appropriate manner

proc ::tged::displayValue {v} {
    set type [$v type]
    if {[string equal string $type]} {
	set l [split [$v get] "\n"]
	if {[llength $l] > 1} {
	    set l [lindex $l 0]
	}
	set l1 [string range $l 0 20]
	if {![string equal $l1 $l]} {
	    set l1 "$l1..."
	}
	return $l1
    }
    if {[string equal binary $type]} {
	return "<binary value>"
    }
    return [$v get]
}

# Display a title frame around the tree that shows if the storage is modified.

proc ::tged::displayModified {tree} {
    variable data

    set tf $data(i,$tree,titleframe)
    if {![info exists data(i,$tree,storage)]} {
	return

    }
    set storage $data(i,$tree,storage)
    set title "[$storage name]"
    if {![$storage isstable]} {
	append title "(*)"
    }
    $tf configure -text $title
}

# Helper procedure to compute a unique ID for links:

proc ::tged::genint {} {
    variable data

    if {![info exists data(gd,genint)]} {
	set data(gd,genint) 0
    }
    set ret $data(gd,genint)
    incr data(gd,genint)

    return $ret
}

# Helper proc to determine if a vertex with the given ID is currently being
# displayed in the text pane.

proc ::tged::isDisplayedScalarVertex {text vertex} {
    variable data

    info exists data(i,$text,vertextoline,$vertex)
}

# Helper proc to determine if a node with the given ID is currently the
# selected node.

proc ::tged::isSelectedNode {tree id} {
    variable data

    if {[catch {set sid $data(i,$tree,selectednode)}]} {
	return 0
    }
    if {$id == $sid} {
	return 1
    }
    return 0
}

# Helper procedure to remove a vertex from the display tree.

proc ::tged::removeVertexFromTreeDisplay {tree nid} {
    variable data

    if {[catch {set n $data(i,$tree,node,$nid)}]} {
	return
    }

    # Work around a suspected bug in bwidget tree widget that errors out
    # if you delete the selected node. So we unset the selection and then
    # after the deletion we set it to the parent if the deleted node had
    # a parent.

    set parent [$tree parent $n]
    $tree selection clear
    $tree delete $n

    unset data(i,$tree,node,$nid)
}

# Helper procedure to update the display of a node.

proc ::tged::addVertexToDisplayedNode {tree node vertex} {
    variable data

    # Try to find the display ID for this node -- punt if not found.

    set nid [$node id]
    if {[catch {set id $data(i,$tree,node,$nid)}]} {
	return
    }

    # If this node has previously been filled out, insert an entry for the
    # new vertex (if it hasn't been filled out yet it will show the new vertex
    # when it does get filled out).

    if {[info exists data(i,$tree,$id,filledOut)]} {
	# Insert the new vertex into this node

	set sid v.[$vertex id]
	set cn [$vertex get]
	$tree insert end $id $sid \
	    -text [$vertex name] \
	    -font ::tged::Keys \
	    -drawcross [chooseCrossDrawingForNode $cn] \
	    -image [Bitmap::get folder]

	# Record that this node is already being displayed in the
	# label $sid

	set data(i,$tree,node,[$cn id]) $sid
    }

    # Configure the display of the node containing the new vertex to
    # to always display a cross

    $tree itemconfigure $id -drawcross allways
}
