# file: .../tcl-lib/snacced.tcl # # $Header: /cvs/Darwin/Security/SecuritySNACCRuntime/tcl-lib/snacced.tcl,v 1.1.1.1 2001/05/18 23:14:11 mb Exp $ # $Log: snacced.tcl,v $ # Revision 1.1.1.1 2001/05/18 23:14:11 mb # Move from private repository to open source repository # # Revision 1.1.1.1 1999/03/16 18:06:56 aram # Originals from SMIME Free Library. # # Revision 1.2 1997/02/28 13:39:57 wan # Modifications collected for new version 1.3: Bug fixes, tk4.2. # # Revision 1.1 1997/01/01 23:12:00 rj # first check-in # # todo: # int, enum and bit string editors with scrollbar #\[banner "initialization"]--------------------------------------------------------------------------------------------------------- set version 1.0 #tk colormodel . monochrome # check all types whether they were marked as PDU. # collect them in an associative array (indexed by module name) foreach t [snacc types] \ { if {[lindex [snacc type $t] 1] == {pdu}} \ { set module [lindex $t 0] set type [lindex $t 1] lappend pdus($module) $type } } #foreach n [array names pdus] \ #{ # debug "module $n: $pdus($n)" #} #\[banner "debugging aid"]---------------------------------------------------------------------------------------------------------- set debug 0 proc debug {text} \ { global debug if $debug {puts $text} } #\[banner "help texts"]------------------------------------------------------------------------------------------------------------- set helptext(about) "SnaccEd $version" set helptext(manoeuv) \ "Button 1 on label show/hide subnodes (except for lists) on list perform action (selected with button 3's popup) Button 2 on label open/close value editor on canvas, list or text drag view Button 3 on label show/hide parent on list select action mode (for button 1) on text pops up menu for text import/export " #\[banner "File loading and saving"]------------------------------------------------------------------------------------------------ # called from file_reload and file_load_from # clears the display so that only the file's root gets shown proc file_prune {fileref} \ { upvar #0 $fileref file set tree $file(tree) set handle $file(handle) list_cleanup /$handle $handle $tree prune {} ed_addnode $tree {} {} {} $handle $handle valid $tree draw } # this function is called from the "File" menu. # it reloads the file contents from its old origin: proc file_reload {fileref} \ { set rc 1 upvar #0 $fileref file # file_prune must be called before the snacc object is modified: file_prune $fileref $file(toplevel) config -cursor watch update idletasks if {[catch {snacc read $file(handle)} msg]} \ { tk_dialog .d load "Couldn't reload: $msg" warning 0 Dismiss } \ else \ { set file(modified) 0 set rc 0 } $file(toplevel) config -cursor arrow return $rc } # this function is called from the "File" menu. # it lets the user select a file and loads its contents proc file_load_from {fileref} \ { set rc 1 upvar #0 $fileref file if {[selbox fn ct]} \ { # file_prune must be called before the snacc object is modified: file_prune $fileref $file(toplevel) config -cursor watch update idletasks if {[catch {snacc read $file(handle) $ct $fn} msg]} \ { tk_dialog .d load "Couldn't load $fn: $msg" warning 0 Dismiss } \ else \ { set file(modified) 0 set rc 0 } $file(toplevel) config -cursor arrow } return $rc } # this function is called from the "File" menu. # it saves the file contents to its old origin: proc file_save {fileref} \ { set rc 1 upvar #0 $fileref file $file(toplevel) config -cursor watch update idletasks if {[catch {snacc write $file(handle)} msg]} \ { tk_dialog .d save "Couldn't save: $msg" warning 0 Dismiss } \ else \ { set file(modified) 0 set rc 0 } $file(toplevel) config -cursor arrow return $rc } # this function is called from the "File" menu. # it lets the user select a file and saves the file's contents proc file_save_as {fileref} \ { set rc 1 upvar #0 $fileref file if {[selbox fn {}]} \ { $file(toplevel) config -cursor watch update idletasks if {[catch {snacc write $file(handle) $fn} msg]} \ { tk_dialog .d save "Couldn't save $fn: $msg" warning 0 Dismiss } \ else \ { set file(modified) 0 set rc 0 } $file(toplevel) config -cursor arrow } return $rc } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # this function is called from prune_or_add_children, list_click, new_choice, toggle_se* and set_or_add_root # it adds the node's children to the display # some of the children may already be displayed (this is usually the case when the function gets called from list_click or set_or_add_root). # ed_addnode will be called for every child. proc ed_expand {tree treepath snaccpath} \ { set canvas [$tree canvas] set info [snacc info $snaccpath] set type [lindex $info 2] switch $type \ { SEQUENCE - SET \ { debug "$type:" foreach elem [lindex $info 3] \ { set name [lindex $elem 0] set validity [lindex $elem 1] debug " $validity $name" ed_addnode $tree $treepath $treepath $snaccpath $name $name $validity } } SEQUENCE\ OF - SET\ OF \ { set len [lindex $info 3] set varname var:$treepath upvar #0 $varname var debug [list treepath=$treepath] debug [list varname=$varname] debug [list idlist=$var(idlist)] debug [list expand list ($type) len=$len] for {set i 0} {$i < $len} {incr i} \ { set id [lindex $var(idlist) $i] debug [list index $i id $id] if {$id} \ { ed_addnode $tree $treepath $treepath $snaccpath $id $i valid } } } CHOICE \ { set name [lindex $info 3] set validity [lindex $info 4] debug " $validity $name" ed_addnode $tree $treepath $treepath $snaccpath $name $name $validity } } } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # ed_addnode is called from set_or_add_root, new_file, file_prune and ed_expand. # the node may already be displayed (this is usually the case when the function ed_expand gets called from list_click or set_or_add_root). in this case the node gets moved to the right position. # otherwise the node is created at the right place. # the arguments are: # tree name of the tree widget # treeparent tag of the displayed parent node. this is usually the same as the treeparentpath, except when the display gets extended into the parent direction where the root tag is {} # treeparentpath tag of the logical parent node. # snaccparentpath names of the # treenode node's name, gets appended to the treeparentpath # snaccnode node's name, gets appended to the snaccparentpath # validity proc ed_addnode {tree treeparent treeparentpath snaccparentpath treenode snaccnode validity} \ { set canvas [$tree canvas] set treepath "$treeparentpath/$treenode" set snaccpath "$snaccparentpath $snaccnode" if [llength [$canvas find withtag $treepath]] \ { debug [list movelink $treepath $treeparent] $tree movelink $treepath $treeparent } \ else \ { #debug [list addnode $snaccpath] if {[llength $snaccparentpath] > 0} \ { set nodelabeltext $snaccnode } \ else \ { set finfo [snacc finfo [string range $snaccpath 1 end]] if {[lindex $finfo 0] == {}} \ { set nodelabeltext {(unnamed)} } \ else \ { set nodelabeltext [lindex $finfo 0] } } $canvas create text 0 0 -text $nodelabeltext -tags [list $validity-label $treepath $treepath:label] set line [$canvas create line 0 0 0 0] # fix for canvas bug: for reverse video, the canvas displays black items on a black background if {[tk colormodel .] == {monochrome} && [lindex [$canvas config -background] 4] == {black}} \ { $canvas itemconfigure $treepath -fill white $canvas itemconfigure $line -fill white } if {$validity == {void}} \ { if {[tk colormodel .] == {color}} \ { # #b0b0b0 is the light grey of disabled checkbuttons: $canvas itemconfigure $treepath -fill #b0b0b0 $canvas itemconfigure $line -fill #b0b0b0 } \ else \ { $canvas itemconfigure $treepath -stipple gray50 $canvas itemconfigure $line -stipple gray50 } } debug [list addlink $treeparent $treepath $line] $tree addlink $treeparent $treepath $line } } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # open/drop subtree proc prune_or_add_children {canvas} \ { set tree $canvas.t # debug $canvas set id [$canvas find withtag current] if {$id == {}} \ { debug "no item" } \ else \ { set treepath [lindex [$canvas gettags $id] 1] set snaccpath [tree2snacc $treepath] set type [lindex [snacc info $snaccpath] 2] switch $type \ { SEQUENCE\ OF - SET\ OF {} default { # debug $treepath if {[$tree isleaf $treepath]} \ { debug [list expanding $treepath $snaccpath] ed_expand $tree $treepath $snaccpath } \ else \ { debug [list cutting $treepath] # !!! list_cleanup usually has to be called with the node that gets removed! # in this case calling it with the node that stays around doesn't hurt because it is guaranteed not to be a SEQUENCE OF or SET OF type (they are handled a few lines above) list_cleanup $treepath $snaccpath $tree prune $treepath } } } } $tree draw } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # this function has to be called whenever a subtree that may contain SET OF or SEQUENCE of types gets removed from the display # it must be called *before* the snacc object gets destroyed, the function examines it! proc list_cleanup {treepath snaccpath} \ { set info [snacc info $snaccpath] set type [lindex $info 2] switch $type \ { SET - SEQUENCE - CHOICE { foreach elem [lindex $info 3] \ { set name [lindex $elem 0] set validity [lindex $elem 1] if {$validity == {valid}} \ { set subtreepath "$treepath/$name" set subsnaccpath "$snaccpath $name" list_cleanup $subtreepath $subsnaccpath } } } SET\ OF - SEQUENCE\ OF { set varname var:$treepath global $varname debug [list varname=$varname] if {[info exists $varname]} \ { set idlist [set $varname\(idlist)] debug [list idlist=$idlist] set i 0 foreach id $idlist \ { if {$id != 0} \ { set subtreepath "$treepath/$id" set subsnaccpath "$snaccpath $i" list_cleanup $subtreepath $subsnaccpath } incr i } unset $varname } } } } # this function must be called when calling "$tree root $treepath". # it calls list_cleanup for all nodes that are neither parent nor in the subtree pointed to by $treepath. proc list_cleanup_not_me {treepath snaccpath} \ { if {[set i [llength $snaccpath]] > 1} \ { incr i -1 set parenttreepath [join [lrange [split $treepath /] 0 $i] /] incr i -1 set parentsnaccpath [lrange $snaccpath 0 $i] set info [snacc info $parentsnaccpath] set type [lindex $info 2] switch $type \ { SET - SEQUENCE - CHOICE { foreach elem [lindex $info 3] \ { set name [lindex $elem 0] set validity [lindex $elem 1] if {$validity == {valid}} \ { set subparenttreepath "$parenttreepath/$name" set subparentsnaccpath "$parentsnaccpath $name" if {$subparenttreepath != $treepath} \ { list_cleanup $subparenttreepath $subparentsnaccpath } } } } SET\ OF - SEQUENCE\ OF { set varname var:$parenttreepath global $varname debug [list varname=$varname] set idlist [set $varname\(idlist)] debug [list idlist=$idlist] set i 0 foreach id $idlist \ { if {$id != 0} \ { set subparenttreepath "$parenttreepath/$id" set subparentsnaccpath "$parentsnaccpath $i" if {$subparenttreepath != $treepath} \ { list_cleanup $subparenttreepath $subparentsnaccpath set $varname\(idlist) [lreplace [set $varname\(idlist)] $i $i 0] } } incr i } } } # recursion: list_cleanup_not_me $parenttreepath $parentsnaccpath } } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # /file0/files/1/name \(-> { file0 files 0 name} proc tree2snacc {treepath} \ { set subtreepath {} foreach elem [lrange [split $treepath /] 1 end] \ { set treeelem $elem if {[regexp {^[0-9]} $elem]} \ { set varname var:$subtreepath global $varname set idlist [set $varname\(idlist)] set id $elem set index 0 foreach lid $idlist \ { if {$lid == $id} break incr index } if {$index == [llength $idlist]} \ { error "tree2snacc: id $id not found in idlist [list $idlist]" } set snaccelem $index } \ else \ { set snaccelem $elem } append subtreepath /$treeelem append subsnaccpath " $snaccelem" debug [list >>$subtreepath--$subsnaccpath<<] } debug [list >>$subtreepath--$subsnaccpath<<] return $subsnaccpath } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc import_text {text_w treepath} \ { if {[selbox fn {}]} \ { if {[catch {set text [snacc import $fn]} msg]} \ { tk_dialog .d import "Couldn't import $fn: $msg" warning 0 Dismiss } \ else \ { $text_w delete 0.0 end $text_w insert end $text snacc set [tree2snacc $treepath] $text } } } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc export_text {text_w} \ { if {[selbox fn {}]} \ { if {[catch {snacc export [$text_w get 0.0 end] $fn} msg]} \ { tk_dialog .d import "Couldn't export $fn: $msg" warning 0 Dismiss } } } proc frame_resize_bindings {fileref treepath} \ { upvar #0 $fileref file set frame $file(canvas).edit$treepath bind $frame [list frame_resize_start $fileref %x %y] bind $frame [list frame_resize_cont $fileref $treepath %x %y] bind $frame [list frame_resize_end $fileref $treepath] $frame config -cursor bottom_right_corner } proc frame_resize_start {fileref x y} \ { #debug [list frame_resize_start $fileref $x $y] upvar #0 $fileref file set file(resize_x) $x set file(resize_y) $y } proc frame_resize_cont {fileref treepath x y} \ { #debug [list frame_resize_cont $fileref $treepath $x $y] upvar #0 $fileref file set frame $file(canvas).edit$treepath set frametag $treepath:edit set oldw [lindex [$file(canvas) itemconfig $frametag -width] 4] set oldh [lindex [$file(canvas) itemconfig $frametag -height] 4] debug "old: $oldw x $oldh" set neww [max 1 [expr $oldw+$x-$file(resize_x)]] set newh [max 1 [expr $oldh+$y-$file(resize_y)]] debug "new: $neww x $newh" $file(canvas) itemconfig $frametag -width $neww -height $newh set file(resize_x) $x set file(resize_y) $y } proc frame_resize_end {fileref treepath} \ { #debug [list frame_resize_end $fileref $treepath] upvar #0 $fileref file $file(tree) nodeconfig $treepath $file(tree) draw } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # toggle content editor proc toggle_editor {canvas} \ { set tree $canvas.t set id [$canvas find withtag current] if {$id == {}} \ { debug "no item" } \ else \ { set treepath [lindex [$canvas gettags $id] 1] set snaccpath [tree2snacc $treepath] set frame $canvas.edit$treepath if [llength [$canvas find withtag $treepath:edit]] \ { debug "removing editor for [list $treepath]" $canvas delete $treepath:edit destroy $frame $tree nodeconfig $treepath -remove {} } \ else \ { debug "opening editor for [list $treepath]" set fileref [lindex [split $treepath /] 1] frame $frame -borderwidth 3 -bg #cdb79e set cleanup [list [list destroy $frame]] set info [snacc info $snaccpath] set type [lindex $info 2] switch $type \ { NULL \ { set label $frame.label label $label -text NULL pack $label } BOOLEAN \ { set value [snacc get $snaccpath] set var var:$treepath global $var set $var $value set button $frame.button #checkbutton $button -variable $var checkbutton $button -onvalue TRUE -offvalue FALSE -variable $var -textvariable $var -command [list debug [list $canvas $treepath]] pack $button trace variable $var w change_simple } INTEGER \ { set value [snacc get $snaccpath] set var var:$treepath global $var set $var $value if {[lindex $info 0] != {{} {}}} \ { set typeinfo [snacc type [lindex $info 0]] foreach elem [lindex $typeinfo 3] \ { set en [lindex $elem 0] set ev [lindex $elem 1] set button $frame.button$en radiobutton $button -text $en -variable $var -value $ev -anchor w pack $button -fill x } } set entry $frame.entry entry $entry -textvariable $var -width 9 -relief sunken int_entry_bindings $entry pack $entry -anchor w -fill x focus $entry trace variable $var w change_simple } ENUMERATED \ { set typeinfo [snacc type [lindex $info 0]] if {[catch {set value [snacc get $snaccpath]} msg] == 1} \ { global errorInfo errorCode if {$errorCode == {SNACC ILLENUM}} \ { set value [lindex [lindex $typeinfo 3] 0] snacc set $snaccpath $value append msg "--setting to first legal symbolic value \"$value\"" tk_dialog .d illenum "$msg" warning 0 Dismiss } \ else \ { error $msg $errorInfo $errorCode } } set var var:$treepath global $var set $var $value foreach ev [lindex $typeinfo 3] \ { set button $frame.button$ev radiobutton $button -text $ev -variable $var -value $ev -anchor w pack $button -fill x } trace variable $var w change_simple } REAL \ { set value [snacc get $snaccpath] set var var:$treepath global $var set $var $value set entry $frame.entry entry $entry -textvariable $var -relief sunken pack $entry frame_resize_bindings $fileref $treepath focus $entry trace variable $var w change_simple } BIT\ STRING \ { set value [snacc get $snaccpath] set var var:$treepath global $var set $var $value set max_ev 0 if {[lindex $info 0] != {{} {}}} \ { set typeinfo [snacc type [lindex $info 0]] foreach elem [lindex $typeinfo 3] \ { set en [lindex $elem 0] set ev [lindex $elem 1] set max_ev [max $ev $max_ev] set button $frame.button$en checkbutton $button -text $en -variable $var:$ev -command [list toggle_bit $var $ev] -anchor w pack $button -fill x } } set entry $frame.entry entry $entry -textvariable $var -relief sunken set len [max 8 [string length $value] [expr $max_ev + 1]] if {$len > 0} \ { debug [list length of entry is $len] $entry config -width $len } pack $entry -anchor w -fill x bit_string_entry_bindings $entry focus $entry trace variable $var w change_bits set $var $value; # trigger the trace } OBJECT\ IDENTIFIER \ { set value [snacc get $snaccpath] set var var:$treepath global $var set $var $value set entry $frame.entry entry $entry -textvariable $var -relief sunken pack $entry -fill both frame_resize_bindings $fileref $treepath focus $entry trace variable $var w change_simple } OCTET\ STRING \ { set value [snacc get $snaccpath] set text $frame.text set sb $frame.sb text $text -borderwidth 2 -relief sunken -yscrollcommand [list $sb set] -width 32 -height 8 scrollbar $sb -relief sunken -command [list $text yview] -width 10 -cursor arrow pack $sb -side right -fill y pack $text -side left -expand true -fill both bind $text [list $text scan mark %y] bind $text [list $text scan dragto %y] bind $text "snacc set \[tree2snacc $treepath\] \[$text get 0.0 end\]" bind $text "snacc set \[tree2snacc $treepath\] \[$text get 0.0 end\]" set m $frame.menu menu $m $m add command -label Load... -command "[list import_text $text $treepath]; [list $m unpost]" $m add command -label Save... -command "[list export_text $text]; [list $m unpost]" bind $text "[list $m] post \[expr %X -16\] \[expr %Y -8\]" bind $m [list $m unpost] bind $m [list $m unpost] $text insert end $value focus $text frame_resize_bindings $fileref $treepath } SEQUENCE - SET \ { set typeinfo [snacc type [lindex $info 0]] debug "$type:" set varelems [lindex $info 3] set typeelems [lindex $typeinfo 3] for {set i 0; set len [llength $varelems]} {$i < $len} {incr i} \ { set varelem [lindex $varelems $i] set typeelem [lindex $typeelems $i] set name [lindex $varelem 0] set validity [lindex $varelem 1] debug " $validity $name" set var var:$treepath:$name global $var set $var $validity set button $frame.$name checkbutton $button -text $name -onvalue valid -offvalue void -variable $var -command [list toggle_se* $canvas $treepath $name] -anchor w if {[lindex $typeelem 4] == {mandatory}} \ { #$button configure -disabledforeground [lindex [$button configure -fg] 4] -state disabled $button configure -state disabled } pack $button -fill x } } SEQUENCE\ OF - SET\ OF \ { set len [lindex $info 3] set varname var:$treepath upvar #0 $varname var if {![info exists var(idlist)]} \ { set var(idlist) {} set var(lastid) 0 } # no! needs a longer lifetime! #lappend cleanup [list global $varname] [list unset $varname] # set mbar $frame.mbar set list $frame.list set sb $frame.sb scrollbar $sb -command [list $list yview] -width 10 -relief sunken -cursor arrow # listbox $list -yscroll [list $sb set] -relief sunken -width 4 -height 5 text $list -borderwidth 2 -relief sunken -yscrollcommand [list $sb set] -width 4 -height 8 -exportselection 0 pack $sb -side right -fill y pack $list -side left -expand true -fill both # frame $mbar -relief raised -bd 2 # pack $mbar -side top -fill x # set mode $mbar.mode # set mode $frame.mode # set m $mode.m # menubutton $mode -text Mode -menu $m set m $frame.mode menu $m set lm "[list list_mode $canvas $treepath]; [list $m unpost]" $m add radiobutton -label Display -variable ${varname}(mode) -value display -command $lm $m invoke last $m add radiobutton -label Insert -variable ${varname}(mode) -value insert -command $lm $m add radiobutton -label Append -variable ${varname}(mode) -value append -command $lm $m add radiobutton -label Delete -variable ${varname}(mode) -value delete -command $lm # pack $mode -side left # pack $mode -side top -fill x $list tag config display -background #b2dfee -relief raised bind $list [list list_click $canvas $treepath] bind $list { } bind $list { } bind $list { } bind $list "[list $m] post \[expr %X-16\] \[expr %Y-8\]" bind $m [list $m unpost] bind $m [list $m unpost] debug $m for {set i 0} {$i < $len} {incr i} \ { $list insert end [format "%4d\n" $i] if {[llength $var(idlist)] > $i} \ { if {[set id [lindex $var(idlist) $i]]} \ { set line [expr $i + 1] $list tag add display $line.0 $line.end } } \ else \ { set var(idlist) [linsert $var(idlist) $i 0] } } frame_resize_bindings $fileref $treepath } CHOICE \ { set name [lindex $info 3] set validity [lindex $info 4] set typeinfo [snacc type [lindex $info 0]] set var var:$treepath set oldvar oldvar:$treepath global $var $oldvar set $var $name set $oldvar $name foreach elem [lindex $typeinfo 3] \ { set en [lindex $elem 0] set button $frame.button$en radiobutton $button -text $en -variable $var -value $en -command [list new_choice $canvas $treepath] -anchor w pack $button -fill x } debug " $validity $name" } default \ { error "unexpected type $type" } } scan [$canvas bbox $treepath:label] "%d%d%d%d" lx uy rx ly $canvas create window $lx $ly -anchor nw -tags [list edit $treepath $treepath:edit] -window $frame update idletasks; # calculate frame's size (needed by tree widget) # explicitly set the frame's width&height to avoid nasty effects when resizing: scan [$canvas bbox $treepath:edit] "%d%d%d%d" lx uy rx ly $canvas itemconfig $treepath:edit -width [expr $rx - $lx] -height [expr $ly - $uy] #debug [list cleanup = [join $cleanup \;]] $tree nodeconfig $treepath -remove [join $cleanup \;] } } $tree draw } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc list_click {canvas treepath} \ { set tree $canvas.t set snaccpath [tree2snacc $treepath] debug [list treepath=$treepath] debug [list snaccpath=$snaccpath] debug [list tree2snacc: [tree2snacc $treepath]] set varname var:$treepath upvar #0 $varname var set frame $canvas.edit$treepath set list $frame.list debug [list list_click: $list] debug [list varname=$varname] debug [list idlist=$var(idlist)] # debug [$list tag ranges display] set text_index [$list index current] #debug [list index: $index] if {$text_index != ""} \ { # strip the column number: set line [lindex [split $text_index .] 0] # lines numbers start at 1, indices at 0: set index [expr $line - 1] set len [llength $var(idlist)] set tags [$list tag names $text_index] switch $var(mode) \ { display \ { debug [list tags: $tags] debug [list line: $line] if {$index < $len} \ { set id [lindex $var(idlist) $index] debug [list index $index id $id] if {$id} \ { $list tag remove display $line.0 $line.end list_cleanup $treepath/$id "$snaccpath $index" debug [list $tree rmlink $treepath/$id] $tree rmlink $treepath/$id set var(idlist) [lreplace $var(idlist) $index $index 0] } \ else \ { $list tag add display $line.0 $line.end set var(idlist) [lreplace $var(idlist) $index $index [incr var(lastid)]] ed_expand $tree $treepath $snaccpath } } } insert - append \ { if {$var(mode) == {append}} {incr index} debug [list insert $index 0] set var(idlist) [linsert $var(idlist) $index 0] debug [list $var(idlist)] debug [list catch [list snacc set "$snaccpath {insert $index}" {}]] catch [list snacc set "$snaccpath {insert $index}" {}] set file(modified) 1 debug [list [snacc get $snaccpath]] $list insert end [format "%4d\n" [expr [lindex [split [$list index end] .] 0] - 1]] for {set i $len} {$i > $index} {incr i -1} \ { set line [expr $i + 1] if {[set id [lindex $var(idlist) $i]]} \ { debug [list $canvas itemconfigure $treepath/$id:label -text $i] $canvas itemconfigure $treepath/$id:label -text $i if {![lindex $var(idlist) [expr $i - 1]]} \ { debug [list $list tag add display $line.0 $line.end] $list tag add display $line.0 $line.end } } \ else \ { if {![lindex $var(idlist) [expr $i - 1]]} \ { debug [list $list tag remove display $line.0 $line.end] $list tag remove display $line.0 $line.end } } } set line [expr $index + 1] debug [list $list tag remove display $line.0 $line.end] $list tag remove display $line.0 $line.end } delete \ { if {$index < $len} \ { debug [list delete $index] if {[set id [lindex $var(idlist) $index]]} \ { # list_cleanup must be called before the snacc object is modified: list_cleanup $treepath/$id "$snaccpath $index" $tree rmlink $treepath/$id } incr len -1 for {set i $index} {$i < $len} {incr i} \ { set line [expr $i + 1] if {[set id [lindex $var(idlist) [expr $i + 1]]]} \ { debug [list $canvas itemconfigure $treepath/$id:label -text $i] $canvas itemconfigure $treepath/$id:label -text $i if {![lindex $var(idlist) $i]} \ { debug [list $list tag add display $line.0 $line.end] $list tag add display $line.0 $line.end } } \ else \ { if {[lindex $var(idlist) $i]} \ { debug [list $list tag remove display $line.0 $line.end] $list tag remove display $line.0 $line.end } } } set var(idlist) [lreplace $var(idlist) $index $index] debug [list $var(idlist)] debug [list snacc unset "$snaccpath $index"] snacc unset "$snaccpath $index" set file(modified) 1 debug [list [snacc get $snaccpath]] $list delete [$list index {end - 1 line}] [$list index end] } } } $tree draw } } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc list_mode {canvas treepath} \ { set var var:$treepath global $var set mode [set ${var}(mode)] set frame $canvas.edit$treepath set list $frame.list switch $mode \ { display {set cursor arrow} insert {set cursor based_arrow_up} append {set cursor based_arrow_down} delete {set cursor pirate} } $list config -cursor $cursor debug [list list_mode: ${var}(mode) set to $mode] } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc toggle_bit {var i} \ { global $var:$i $var set bit [set $var:$i] set val [set $var] debug [list toggle_bit $val $i to $bit] set pre [string range $val 0 [expr $i - 1]] set fill {} for {set l [string length $val]} {$l < $i} {incr l} \ { append fill 0 debug [list appending: $val] } set post [string range $val [expr $i + 1] end] debug [list toggle_bit combining $pre $fill $bit $post] set $var $pre$fill$bit$post } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc change_bits {var element op} \ { global $var set val [set $var] debug [list change_bits $var set to $val] debug [list set l [string length $val]] set l [string length $val] for {set i 0} {$i < $l} {incr i} \ { global $var:$i if {[info exists $var:$i]} \ { debug [list set $var:$i [string index $val $i]] set $var:$i [string index $val $i] } \ else \ { debug [list non-exist: $var:$i] } } foreach bitvar [info globals $var:*] \ { set i [lindex [split $bitvar :] 2] if {$i >= $l} \ { global $bitvar set $bitvar 0 } } change_simple $var $element $op } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc change_simple {var element op} \ { global $var set val [set $var] debug [list change_simple $var set to $val] set treepath [lindex [split $var :] 1] debug [list treepath= $treepath] set fileref [lindex [split $treepath /] 1] upvar #0 $fileref file set canvas $file(canvas) debug [list canvas= $canvas] set snaccpath [tree2snacc $treepath] debug [list snaccpath= $snaccpath] snacc set $snaccpath $val set file(modified) 1 } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc new_choice {canvas treepath} \ { set tree $canvas.t set snaccpath [tree2snacc $treepath] set var var:$treepath set oldvar oldvar:$treepath global $var $oldvar set val [set $var] set oldval [set $oldvar] set fileref [lindex [split $treepath /] 1] upvar #0 $fileref file debug "$file(modified)" debug [list new choice: $snaccpath = $val] # list_cleanup must be called before the snacc object is modified: list_cleanup $treepath/$oldval "$snaccpath $oldval" catch {snacc set $snaccpath [list $val {}]} set file(modified) 1 debug "$file(modified)" if {[llength [$canvas find withtag "$treepath/$oldval"]]} \ { $tree rmlink "$treepath/$oldval" ed_expand $tree $treepath $snaccpath $tree draw } set $oldvar $val } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc toggle_se* {canvas treepath name} \ { set tree $canvas.t set snaccpath [tree2snacc $treepath] set var var:$treepath:$name global $var set val [set $var] set fileref [lindex [split $treepath /] 1] upvar #0 $fileref file debug "$snaccpath $name = $val" # this procedure is called after the button value has changed, so adjust the display to the current (new) setting: if {$val == {void}} \ { # (change valid \(-> void) # list_cleanup must be called before the snacc object is modified: list_cleanup $treepath/$name "$snaccpath $name" snacc unset "$snaccpath $name" } \ else \ { # (change void \(-> valid) catch {snacc set "$snaccpath $name" {}} } set file(modified) 1 if {[llength [$canvas find withtag "$treepath/$name"]]} \ { debug [list rmlink "$treepath/$name"] $tree rmlink "$treepath/$name" # a bug in the tree widget requires us to redraw here: $tree draw ed_expand $tree $treepath $snaccpath $tree draw } } #\[sep]----------------------------------------------------------------------------------------------------------------------------- # add/drop parent and siblings proc set_or_add_root {canvas} \ { set tree $canvas.t set id [$canvas find withtag current] if {$id == {}} \ { debug "no item" } \ else \ { set treepath [lindex [$canvas gettags $id] 1] set snaccpath [tree2snacc $treepath] # debug $path if {[llength $snaccpath] == 1} \ { debug "at root already" } \ else \ { if {[$tree isroot $treepath]} \ { # show the parent: debug [list expanding [list $treepath $snaccpath]] set i [llength $snaccpath] incr i -1 set treeparentpath [join [lrange [split $treepath /] 0 $i] /] set treeparentnode [lindex [split $treepath /] $i] incr i -1 set snaccparentpath [lrange $snaccpath 0 $i] set snaccparentnode [lindex $snaccpath $i] set treeparentparentpath [join [lrange [split $treepath /] 0 $i] /] incr i -1 set snaccparentparentpath [lrange $snaccpath 0 $i] #debug [list ed_addnode $tree {} $parentparentpath $parentnode valid] ed_addnode $tree {} $treeparentparentpath $snaccparentparentpath $treeparentnode $snaccparentnode valid #debug [list ed_expand $tree $parentpath] ed_expand $tree $treeparentpath $snaccparentpath } \ else \ { # hide everything above this subtree: # debug "cutting $path" list_cleanup_not_me $treepath $snaccpath $tree root $treepath } # debug [snacc info $path] } } $tree draw } #\[sep]----------------------------------------------------------------------------------------------------------------------------- proc file_open {} \ { if {[selbox fn ct nullfn]} \ { debug "fn=$fn ct=$ct" if {$fn != {}} \ { if {[catch {set f [snacc open $ct $fn create]} msg]} \ { tk_dialog .d load "Couldn't open $fn {$ct}: $msg" warning 0 Dismiss return -1 } } \ else \ { if {[catch {set f [snacc create $ct]} msg]} \ { tk_dialog .d create "Couldn't create {$ct}: $msg" warning 0 Dismiss return -1 } } new_file $f return 0 } return -1 } #\[sep]----------------------------------------------------------------------------------------------------------------------------- set #file 0 set #files 0 # returns 1 on `cancel', otherwise exits or returns 0 proc close_file {fileref} \ { upvar #0 $fileref file if {$file(modified)} \ { set fi [snacc finfo $file(handle)] set fn [lindex $fi 0] set hasfn [expr {$fn != {}}] set isrw [expr {[lindex $fi 1] == {rw}}] set msg {There are unsaved changes} if {$hasfn} \ { append msg " in `$fn'" } append msg {. Save them?} switch [lindex {save discard cancel} [tk_dialog .d modified $msg questhead 0 Yes No Cancel]] \ { cancel \ { return 1 } save \ { if {$hasfn && $isrw} \ { if {[file_save $fileref]} \ { return 1 } } \ else \ { if {[file_save_as $fileref]} \ { return 1 } } } } } destroy $file(toplevel) global #files if {![incr #files -1]} \ { exit } return 0 } proc file_quit {} \ { global #files for {set i 0} {${#files}} {incr i} \ { if {[winfo exists .[set fileref file$i]]} \ { if {[close_file $fileref]} \ { return } } } } proc new_file {handle} \ { global #file while {[winfo exists [set toplevel .[set fileref file${#file}]]]} \ { incr #file } #global $fileref upvar #0 $fileref file set file(handle) $handle set file(toplevel) [toplevel $toplevel] wm title $toplevel snaccEd wm minsize $toplevel 150 100 wm geometry $toplevel 500x500 global #files incr #files set file(modified) 0 $toplevel config -cursor arrow set menubar $toplevel.menu frame $menubar -relief raised -bd 2 pack $menubar -side top -fill x set filem $menubar.file set m $filem.m menubutton $filem -text File -menu $m menu $m $m add command -label Reload -command [list file_reload $fileref] $m add command -label Load... -command [list file_load_from $fileref] $m add command -label Save -command [list file_save $fileref] $m add command -label {Save As...} -command [list file_save_as $fileref] $m add command -label Close -command [list close_file $fileref] $m add separator $m add command -label Open... -command file_open $m add separator $m add command -label Quit -command file_quit set fi [snacc finfo $handle] set hasnofn [expr {[lindex $fi 0] == {}}] set isro [expr {[lindex $fi 1] == {ro}}] if {$hasnofn} \ { $m entryconfigure Reload -state disabled } if {$hasnofn || $isro} \ { $m entryconfigure Save -state disabled } pack $filem -side left set help $menubar.help set m $help.m menubutton $help -text Help -menu $help.m menu $m $m add command -label About -command "help [list $m] \$helptext(about)" $m add command -label Manoeuvering -command "help [list $m] \$helptext(manoeuv)" pack $help -side right tk_menuBar $menubar $filem $help frame $toplevel.f0 frame $toplevel.f1 pack $toplevel.f0 -expand true -fill both pack $toplevel.f1 -fill x set file(canvas) [set canvas [canvas $toplevel.c -width 0 -height 0]] set hsb [scrollbar $toplevel.hsb -orient horiz -relief sunken -command [list $canvas xview]] set vsb [scrollbar $toplevel.vsb -relief sunken -command [list $canvas yview]] $canvas config -xscroll [list $hsb set] -yscroll [list $vsb set] set blind [frame $toplevel.blind -width [lindex [$vsb config -width] 4]] pack $vsb -in $toplevel.f0 -side right -fill y pack $canvas -in $toplevel.f0 -side left -expand true -fill both pack $blind -in $toplevel.f1 -side right pack $hsb -in $toplevel.f1 -side left -expand true -fill x bind $canvas [list $canvas scan mark %x %y] bind $canvas [list $canvas scan dragto %x %y] $canvas bind valid-label {prune_or_add_children %W} $canvas bind valid-label {toggle_editor %W} $canvas bind valid-label {set_or_add_root %W} set file(tree) [set tree [tree $canvas.t]] ed_addnode $tree {} {} {} $handle $handle valid $tree draw tkwait visibility $toplevel } proc snacced {} \ { wm withdraw . global argc argv if {$argc == 0} \ { if {[file_open]} \ { exit 1 } } \ else \ { if {$argc == 3} \ { set ct [lrange $argv 0 1] set fn [lindex $argv 2] if {[catch {set f [snacc open $ct $fn create]} msg]} \ { tk_dialog .d load "Couldn't open $fn {$ct}: $msg" warning 0 Dismiss exit 1 } } \ elseif {$argc == 2} \ { set ct [lrange $argv 0 1] if {[catch {set f [snacc create $ct]} msg]} \ { tk_dialog .d create "Couldn't create {$ct}: $msg" warning 0 Dismiss exit 1 } } \ else \ { exit 1 } new_file $f } }