###########################################################################
#                                                                         #
# Procedures activated in response to bindings on the tged editor.        #
#                                                                         #
# 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.

# Helper procedure to create the mapping from combo box indices to
# vertex type selectors:

proc ::tged::createVertexTypeChoices {} {
    variable data

    set data(gc,vertextype,-1) ""
    set data(gc,vertextype,0) -int
    set data(gc,vertextype,1) -double
    set data(gc,vertextype,2) -string
    set data(gc,vertextype,3) -binary
}

# Helper function to extract the type string for the combo box used to
# select the type of a new vertex to add

proc ::tged::getVertexTypeChoice {cb} {
    variable data

    return $data(gc,vertextype,[$cb getvalue])
}

# Helper procedure to propagate values to named variables.
#
# This procedure takes 3*n+1 arguments, for n values to propagate:
# * The first argument is a widget. It should be destroyed when the
#   propagation is done.
# * Arguments i,i+1,i+2 are a triple; the first argument names a slot in the
#   global "data" array to contain the value; the second argument is a value
#   interpreted according to the third argument:
#	- if the third argument is "val" the second argument is the value.
#	- if the third argument is "entry" the second argument is the name
#	  of an entry widget. The value is the contents of the widget.
#	- if the third argument is "text" the second argument is the name
#	  of a text widget. The value is the contents of the widget.
#	- if the third argument is "typechoice" the second argument is the
#	  name of a combo box whose selected value represents a type. The
#	  value is the name of that type.

proc ::tged::setVars {win args} {
    variable data

    foreach {slot val type} $args {

	# Only set the variable if it does not exist. This avoids smashing
	# the values stored first by clicking the OK button which then causes
	# the window to be destroyed, firing the <Destroy> binding, which then
	# comes back here to set the values as if the Cancel button 
	# was pressed.

	if {[info exists data($slot)]} {
	    continue
	}

	# Figure out how to set the variable.
	#
	# NOTE: If we're extracting the value from a text widget, remember
	# to remove the last extra newline.

	switch $type {
	    val {
		set data($slot) $val
	    }
	    entry {
		set data($slot) [$val get]
	    }
	    text {
		set data($slot) [string range [$val get 1.0 end] 0 end-1]
	    }
	    typechoice {
		set data($slot) [getVertexTypeChoice $val]
	    }
	}
    }

    catch {destroy $win}
}

# Utility procedure to strip the canvas widget out of the passed tree
# widget name. The bindings are on the canvas contained within the tree,
# hence the window name passed will have the canvas as its last component.
# But operations such as selection should be done relative to the whole
# tree widget, so we need to strip off that last component.

proc ::tged::stripCanvasName {tree} {
    join [lrange [split $tree .] 0 end-1] .
}

# Clean up everything associated with this tree widget:

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

    # Remove the callbacks for this storage:

    removeCallbacks $tree

    # Get the hull widget:

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

    # Get the text widget:

    set txt $data(i,$tree,text)

    # Delete all entries to do with this tree widget:

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

    # Delete all entries to do with the associated text widget:

    array unset data i,$txt,*
    array unset data c,$txt,*

    # Delete all entries to do with the hull widget:

    array unset data i,$hull,*
    array unset data c,$hull,*
}

# Commit the storage:

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

    set storage $data(i,$tree,storage)
    if {[string equal "" $storage]} {
	return
    }
    $storage commit

    # Mark the tree as unmodified:

    markEdited $tree ""
}

# Delete the selected node from the tree:

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

    if {[catch {set sel $data(i,$tree,selection)}]} {
	return
    }

    # Move the selection to the parent of the node being deleted, if
    # it has one.

    set parent [$tree parent $sel]
    if {$parent == ""} {
	$tree selection clear
    } else {
	$tree selection set [$tree parent $sel]
    }

    deleteNode $tree $sel
}
    
# Delete the passed node (no confirmation yet, will do that later):

proc ::tged::deleteNode {tree node} {
    set entity [split $node .]

    if {[string equal l [lindex $entity 0]]} {
	set vertex [getOrCacheVertex $tree [lindex $entity 1]]
	$vertex detach
	forgetCachedVertex $tree [$vertex id]
    } elseif {[string equal v [lindex $entity 0]]} {
	set vertex [getOrCacheVertex $tree [lindex $entity 1]]
	$vertex detach
	forgetCachedVertex $tree [$vertex id]
    } else {
	set node [getOrCacheNode $tree [lindex $entity 1]]
	$node detach
	forgetCachedNode $tree [$node id]
    }

    # Mark the tree as dirty:

    markEdited $tree " (*)"

    # The tree display will be updated through callback events.
}

# Helper procedure to create modal dialog for creating a new node.

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

    # Get a new counter for the dialog toplevel window:

    set tlc $data(gd,toplevelCounter)
    incr data(gd,toplevelCounter)

    # This is the variable to wait on:

    set wvar i,$tree,$tlc,wvar
    catch {unset data($wvar)}

    # This is the status variable that contains the result of this dialog:

    set dvar i,$tree,$tlc,dvar
    catch {unset data($dvar)}

    # Create the gui dialog:

    set ned $tree.toplevelEditor$tlc
    toplevel $ned
    wm title $ned "Specify a node name: "

    # Container frame:

    frame $ned.f

    # Entry frame:

    frame $ned.f.t
    label $ned.f.t.l -text "Specify a node name: "
    set entry [entry $ned.f.t.e]
    bind $entry <KeyPress-Return> [list ::tged::setVars $ned \
				       $wvar $ned.f.t.e entry \
				       $dvar done val]
    bind $entry <Destroy> [list ::tged::setVars $ned \
			       $wvar "" val \
			       $dvar cancel val]

    # Buttons frame:

    frame $ned.f.b
    button $ned.f.b.ok \
	-text OK \
	-highlightthickness 2 \
	-command [list ::tged::setVars $ned \
		      $wvar $ned.f.t.e entry \
		      $dvar done val]
    bind $ned.f.b.ok <KeyPress-Return> [list $ned.f.b.ok invoke]
    button $ned.f.b.cancel \
	-text Cancel \
	-command [list ::tged::setVars $ned \
		      $wvar "" val \
		      $dvar cancel val]
    bind $ned.f.b.cancel <KeyPress-Return> [list $ned.f.b.cancel invoke]

    # Pack everything nice together:

    pack $ned.f		-side top -expand y -fill both
    pack $ned.f.t	-side top -expand y -fill both
    pack $ned.f.t.l	-side left -fill y
    pack $ned.f.t.e	-side right -expand y -fill both

    pack $ned.f.b	-side bottom -fill x
    pack $ned.f.b.ok $ned.f.b.cancel -side left -padx 3 -pady 3

    # Direct keyboard strokes to the entry:

    focus $ned.f.t.e

    # Wait for the dialog to finish. Note that vwait needs the
    # fully qualified name of the variable to wait on:

    vwait ::tged::data($dvar)

    # Return the names of the variables containing the result:

    return [list $dvar $wvar]
}

# Add a node to the selection:

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

    # Check if there is a selection, if not, then punt.

    if {![info exists ::tged::data(i,$tree,selection)]} {
	return
    }
    set node $data(i,$tree,selection)

    # Extract the parts of the passed label, if that fails then punt.

    if {[catch {set selection [split $node .]}]} {
	return
    }

    # Ask the user for the name of the new node:

    if {[catch {set userInput [addNodeDialog $tree]} err]} {
	return
    }

    set dvar [lindex $userInput 0]
    set wvar [lindex $userInput 1]

    # If the user aborted the operation then punt:

    if {[string equal cancel $data($dvar)]} {
	catch {unset data($dvar)}
	catch {unset data($wvar)}

	return
    }

    # OK, so wvar contains the name of the new node. Now get the node
    # into which we're going to insert the new node.

    set nvn $data($wvar)

    catch {unset data($dvar)}
    catch {unset data($wvar)}

    if {[string equal n [lindex $selection 0]]} {
	set node [getOrCacheNode $tree [lindex $selection 1]]
    } else {
	set vertex [getOrCacheVertex $tree [lindex $selection 1]]
	if {![string equal node [$vertex type]]} {
	    return
	}
	set node [$vertex get]
    }

    # Finally we're ready to insert the new node:

    $node addnode $nvn last

    # Mark the tree as edited:

    markEdited $tree " (*)"

    # The tree display will be updated by callback events.
}

# Dialog to add a value to the selected node or to edit an existing vertex:

proc ::tged::addOrEditValueDialog {tree {vertex ""}} {
    variable data

    # Get a new counter for the dialog toplevel window:

    set tlc $data(gd,toplevelCounter)
    incr data(gd,toplevelCounter)

    # This is the variable that will contain the name of the new vertex.

    set wvar i,$tree,$tlc,wvar
    catch {unset data($wvar)}

    # This is the variable that will contain the type of the new vertex:

    set tvar i,$tree,$tlc,tvar
    catch {unset data($tvar)}

    # This is the variable that will contain the value of the new vertex:

    set vvar i,$tree,$tlc,vvar
    catch {unset data($vvar)}

    # This is the status variable that contains the result of this dialog:

    set dvar i,$tree,$tlc,dvar
    catch {unset data($dvar)}

    # Create the dialog gui:

    set ved $tree.toplevelEditor$tlc
    toplevel $ved
    wm title $ved "Edit Value: "

    # Container frame:

    frame $ved.f

    # Entries frame:

    frame $ved.f.t

    label $ved.f.t.lvn -text "Enter value name: "	-anchor w
    label $ved.f.t.lvt -text "Select its type: "	-anchor w
    label $ved.f.t.lvv -text "Enter its value: "	-anchor w

    set entry [Entry $ved.f.t.vn -helptext "Select the value's name"]
    set combo [ComboBox $ved.f.t.vt -values {integer double string binary} \
		   -helptext "Select the value's type"]
    ScrolledWindow $ved.f.t.vvsw
    set text [text $ved.f.t.vvsw.txt -width 50 -height 20]
    $ved.f.t.vvsw setwidget $ved.f.t.vvsw.txt

    # Bind the <Destroy> event on *one* widget only so that if the hull
    # is destroyed it'll fire once. We yse the entry widget for the name
    # of the vertex, it could be any other leaf widget.

    bind $entry <Destroy> [list ::tged::setVars $ved \
			       $wvar "" val \
			       $tvar "" val \
			       $vvar "" val \
			       $dvar cancel val]

    # Buttons frame:

    frame $ved.f.b
    button $ved.f.b.ok \
	-text OK \
	-highlightthickness 2 \
	-command [list ::tged::setVars $ved \
		      $wvar $ved.f.t.vn entry \
		      $tvar $ved.f.t.vt typechoice \
		      $vvar $ved.f.t.vvsw.txt text \
		      $dvar done val]
    bind $ved.f.b.ok <KeyPress-Return> [list $ved.f.b.ok invoke]
    button $ved.f.b.cancel \
	-text Cancel \
	-command [list ::tged::setVars $ved \
		      $wvar "" val \
		      $tvar "" val \
		      $vvar "" val \
		      $dvar cancel val]
    bind $ved.f.b.cancel <KeyPress-Return> [list $ved.f.b.cancel invoke]

    # Pack everything nice together:

    pack $ved.f		-side top -expand y -fill both
    pack $ved.f.t	-side top -expand y -fill both

    grid $ved.f.t.lvn $ved.f.t.vn	-sticky news -padx 3 -pady 3
    grid $ved.f.t.lvt $ved.f.t.vt	-sticky news -padx 3 -pady 3
    grid $ved.f.t.lvv $ved.f.t.vvsw	-sticky news -padx 3 -pady 3

    pack $ved.f.b	-side bottom -fill x
    pack $ved.f.b.ok $ved.f.b.cancel -side left -padx 3 -pady 3

    # If an existing vertex is being edited, associate the vertex with
    # the text widget so that the text widget can be initialized to
    # the existing vertex's value:

    if {![string equal "" $vertex]} {
	set index @[lsearch {int double string binary} [$vertex type]]
	$entry insert end [$vertex name]
    } else {
	set index first
    }

    # Ensure that editing restrictions apply for numeric data, and initialize
    # the text widget to the value of the given vertex, or empty.

    $combo configure -modifycmd \
	     [list ::tged::setValueEditMaskAndContents $text $combo $vertex]
    $ved.f.t.vt setvalue $index
    setValueEditMaskAndContents $text $combo $vertex
    
    # Direct keyboard strokes to the entry for the vertex name:

    focus $ved.f.t.vn

    # Wait for the dialog to finish. Note that wvait needs the fully
    # qualified name of the variable to wait on:

    vwait ::tged::data($dvar)

    # Return the names of the variables that contain the result:

    return [list $dvar $tvar $vvar $wvar]
}

# Procedure called to edit and add a new value to the selected node.

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

    # Check if there is a selection, if not, then punt.

    if {![info exists ::tged::data(i,$tree,selection)]} {
	return
    }
    set node $data(i,$tree,selection)

    # Extract the parts of the passed label, if that fails then return.

    if {[catch {set selection [split $node .]}]} {
	return
    }

    # Ask the user for the name, type and value of a new vertex:

    if {[catch {set userInput [addOrEditValueDialog $tree]} err]} {
	tk_messageBox -type ok -message "error in dialog: $err"
	return
    }

    foreach {dvar tvar vvar wvar} $userInput {}

    # If the user aborted the operation then return:

    if {[string equal cancel $data($dvar)]} {
	catch {unset data($dvar)}
	catch {unset data($tvar)}
	catch {unset data($vvar)}
	catch {unset data($wvar)}

	return
    }

    # Retrieve the individual parts for the construction of the new
    # vertex, and perform some sanity checks.

    set vn $data($wvar)
    set vt $data($tvar)
    set vv $data($vvar)

    unset data($dvar)
    unset data($wvar)
    unset data($vvar)
    unset data($tvar)

    if {[string equal n [lindex $selection 0]]} {
	set node [getOrCacheNode $tree [lindex $selection 1]]
    } else {
	set vertex [getOrCacheVertex $tree [lindex $selection 1]]
	if {![string equal node [$vertex type]]} {
	    return
	}
	set node [$vertex get]
    }

    # Finally we're ready to add the new vertex:

    $node add $vn last $vv $vt

    # Mark the tree as edited:

    markEdited $tree " (*)"

    # Display will be updated by callback.
}

proc ::tged::editDisplayedValue {text} {
    variable data

    # See if there's a selected value, if not then return.

    if {![info exists data(i,$text,selectedline)]} {
	return
    }

    # Retrieve the vertex for the selected value:

    set tree [associatedTreeWidget $text]
    set vertex ""
    catch {set vertex $data(i,$text,linetovertex,$data(i,$text,selectedline))}
    if {$vertex == ""} {
	return
    }

    # Ask the user to edit the value of this vertex:

    if {[catch {set userInput [addOrEditValueDialog $tree $vertex]} err]} {
	tk_messageBox -type ok -message "error in dialog: $err"
	return
    }

    foreach {dvar tvar vvar wvar} $userInput {}

    # If the user aborted the operation then return:

    if {[string equal cancel $data($dvar)]} {
	catch {unset data($dvar)}
	catch {unset data($tvar)}
	catch {unset data($vvar)}
	catch {unset data($wvar)}

	return
    }

    # Retrieve the individual parts for the construction of the new
    # vertex and clean up

    set vn $data($wvar)
    set vt $data($tvar)
    set vv $data($vvar)

    unset data($dvar)
    unset data($wvar)
    unset data($vvar)
    unset data($tvar)

    # Set the vertex to its (potentially new) name and value.

    if {[$vertex name] != $vn} {
	$vertex rename $vn
    }
    if {[$vertex get] != $vv} {
	$vertex set $vv $vt
    }

    # Mark the tree as edited:

    markEdited $tree " (*)"

    # Display will be updated by callback.
}

# Detach (and drop) a displayed value

proc ::tged::deleteDisplayedValue {text} {
    variable data

    # Punt if there's no selected value

    if {![info exists data(i,$text,selectedline)]} {
	return
    }

    # Retrieve the vertex for the selected value:

    set tree [associatedTreeWidget $text]
    set vertex $data(i,$text,linetovertex,$data(i,$text,selectedline))
    if {$vertex == ""} {
	return
    }

    # Get the node that contains this vertex. Punt if the vertex is already
    # detached (should not happen):

    if {[$vertex isdetached]} {
	return
    }
    set node [$vertex node]

    # Detach the vertex:

    $vertex detach
}    

# Select a line in the display of values, given a pixel (@x,y).
#
# The algorithm is as follows:
# * If the pixel is on lines from 3 to N where N is the last line of
#   the text widget, select that line.
# * If the pixel is anywhere on line < 3 or > N do nothing.

proc ::tged::selectDisplayedValue {text x y} {
    variable data

    # Figure out what line we're on, and if we're in a legitimate line:

    $text mark set displaymark "@$x,$y linestart"
    $text tag add displaytag displaymark "displaymark lineend"

    set range [$text tag range displaytag]

    $text mark unset displaymark
    $text tag remove displaytag 1.0 end

    # If the range is empty, do nothing more:

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

    # If the line proposed to be selected is < 3 or > N then return.

    selectDisplayedValueByLine $text [lindex [split [lindex $range 0] .] 0]
}

# Select a line in the display of values, given a line number.
#
# This procedure allows selection of lines 3 to N only. If a line number
# not in this range is given, it does nothing.

proc ::tged::selectDisplayedValueByLine {text line} {
    variable data

    if {($line < 3) || ($line >= $data(i,$text,linecount))} {
	return
    }

    # OK, this line is a legitimate selection, so select it:

    $text mark set insert $line.0
    $text tag remove sel 1.0 end
    $text tag add sel insert "insert lineend"
    $text mark unset insert

    $text see insert

    # Remember which line is selected:

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

# Select the line $offset lines away from the currently selected line:

proc ::tged::selectNextDisplayedValue {text offset} {
    variable data

    if {[info exists data(i,$text,selectedline)]} {
	selectDisplayedValueByLine \
	    $text \
	    [expr $data(i,$text,selectedline) + $offset]
    }
}

# Mark the tree as dirty or unmodified:

proc ::tged::markEdited {tree mark} {
    variable data

    set tf $data(i,$tree,titleframe)
    set storage $data(i,$tree,storage)
    $tf configure -text "[$storage name]$mark"
}
