# ACTIVESTATE TEAPOT-PKG BEGIN TM -*- tcl -*- # -- Tcl Module # @@ Meta Begin # Package menubar::tree 0.5 # Meta as::build::date 2015-05-26 # Meta as::origin http://sourceforge.net/projects/tcllib # Meta license BSD # Meta platform tcl # Meta require TclOO # Meta require menubar::node # @@ Meta End # ACTIVESTATE TEAPOT-PKG BEGIN DECLARE package provide menubar::tree 0.5 # ACTIVESTATE TEAPOT-PKG END DECLARE # ACTIVESTATE TEAPOT-PKG END TM # tree.tcl -- # # Package that defines the menubar::Tree class. This class is a # privite class used by the menubar class. # # Copyright (c) 2009 Tom Krehbiel # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # RCS: @(#) $Id: tree.tcl,v 1.5 2010/01/06 20:55:54 tomk Exp $ package require TclOO package require menubar::node package provide menubar::tree 0.5 # -------------------------------------------------- # # menubar::Tree class - used by menubar class # # -------------------------------------------------- # -- # # nid - integer value used to create unique node names # root - name of tree's root node # nodes - index of node names and node instances # oo::class create ::menubar::tree { self export varname constructor { } { variable root variable nodes my eval upvar [[self class] varname nid] nid set nid 0 set root "root" set nodes [dict create "root" [::menubar::node new ""]] } destructor { variable nodes dict for {name node} ${nodes} { ${node} destroy } } ##### PRIVITE ############################## # -- # used by debugging utility method DumpSubtree { parent {indent 0} } { set pnode [my Name2Node ${parent}] puts "[format "%-12s" ${pnode}]- [string repeat { } ${indent}]${parent}" incr indent foreach child [${pnode} children] { my DumpSubtree [my Node2Name ${child}] ${indent} } } # -- # check args for a node that exists and return its name # else return "" method NotUsed { args } { variable nodes foreach name ${args} { if { [dict exists ${nodes} ${name}] } { return ${name} } } return "" } # -- # return a node instance given a node name method Name2Node { name } { variable nodes return [dict get ${nodes} ${name}] } # -- # return a node name given a node instance method Node2Name { node } { variable nodes dict for {name node} [dict filter ${nodes} value ${node}] { return ${name} } error "node (${node}) - not found" } # -- # return a list of node instances given a list of node names method Names2NodeList { args } { set nlist {} foreach name ${args} { lappend nlist [my Name2Node ${name}] } return ${nlist} } # -- # return a list of node names given a list of node instances method Nodes2NameList { args } { set nlist {} foreach node ${args} { lappend nlist [my Node2Name ${node}] } return ${nlist} } # -- # return the list of all nodes below parent node # optionaly filter nodes useing procedure 'filter' method GetSubtree { parent {filter ""} } { variable nodes set pnode [my Name2Node ${parent}] set children [my Nodes2NameList {*}[${pnode} children]] set subtree "" foreach child ${children} { if { ${filter} eq "" || [eval [list ${filter} [self object] ${child}]] == 0 } { lappend subtree ${child} lappend subtree {*}[my GetSubtree ${child} ${filter}] } } return ${subtree} } # -- # completely delete one node method DeleteNode { name } { variable root variable nodes set node [my Name2Node ${name}] # delete node from index set nodes [dict remove ${nodes} ${name}] # create a new root node if it was deleted if { ${name} eq ${root} } { dict set nodes ${name} [::menubar::node new ""] } ${node} destroy } # -- # replace the child entry for 'name' in its parent # with 0 or more new children method ReplaceParentLink { name args } { set cnode [my Name2Node ${name}] set pnode [${cnode} parent] if { ${pnode} eq "" } { return } set children [${pnode} children] set idx [lsearch -exact ${children} ${cnode}] if { ${idx} < 0 } { error "node (${name}) - not found" } if { [llength ${args}] == 0 } { set children [lreplace ${children} ${idx} ${idx}] } else { set nlist [my Names2NodeList {*}${args}] set children [lreplace ${children} ${idx} ${idx} {*}${nlist}] } ${pnode} children ${children} -force } # -- # Serialize a node and add it to stream. # # The result is a 3 element list haveing the following entries. # # 1) node name # 2) the node's attributes in dictionary form # 3) a recursive serialization of all children of the node # method SerializeNode { stream name {isroot 0}} { variable root variable nodes # serialize the children set children {} foreach child [my children ${name}] { lappend children {*}[my SerializeNode ${stream} ${child}] } set node [my Name2Node ${name}] lappend stream ${name} [${node} attrs.filter] ${children} return ${stream} } # -- # Unlink a list of nodes from their parents. Note that a node # may be in the subtree of a node that is being unlinked. method UnlinkNodes { args } { set notfound [my exists {*}${args}] if { ${notfound} ne "" } { error "node (${notfound}) - not found" } # Break the links to the parents foreach name ${args} { my ReplaceParentLink ${name} set pnode [my Name2Node ${name}] ${pnode} parent "" } } # -- Pstream # Pretty print a node from a serialization stream. method Pstream { name attrs children indent } { set pad [string repeat " " ${indent}] puts "${pad}${name}" puts "${pad} ${attrs}" incr indent foreach {n a c} ${children} { my Pstream ${n} ${a} ${c} ${indent} } } # -- # pnode - parent node # name - name of new node # attrs - attribure dict for new node # children - recursive list of child node serializations method DeserializeNode { pnode name attrs children } { variable nodes # create the a node and set it's parent set cnode [::menubar::node new ${pnode}] # add the node to the index dict set nodes ${name} ${cnode} # set the node's attributes ${cnode} attrs ${attrs} -force # create all the children for the node set cnodes {} foreach {n a c} ${children} { lappend cnodes [my DeserializeNode ${cnode} ${n} ${a} ${c}] } ${cnode} children ${cnodes} -force return ${cnode} } ##### PUBLIC ############################## # -- # method ancestors { child } { if { [my exists ${child}] ne "" } { error "node (${child}) - not found" } set ancestors {} while { true } { set ancestor [my parent ${child}] if { ${ancestor} eq "" } { break } else { lappend ancestors ${ancestor} set child ${ancestor} } } return ${ancestors} } # -- # method children { parent } { variable nodes if { [my exists ${parent}] ne "" } { error "node (${parent}) - not found" } set pnode [my Name2Node ${parent}] set children [${pnode} children] return [my Nodes2NameList {*}${children}] } # -- # Remove a node from the tree and move its # children into the parent. Ignore cut on # the root node. method cut { name {opt ""} } { variable nodes if { ${name} eq [my rootname] } { return } if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } # get the children for the node set children [my children ${name}] # replace the node with its childer in the parent my ReplaceParentLink ${name} {*}${children} if { ${opt} eq "-delete" } { # delete the node set node [my Name2Node ${name}] dict unset nodes ${name} ${node} destroy } return } # -- # method delete { args } { set notfound [my exists {*}${args}] if { ${notfound} ne "" } { error "node (${notfound}) - not found" } # Remove all the subtree nodes. # This code accounts for the possibility that # one of the args is in the subtree of another arg. set names {} foreach name ${args} { lappend names {*}[my descendants ${name}] } foreach name [lsort -unique ${names}] { my DeleteNode ${name} } # Now remove the nodes themselves and their child # entry in their parent foreach name ${args} { my ReplaceParentLink ${name} my DeleteNode ${name} } return } # -- # method depth { name } { return [llength [my ancestors ${name}]] } # -- # method descendants { parent {opt ""} {arg ""} } { variable nodes if { [my exists ${parent}] ne "" } { error "node (${parent}) - not found" } if { ${opt} eq "-filter" } { set filter ${arg} return [my GetSubtree ${parent} ${filter}] } else { return [my GetSubtree ${parent}] } } # -- # Replace the attribute and subtree definitions of node # 'lname' with the definitions found in 'stream'. The 'lname' # node must be a leaf node unless the '-force' option is is # used. method deserialize { lname stream {opt ""} } { variable root variable nodes if { [my exists ${lname}] ne "" } { error "node (${lname}) - not found" } if { ${opt} eq "-force" } { # force lname to be a leaf set parent [my parent ${lname}] my delete ${lname} set node [::menubar::node new [my Name2Node ${parent}]] dict set nodes ${lname} ${node} } if { ![my isleaf ${lname}] } { error "node (${lname}) - is not a leaf node" } # get the leaf node set lnode [my Name2Node ${lname}] # get the root of the serialization lassign ${stream} rname attrs children # put attributes in the leaf node ${lnode} attrs ${attrs} -force # deserialize all the children into the leaf node set cnodes {} foreach {n a c} ${children} { lappend cnodes [my DeserializeNode ${lnode} ${n} ${a} ${c}] } ${lnode} children ${cnodes} -force return } # -- # return "" if all exist else return name that isn't found method exists { args } { variable nodes foreach name ${args} { if { ![dict exists ${nodes} ${name}] } { return ${name} } } return "" } # -- # method index { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set cnode [my Name2Node ${name}] set pnode [${cnode} parent] set children [${pnode} children] return [lsearch -exact ${children} ${cnode}] } # -- # method insert { parent index args } { variable nid variable nodes if { [llength ${args}] == 0 } { incr nid set args "node${nid}" } else { if { ${parent} in ${args} } { error "parent (${parent}) - found in insert list" } } set pnode [my Name2Node ${parent}] set nlist "" foreach name ${args} { if { [my exists ${name}] ne "" } { # create a new child that references the parent set node [::menubar::node new ${pnode}] # add the node to the index dict set nodes ${name} ${node} } else { # child already exists so it must be cut from its # current location my UnlinkNodes ${name} set node [my Name2Node ${name}] ${node} parent ${pnode} } lappend nlist ${node} } # insert the list of child nodes into the # parent's list of children if { [llength ${nlist}] > 0 } { ${pnode} insert ${index} {*}${nlist} } return ${args} } # -- # method isleaf { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [expr ( [llength [${node} children]] > 0 ) ? 0 : 1] } # -- # method keys { {name ""} {gpat ""} } { if { ${name} eq "" } { set nlist [my nodes] } else { set nlist ${name} } set result {} foreach name ${nlist} { set node [my Name2Node ${name}] if { ${gpat} eq "" } { lappend result {*}[${node} attr.keys] } else { set d [dict create {*}[${node} attrs.filter ${gpat}]] lappend result {*}[dict keys ${d}] } } return [lsort -unique ${result}] } # -- # method key.append { name key value } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] ${node} attr.append ${key} ${value} return } # -- # method key.exists { name key } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [${node} attr.exists ${key}] } # -- # method key.get { name key } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [${node} attr.get ${key}] } # -- # method key.getall { name {globpat ""} } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [${node} attrs.filter ${globpat}] } # -- # method key.lappend { name key value } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] ${node} attr.lappend ${key} ${value} return [${node} attr.get ${key}] } # -- # method key.nodes { key {flag ""} {arg ""} } { set result {} set names [my nodes] switch -exact ${flag} { "-nodes" { set names ${arg} } "-glob" { set nlist {} set gpat ${arg} foreach name ${names} { if { [string match ${gpat} ${name}] == 1 } { lappend nlist ${name} } } set names ${nlist} } "-regexp" { set nlist {} set rpat ${arg} foreach name ${names} { if { [regexp ${rpat} ${name}] == 1 } { lappend nlist ${name} } } set names ${nlist} } default { }} foreach name ${names} { if { [my key.exists ${name} ${key}] } { lappend result ${name} [my key.get ${name} ${key}] } } return ${result} } # -- # method key.set { name key args } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] if { [llength ${args}] == 1 } { ${node} attr.set ${key} [lindex ${args} 0] } return [${node} attr.get ${key}] } # -- # method key.unset { name key } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] ${node} attr.unset ${key} } # -- # method leaves { } { set leaves {} foreach name [my nodes] { if { [my isleaf ${name}] == 1 } { lappend leaves ${name} } } return ${leaves} } # -- # method move { parent index args } { set pnode [my Name2Node ${parent}] # Make sure the list of nodes doesn't contain an # ancestor of the parent. If this were allowed the # subtree would become disconnected. set alist [my ancestors ${parent}] foreach name ${args} { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } if { ${name} in ${alist} } { error "node (${name}) is an ancestor of node (${parent})" } } # unlink the nodes set nlist {} foreach name ${args} { my UnlinkNodes ${name} set node [my Name2Node ${name}] ${node} parent ${pnode} lappend nlist ${node} } # link the nodes into the parent at location 'index' set children [${pnode} children] ${pnode} children [linsert ${children} ${index} {*}${nlist}] return } # -- # method next { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set cnode [my Name2Node ${name}] set pnode [${cnode} parent] set children [${pnode} children] set idx [lsearch -exact ${children} ${cnode}] incr idx if { ${idx} < [llength ${children}] } { return [my Node2Name [lindex ${children} ${idx}]] } else { return "" } } # -- # method numchildren { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set node [my Name2Node ${name}] return [llength [${node} children]] } # -- # method nodes { } { variable nodes return [dict keys ${nodes}] } # -- # method parent { child } { variable nodes if { [my exists ${child}] ne "" } { error "node (${child}) - not found" } set cnode [my Name2Node ${child}] set pnode [${cnode} parent] if { ${pnode} eq "" } { return "" } else { return [my Node2Name ${pnode}] } } # -- # method previous { name } { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } set cnode [my Name2Node ${name}] set pnode [${cnode} parent] set children [${pnode} children] set idx [lsearch -exact ${children} ${cnode}] incr idx -1 if { ${idx} >= 0 } { return [my Node2Name [lindex ${children} ${idx}]] } else { return "" } } # -- # method rename { from to } { variable root variable nodes if { ![dict exists ${nodes} ${from}] } { error "node (${to}) - not found" } if { [dict exists ${nodes} ${to}] } { error "node (${to}) - already exists" } set node [dict get ${nodes} ${from}] set nodes [dict remove ${nodes} ${from}] dict set nodes ${to} ${node} if { ${from} eq ${root} } { set root ${to} } return } # -- # method rootname { } { variable root return ${root} } # -- # Return a serialization of the subtree starting at 'name'. # # The result is a list containing three element. The elements # are (1) a node name (2) the node's attributes in dictionary # form (3) zero or more additional three element lists that # recursivly serialize the children of the node. # method serialize { name } { variable root variable nodes if { ${name} ne "root" && [my exists ${name}] ne "" } { error "node (${name}) - not found" } # create the null node set stream {} set stream [my SerializeNode ${stream} ${name} 1] return ${stream} } # -- # method size { {name ""} } { if { ${name} eq "" } { set name [my rootname] } else { if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } } return [llength [my descendants ${name}]] } # -- # method splice { parent from {to ""} {child ""} } { variable nid variable nodes if { ${parent} eq "root" } { set parent [my rootname] } else { if { [my exists ${parent}] ne "" } { error "node (${parent}) - not found" } } if { ${to} eq "" } { set to "end" } if { ${child} eq "" } { incr nid set child "node${nid}" } else { if { [my NotUsed ${child}] ne "" } { error "node (${child}) - already exists" } } # get the parent information set pnode [my Name2Node ${parent}] # create the new child set node [::menubar::node new ${pnode}] # add the node to the index dict set nodes ${child} ${node} # get the parents children set children [${pnode} children] # put the range of childern in the new node ${node} children [lrange ${children} ${from} ${to}] -force # remove the range of children from the parent and insert the new node ${pnode} children [lreplace ${children} ${from} ${to} ${node}] -force return ${child} } # -- # method swap { name1 name2 } { if { ${name1} eq ${name2} } { return } # make sure the nodes exist if { [my exists ${name1}] ne "" } { error "node (${name1}) - not found" } if { [my exists ${name2}] ne "" } { error "node (${name2}) - not found" } # make sure one node isn't in the the other node's subtree # (this also precludes a swap with 'root') set node1 [my Name2Node ${name1}] set node2 [my Name2Node ${name2}] if { [lsearch -exact [my descendants ${name1}] ${name2}] != -1 } { error "node (${name2}) in subtree of node (${name1})" } if { [lsearch -exact [my descendants ${name2}] ${name1}] != -1 } { error "node (${name1}) in subtree of node (${name2})" } # check to see if the nodes have a common parent set pnode1 [${node1} parent] set pnode2 [${node2} parent] if { ${pnode1} eq ${pnode2} } { # nodes have a common parent node set children [${pnode1} children] set idx1 [lsearch -exact ${children} ${node1}] set idx2 [lsearch -exact ${children} ${node2}] set children [lreplace ${children} ${idx1} ${idx1} ${node2}] set children [lreplace ${children} ${idx2} ${idx2} ${node1}] ${pnode1} children ${children} -force } else { # nodes have different parent nodes set children1 [${pnode1} children] set children2 [${pnode2} children] set idx1 [lsearch -exact ${children1} ${node1}] set idx2 [lsearch -exact ${children2} ${node2}] set children1 [lreplace ${children1} ${idx1} ${idx1} ${node2}] set children2 [lreplace ${children2} ${idx2} ${idx2} ${node1}] ${pnode1} children ${children1} -force ${pnode2} children ${children2} -force ${node1} parent ${pnode2} ${node2} parent ${pnode1} } return } ##### WALKPROC CODE (DEPTH FIRST) ############################ # -- # method DfsPreOrderWalk { name cmdprefix } { variable nodes if { [catch {${cmdprefix} [self object] ${name} "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my PreOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } return 0 } # -- # method DfsPostOrderWalk { name cmdprefix } { variable nodes variable nodes set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my PostOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } if { [catch {${cmdprefix} [self object] ${name} "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } return 0 } # -- # method DfsBothOrderWalk { name cmdprefix } { variable nodes if { [catch {${cmdprefix} [self object] ${name} "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my BothOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } if { [catch {${cmdprefix} [self object] ${name} "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } return 0 } # -- # method DfsInOrderWalk { name cmdprefix } { variable nodes set node [my Name2Node ${name}] for {set idx 0} { true } {incr idx} { if { ${idx} == 1 } { if { [catch {${cmdprefix} [self object] ${name} "visit"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } set children [my children ${name}] if { ${idx} >= [llength ${children}] } { break } set child [lindex [my children ${name}] ${idx}] if { [my InOrderWalk ${child} ${cmdprefix}] != 0 } { return 1 } } if { ${idx} == 0 } { if { [catch {${cmdprefix} [self object] ${name} "visit"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } return 0 } ##### WALKPROC CODE (BREADTH FIRST) ############################ # -- # This method takes as input a list of nodes (nlist) and returns # a new list that is the list of all children for the input list. method DecendOneLevelForward { nlist } { set result {} foreach node ${nlist} { lappend result {*}[${node} children] } return ${result} } # -- # This method takes as input a list of nodes (nlist) and returns # a new list that is the list of all children for the input list. method DecendOneLevelBackward { nlist } { set result {} foreach node ${nlist} { lappend result {*}[lreverse [${node} children]] } return ${result} } # -- # method BfsPreOrderWalk { nlist cmdprefix } { if { [llength ${nlist}] == 0 } { return 0 } foreach node ${nlist} { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } if { [my BfsPreOrderWalk [my DecendOneLevelForward ${nlist}] ${cmdprefix}] != 0 } { return 1 } return 0 } # -- # method BfsPostOrderWalk { nlist cmdprefix } { if { [llength ${nlist}] == 0 } { return 0 } if { [my BfsPostOrderWalk [my DecendOneLevelBackward ${nlist}] ${cmdprefix}] != 0 } { return 1 } foreach node ${nlist} { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } return 0 } # -- # method BfsBothOrderWalk { nlist cmdprefix } { if { [llength ${nlist}] == 0 } { return 0 } foreach node ${nlist} { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "enter"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } my BfsBothOrderWalk [my DecendOneLevelForward ${nlist}] ${cmdprefix} foreach node [lreverse ${nlist}] { if { [catch {${cmdprefix} [self object] [my Node2Name ${node}] "leave"} bool] || ${bool} != 0 } { #puts "bool: $bool" # shutdown the walk return 1 } } return 0 } # -- # method BfsInOrderWalk { } { error "unable to do a in-order breadth first walk" } # -- # method walkproc { name cmdprefix args } { set types {bfs dfs} set orders {pre post both in} set type "dfs" set order "pre" if { [my exists ${name}] ne "" } { error "node (${name}) - not found" } foreach {opt val} ${args} { switch -exact -- ${opt} { "-order" { if { ${val} ni ${orders} } { error "-order ${val} - must be oneof: [join ${orders} {, }]" } set order ${val} } "-type" { if { ${val} ni ${types} } { error "-type ${val} - must be oneof: [join ${types} {, }]" } set type ${val} } default { }} } if { ${type} eq "dfs" } { switch -exact -- ${order} { "post" { my DfsPostOrderWalk ${name} ${cmdprefix} } "both" { my DfsBothOrderWalk ${name} ${cmdprefix} } "in" { my DfsInOrderWalk ${name} ${cmdprefix} } "pre" - default { my DfsPreOrderWalk ${name} ${cmdprefix} }} } else { switch -exact -- ${order} { "post" { my BfsPostOrderWalk [my Name2Node ${name}] ${cmdprefix} } "both" { my BfsBothOrderWalk [my Name2Node ${name}] ${cmdprefix} } "in" { my BfsInOrderWalk } "pre" - default { my BfsPreOrderWalk [my Name2Node ${name}] ${cmdprefix} }} } return } }