Index: trunk/tcl/install
===================================================================
--- trunk/tcl/install	(revision 1)
+++ trunk/tcl/install	(revision 6)
@@ -40,6 +40,4 @@
 set targetdir [file join $dir $package$version]
 
-mkindex scripts
-
 if {![file exists $targetdir]} {
     puts "making directory $targetdir..."
@@ -47,5 +45,25 @@
 }
 
-foreach file [find .] {
+set origdir [pwd]
+foreach context {. ../gui} {
+    cd $context
+
+    foreach file [find .] {
+        set target [file join $targetdir $file]
+        if {[file isdirectory $file]} {
+            puts "making directory $target..."
+            catch {file mkdir $target}
+            file attributes $target -permissions ugo+rx
+        } else {
+            puts "installing $target..."
+            file copy -force $file $target
+            file attributes $target -permissions ugo+r
+        }
+    }
+}
+
+cd ..
+catch {file mkdir [file join $targetdir lib]}
+foreach file [find ./lib] {
     set target [file join $targetdir $file]
     if {[file isdirectory $file]} {
@@ -60,4 +78,6 @@
 }
 
+cd $origdir
+
 set fid [open [file join $targetdir pkgIndex.tcl] w]
 puts $fid "# Tcl package index file"
@@ -69,3 +89,5 @@
 close $fid
 
+mkindex [file join $targetdir scripts]
+
 puts "== $package-$version INSTALLED"
Index: trunk/tcl/scripts/library.tcl
===================================================================
--- trunk/tcl/scripts/library.tcl	(revision 1)
+++ trunk/tcl/scripts/library.tcl	(revision 6)
@@ -12,8 +12,8 @@
 
 namespace eval Rappture { # forward declaration }
-namespace eval Rappture::Library { # forward declaration }
-
-# ----------------------------------------------------------------------
-# USAGE: open ?-std? <file>
+
+# ----------------------------------------------------------------------
+# USAGE: library ?-std? <file>
+# USAGE: library isvalid <object>
 #
 # Used to open a <file> containing an XML description of tool
@@ -23,6 +23,22 @@
 # If the -std flag is included, then the file is treated as the
 # name of a standard file, which is part of the Rappture installation.
-# ----------------------------------------------------------------------
-proc Rappture::Library::open {args} {
+#
+# The isvalid operation checks an <object> to see if it is a valid
+# library object.  Returns 1 if so, and 0 otherwise.
+# ----------------------------------------------------------------------
+proc Rappture::library {args} {
+    # handle the isvalid operation...
+    if {[llength $args] > 1 && [lindex $args 0] == "isvalid"} {
+        if {[llength $args] != 2} {
+            error "wrong # args: should be \"library isvalid object\""
+        }
+        set obj [lindex $args 1]
+        if {[catch {$obj isa ::Rappture::LibraryObj} valid] == 0 && $valid} {
+            return 1
+        }
+        return 0
+    }
+
+    # handle the open operation...
     set stdfile 0
     while {[llength $args] > 1} {
@@ -51,27 +67,19 @@
 
 # ----------------------------------------------------------------------
-# USAGE: valid <obj>
-#
-# Checks to see if the given object is a valid Library.  Returns 1
-# if so, and 0 otherwise.
-# ----------------------------------------------------------------------
-proc Rappture::Library::valid {obj} {
-    if {[catch {$obj isa ::Rappture::LibraryObj} valid] == 0 && $valid} {
-        return 1
-    }
-    return 0
-}
-
-# ----------------------------------------------------------------------
 itcl::class Rappture::LibraryObj {
     constructor {info} { # defined below }
     destructor { # defined below }
 
-    public method get {args}
+    public method element {args}
+    public method children {args}
+    public method get {{path ""}}
     public method put {args}
+    public method remove {{path ""}}
     public method xml {}
 
     protected method find {path}
     protected method path2list {path}
+    protected method node2name {node}
+    protected method node2comp {node}
 
     private variable _root 0       ;# non-zero => this obj owns document
@@ -107,22 +115,31 @@
 
 # ----------------------------------------------------------------------
-# USAGE: get ?-exists|-object|-type|-info|-count|-children? ?<path>?
-#
-# Searches the DOM inside this object for the information on the
-# requested <path>.  By default, it returns the -info associated
-# with the path.  The other flags can be used to query other
-# aspects of the information at the requested node.
-# ----------------------------------------------------------------------
-itcl::body Rappture::LibraryObj::get {args} {
-    set format -info
-    while {[llength $args] > 0} {
+# USAGE: element ?-flavor <fval>? ?<path>?
+#
+# Clients use this to query a particular element within the entire
+# data structure.  The path is a string of the form
+# "structure.box(source).corner".  This example represents the tag
+# <corner> within a tag <box id="source"> within a tag <structure>,
+# which must be found at the top level within this document.
+#
+# By default, this method returns the component name "type(id)".
+# This is changed by setting the -flavor argument to "id" (for name
+# of the tail element), to "type" (for the type of the tail element),
+# to "object" (for an object representing the DOM node referenced by
+# the path.
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::element {args} {
+    array set params {
+        -flavor component
+    }
+    while {[llength $args] > 1} {
         set first [lindex $args 0]
         if {[string index $first 0] == "-"} {
-            set choices {-exists -object -type -info -count -children}
+            set choices [array names params]
             if {[lsearch $choices $first] < 0} {
                 error "bad option \"$first\": should be [join [lsort $choices] {, }]"
             }
-            set format $first
-            set args [lrange $args 1 end]
+            set params($first) [lindex $args 1]
+            set args [lrange $args 2 end]
         } else {
             break
@@ -130,106 +147,218 @@
     }
     if {[llength $args] > 1} {
-        error "wrong # args: should be \"get ?-exists? ?-object? ?-type? ?-info? ?-count? ?-children? ?path?\""
+        error "wrong # args: should be \"element ?-flavor fval? ?path?\""
     }
     set path [lindex $args 0]
 
     set node [find $path]
-
-    switch -- $format {
-        -exists {
-            if {$node != ""} {
-                return 1
-            }
-            return 0
-        }
-        -object {
-            if {$node != ""} {
-                return [::Rappture::LibraryObj ::#auto $node]
-            }
-            return ""
-        }
-        -info {
-            if {$node != ""} {
-                return [string trim [$node text]]
-            }
-            return ""
-        }
-        -type   {
-            if {$node != ""} {
-                return [$node nodeName]
-            }
-        }
-        -count {
-            if {$node == ""} {
-                return ""
-            }
-            set node [$node parent]
-            set type [lindex [path2list $path] end]
-            set nlist [$node getElementsByTagName $type]
-            return [llength $nlist]
-        }
-        -children {
-            if {$node == ""} {
-                return ""
-            }
-            set rlist ""
-            set nlist [$node childNodes]
-            foreach n $nlist {
-                set type [$n nodeName]
-                if {[regexp {^#} $type]} {
-                    continue
-                }
-                if {![info exists count($type)]} {
-                    set count($type) 0
-                    lappend rlist $type
-                } else {
-                    lappend rlist "$type[incr count($type)]"
-                }
-            }
-            return $rlist
-        }
-    }
-    return ""
-}
-
-# ----------------------------------------------------------------------
-# USAGE: put <path> ?-text|-object? <string>
-#
-# Inserts information into the DOM represented by this object.
-# The <path> is a path to the insertion point, which uses a syntax
-# similar to the "get" method.  The <string> being inserted can either
-# be ordinary text, or another LibraryObj object.
-# ----------------------------------------------------------------------
-itcl::body Rappture::LibraryObj::put {args} {
-    set what "-text"
-    set path [lindex $args 0]
-    set args [lrange $args 1 end]
+    if {$node == ""} {
+        return ""
+    }
+
+    switch -- $params(-flavor) {
+      object {
+          return [::Rappture::LibraryObj ::#auto $node]
+      }
+      component {
+          return [node2comp $node]
+      }
+      id {
+          return [node2name $node]
+      }
+      type {
+          return [$node nodeName]
+      }
+      default {
+          error "bad flavor \"$params(-flavor)\": should be object, id, type, component"
+      }
+    }
+}
+
+# ----------------------------------------------------------------------
+# USAGE: children ?-flavor <fval>? ?-type <name>? ?<path>?
+#
+# Clients use this to query the children of a particular element
+# within the entire data structure.  This is just like the "element"
+# method, but it returns the children of the element instead of the
+# element itself.  If the optional -type argument is specified, then
+# the return list is restricted to children of the specified type.
+#
+# By default, this method returns a list of component names "type(id)".
+# This is changed by setting the -flavor argument to "id" (for tail
+# names of all children), to "type" (for the types of all children),
+# to "object" (for a list of objects representing the DOM nodes for
+# all children).
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::children {args} {
+    array set params {
+        -flavor component
+        -type ""
+    }
     while {[llength $args] > 1} {
         set first [lindex $args 0]
-        set args [lrange $args 1 end]
-        if {$first != "-text" && $first != "-object"} {
-            error "bad option \"$first\": should be -object, -text"
-        }
-        set what $first
-    }
-    if {[llength $args] != 1} {
-        error "wrong # args: should be \"put path ?-text? ?-object? string\""
-    }
-    set str [lindex $args 0]
-
-    switch -- $what {
-        -text {
-            set node [find -create $path]
-            foreach n [$node childNodes] {
-                if {[$n nodeType] == "TEXT_NODE"} {
-                    $n delete
-                }
-            }
-            set n [$_document createText $str]
-            $node appendChild $n
-        }
-        -object {
-            error "not yet implemented"
-        }
+        if {[string index $first 0] == "-"} {
+            set choices [array names params]
+            if {[lsearch $choices $first] < 0} {
+                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
+            }
+            set params($first) [lindex $args 1]
+            set args [lrange $args 2 end]
+        } else {
+            break
+        }
+    }
+    if {[llength $args] > 1} {
+        error "wrong # args: should be \"children ?-flavor fval? ?-type name? ?path?\""
+    }
+    set path [lindex $args 0]
+
+    set node [find $path]
+    if {$node == ""} {
+        return ""
+    }
+
+    set nlist ""
+    foreach n [$node childNodes] {
+        set type [$n nodeName]
+        if {[regexp {^#} $type]} {
+            continue
+        }
+        if {$params(-type) != "" && $params(-type) != $type} {
+            continue
+        }
+        lappend nlist $n
+    }
+
+    set rlist ""
+    switch -- $params(-flavor) {
+      object {
+          foreach n $nlist {
+              lappend rlist [::Rappture::LibraryObj ::#auto $n]
+          }
+      }
+      component {
+          foreach n $nlist {
+              lappend rlist [node2comp $n]
+          }
+      }
+      id {
+          foreach n $nlist {
+              lappend rlist [node2name $n]
+          }
+      }
+      type {
+          foreach n $nlist {
+              lappend rlist [$n nodeName]
+          }
+      }
+      default {
+          error "bad flavor \"$params(-flavor)\": should be object, id, type, component"
+      }
+    }
+    return $rlist
+}
+
+# ----------------------------------------------------------------------
+# USAGE: get ?<path>?
+#
+# Clients use this to query the value of a node.  If the path is not
+# specified, it returns the value associated with the root node.
+# Otherwise, it returns the value for the element specified by the
+# path.
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::get {{path ""}} {
+    set node [find $path]
+    if {$node == ""} {
+        return ""
+    }
+    return [string trim [$node text]]
+}
+
+# ----------------------------------------------------------------------
+# USAGE: put ?-append yes? ?-id num? ?<path>? <string>
+#
+# Clients use this to set the value of a node.  If the path is not
+# specified, it sets the value for the root node.  Otherwise, it sets
+# the value for the element specified by the path.  If the value is a
+# string, then it is treated as the text within the tag at the tail
+# of the path.  If it is a DOM node or a library, then it is inserted
+# into the tree at the specified path.
+#
+# If the optional id is specified, then it sets the identifier for
+# the tag at the tail of the path.  If the optional append flag is
+# specified, then the value is appended to the current value.
+# Otherwise, the value replaces the current value.
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::put {args} {
+    array set params {
+        -id ""
+        -append no
+    }
+    while {[llength $args] > 1} {
+        set first [lindex $args 0]
+        if {[string index $first 0] == "-"} {
+            set choices [array names params]
+            if {[lsearch $choices $first] < 0} {
+                error "bad option \"$first\": should be [join [lsort $choices] {, }]"
+            }
+            set params($first) [lindex $args 1]
+            set args [lrange $args 2 end]
+        } else {
+            break
+        }
+    }
+    if {[llength $args] > 2} {
+        error "wrong # args: should be \"put ?-append bval? ?-id num? ?path? string\""
+    }
+    if {[llength $args] == 2} {
+        set path [lindex $args 0]
+        set str [lindex $args 1]
+    } else {
+        set path ""
+        set str [lindex $args 0]
+    }
+    set node [find -create $path]
+
+    #
+    # Clean up any nodes that don't belong.  If we're appending
+    # the value, then clean up only child <tag> nodes.  Otherwise,
+    # clean up all nodes.
+    #
+    set nlist ""
+    if {$params(-append)} {
+        foreach n [$node childNodes] {
+            if {[$n nodeType] != "TEXT_NODE"} {
+                lappend nlist $n
+            }
+        }
+    } else {
+        set nlist [$node childNodes]
+    }
+    foreach n $nlist {
+        $n delete
+    }
+
+    if {[Rappture::library isvalid $str]} {
+        error "not yet implemented"
+    } else {
+        set n [$_document createText $str]
+        $node appendChild $n
+        if {"" != $params(-id)} {
+            $node setAttribute id $params(-id)
+        }
+    }
+    return ""
+}
+
+# ----------------------------------------------------------------------
+# USAGE: remove ?<path>?
+#
+# Clients use this to remove the specified node.  Removes the node
+# from the tree.
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::remove {{path ""}} {
+    set node [find $path]
+    if {$node != ""} {
+        $node delete
     }
 }
@@ -248,7 +377,22 @@
 # USAGE: find ?-create? <path>
 #
-# Searches from the starting node for this object according to the
-# given <path>.  Returns the node found at the end of the path,
-# or "" if the node was not found.
+# Used internally to find a particular element within the root node
+# according to the path, which is a string of the form
+# "typeNN(id).typeNN(id). ...", where each "type" is a tag <type>;
+# if the optional NN is specified, it indicates an index for the
+# <type> tag within its parent; if the optional (id) part is included,
+# it indicates a tag of the form <type id="id">.
+#
+# By default, it looks for an element along the path and returns None
+# if not found.  If the create flag is set, it creates various elements
+# along the path as it goes.  This is useful for "put" operations.
+#
+# If you include "#" instead of a specific number, a node will be
+# created automatically with a new number.  For example, the path
+# "foo.bar#" called the first time will create "foo.bar", the second
+# time "foo.bar1", the third time "foo.bar2" and so forth.
+#
+# Returns an object representing the element indicated by the path,
+# or "" if the path is not found.
 # ----------------------------------------------------------------------
 itcl::body Rappture::LibraryObj::find {args} {
@@ -266,20 +410,27 @@
         error "wrong # args: should be \"find ?-create? path\""
     }
-    set path [path2list [lindex $args 0]]
+    set path [lindex $args 0]
+
+    if {$path == ""} {
+        return $_node
+    }
+    set path [path2list $path]
 
     #
     # Follow the given path and look for all of the parts.
     #
-    set nnum 1
     set lastnode $_node
     set node $lastnode
     foreach part $path {
-        if {[regexp {^([a-zA-Z_]+)([0-9]*)$} $part match type index]
-              && ($index != "" || [$node getElementsByTagName $type] != "")} {
-            #
-            # If the name is like "type2", then look for elements with
-            # the type name and return the one with the given index.
-            # If the name is like "type", then assume the index is 0.
-            #
+        if {![regexp {^(([a-zA-Z_]+#?)([0-9]*))?(\(([^\)]+)\))?$} $part \
+               match dummy type index dummy name]} {
+            error "bad path component \"$part\""
+        }
+        #
+        # If the name is like "type2", then look for elements with
+        # the type name and return the one with the given index.
+        # If the name is like "type", then assume the index is 0.
+        #
+        if {$name == ""} {
             if {$index == ""} {
                 set index 0
@@ -287,13 +438,19 @@
             set nlist [$node getElementsByTagName $type]
             set node [lindex $nlist $index]
-        } elseif {[regexp {^([a-zA-Z_]+)\(([^\)]*)\)$} $part match type name]} {
+        } else {
             #
-            # If the name is like "type(name)", then look for elements
+            # If the name is like "type(id)", then look for elements
             # that match the type and see if one has the requested name.
+            # if the name is like "(id)", then look for any elements
+            # with the requested name.
             #
-            set nlist [$node getElementsByTagName $type]
+            if {$type != ""} {
+                set nlist [$node getElementsByTagName $type]
+            } else {
+                set nlist [$node childNodes]
+            }
             set found 0
             foreach n $nlist {
-                if {[catch {$n getAttribute name} tag]} { set tag "" }
+                if {[catch {$n getAttribute id} tag]} { set tag "" }
                 if {$tag == $name} {
                     set found 1
@@ -302,20 +459,4 @@
             }
             set node [expr {($found) ? $n : ""}]
-        } else {
-            #
-            # Otherwise, the name might be something like "name".
-            # Scan through all elements and see if any has the
-            # matching name.
-            #
-            set nlist [$node childNodes]
-            set found 0
-            foreach n $nlist {
-                if {[catch {$n getAttribute name} tag]} { set tag "" }
-                if {$tag == $part} {
-                    set found 1
-                    break
-                }
-            }
-            set node [expr {($found) ? $n : ""}]
         }
 
@@ -324,17 +465,44 @@
                 return ""
             }
+
+            #
+            # If the "create" flag is set, then create a node
+            # with the specified "type(id)" and continue on.
+            # If the type is "type#", then create a node with
+            # an automatic number.
+            #
             if {![regexp {^([^\(]+)\(([^\)]+)\)$} $part match type name]} {
                 set type $part
                 set name ""
             }
-            set node [$_document createElement $type]
-            $lastnode appendChild $node
-
+
+            if {[string match *# $type]} {
+                set type [string trimright $type #]
+                set node [$_document createElement $type]
+
+                # find the last node of same type and append there
+                set pos ""
+                foreach n [$lastnode childNodes] {
+                    if {[$n nodeName] == $type} {
+                        set pos $n
+                    }
+                }
+                if {$pos != ""} {
+                    set pos [$pos nextSibling]
+                }
+                if {$pos != ""} {
+                    $lastnode insertBefore $node $pos
+                } else {
+                    $lastnode appendChild $node
+                }
+            } else {
+                set node [$_document createElement $type]
+                $lastnode appendChild $node
+            }
             if {"" != $name} {
-                $node setAttribute name $name
+                $node setAttribute id $name
             }
         }
         set lastnode $node
-        incr nnum
     }
     return $node
@@ -366,2 +534,57 @@
     return $path
 }
+
+# ----------------------------------------------------------------------
+# USAGE: node2name <node>
+#
+# Used internally to create a name for the specified node.  If the
+# node doesn't have a specific name ("id" attribute) then a name of
+# the form "type123" is constructed.
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::node2name {node} {
+    if {[catch {$node getAttribute id} name]} { set name "" }
+    if {$name == ""} {
+        set pnode [$node parentNode]
+        if {$pnode == ""} {
+            return ""
+        }
+        set type [$node nodeName]
+        set siblings [$pnode getElementsByTagName $type]
+        set index [lsearch $siblings $node]
+        if {$index == 0} {
+            set name $type
+        } else {
+            set name "$type$index"
+        }
+    }
+    return $name
+}
+
+# ----------------------------------------------------------------------
+# USAGE: node2comp <node>
+#
+# Used internally to create a path component name for the specified
+# node.  A path component name has the form "type(id)" or just
+# "type##" if the node doesn't have a name.  This name can be used
+# in a path to uniquely address the component.
+# ----------------------------------------------------------------------
+itcl::body Rappture::LibraryObj::node2comp {node} {
+    set type [$node nodeName]
+    if {[catch {$node getAttribute id} name]} { set name "" }
+    if {$name == ""} {
+        set pnode [$node parentNode]
+        if {$pnode == ""} {
+            return ""
+        }
+        set siblings [$pnode getElementsByTagName $type]
+        set index [lsearch $siblings $node]
+        if {$index == 0} {
+            set name $type
+        } else {
+            set name "$type$index"
+        }
+    } else {
+        set name "${type}($name)"
+    }
+    return $name
+}
