Index: branches/blt4/examples/zoo/drawing/tool.xml
===================================================================
--- branches/blt4/examples/zoo/drawing/tool.xml	(revision 3091)
+++ branches/blt4/examples/zoo/drawing/tool.xml	(revision 3102)
@@ -87,26 +87,4 @@
   	  <dash>4 2</dash>
         </grid>
- 	<string>
-	  <about> 
-	    <label>String:</label>
-          </about>  
- 	  <coords>.1 1.1</coords>
-  	  <color>blue</color>
-  	  <font>Arial 9</font>
-  	  <anchor>nw</anchor>
-	  <default>value</default>
-        </string>
- 	<number>
-	  <about> 
-	    <label>Number:</label>
-          </about>  
- 	  <coords></coords>
-  	  <color>blue</color>
-  	  <font>Arial 12</font>
-  	  <anchor>n</anchor>
-	  <default></default>
-	  <min></min>
-	  <max></max>
-        </number>
     </components>
   </drawing>
Index: branches/blt4/gui/scripts/Makefile.in
===================================================================
--- branches/blt4/gui/scripts/Makefile.in	(revision 3091)
+++ branches/blt4/gui/scripts/Makefile.in	(revision 3102)
@@ -30,4 +30,5 @@
 		$(srcdir)/color.tcl \
 		$(srcdir)/combobox.tcl \
+		$(srcdir)/combochecks.tcl \
 		$(srcdir)/contourresult.tcl \
 		$(srcdir)/controlOwner.tcl \
@@ -43,8 +44,8 @@
 		$(srcdir)/dispatcher.tcl \
 		$(srcdir)/drawing.tcl \
-		$(srcdir)/drawingentry.tcl \
 		$(srcdir)/drawingcontrols.tcl \
 		$(srcdir)/drawingentry.tcl \
 		$(srcdir)/dropdown.tcl \
+		$(srcdir)/dropdownchecks.tcl \
 		$(srcdir)/dropdownlist.tcl \
 		$(srcdir)/editor.tcl \
Index: branches/blt4/gui/scripts/combochecks.tcl
===================================================================
--- branches/blt4/gui/scripts/combochecks.tcl	(revision 3102)
+++ branches/blt4/gui/scripts/combochecks.tcl	(revision 3102)
@@ -0,0 +1,250 @@
+# ----------------------------------------------------------------------
+#  COMPONENT: combochecks - like a combobox, but items with checkboxes
+#
+#  This widget looks a lot like a combobox, but has a drop-down list
+#  of checkbox items.  The list of checked items is shown in the
+#  entry area as a comma-separated list.
+# ======================================================================
+#  AUTHOR:  Michael McLennan, Purdue University
+#  Copyright (c) 2004-2012  Purdue Research Foundation
+#
+#  See the file "license.terms" for information on usage and
+#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# ======================================================================
+package require Itk
+package require BLT
+
+option add *Combochecks.borderWidth 2 widgetDefault
+option add *Combochecks.relief sunken widgetDefault
+option add *Combochecks.width 10 widgetDefault
+option add *Combochecks.textBackground white widgetDefault
+option add *Combochecks.textForeground black widgetDefault
+option add *Combochecks.disabledBackground white widgetDefault
+option add *Combochecks.disabledForeground gray widgetDefault
+option add *Combochecks.font -*-helvetica-medium-r-normal-*-12-* widgetDefault
+
+itcl::class Rappture::Combochecks {
+    inherit itk::Widget
+
+    itk_option define -state state State "normal"
+    itk_option define -width width Width 0
+    itk_option define -disabledbackground disabledBackground DisabledBackground ""
+    itk_option define -disabledforeground disabledForeground DisabledForeground ""
+
+    constructor {args} { # defined below }
+
+    public method value {args}
+    public method current {}
+    public method choices {option args}
+
+    protected method _entry {option}
+    protected method _fixDisplay {}
+    protected method _fixState {}
+
+    blt::bitmap define CombochecksArrow {
+        #define arrow_width 8
+        #define arrow_height 4
+        static unsigned char arrow_bits[] = {
+           0xfe, 0x7c, 0x38, 0x10};
+    }
+    private variable _value2label
+    private variable _label2value
+}
+                                                                                
+itk::usual Combochecks {
+    keep -cursor -font
+    keep -foreground -background
+    keep -textforeground -textbackground
+}
+
+# ----------------------------------------------------------------------
+# CONSTRUCTOR
+# ----------------------------------------------------------------------
+itcl::body Rappture::Combochecks::constructor {args} {
+    itk_option add hull.borderwidth hull.relief
+
+    itk_component add button {
+        button $itk_interior.btn -bitmap CombochecksArrow -padx 0 \
+            -borderwidth 1 -relief raised -highlightthickness 0
+    } {
+        usual
+        ignore -highlightthickness -highlightbackground -highlightcolor
+        ignore -borderwidth -relief
+    }
+    pack $itk_component(button) -side right -fill y
+
+    itk_component add entry {
+        entry $itk_interior.entry -borderwidth 0 -relief flat -state disabled
+    } {
+        usual
+        keep -width
+        rename -highlightbackground -textbackground textBackground Background
+        rename -background -textbackground textBackground Background
+        rename -foreground -textforeground textForeground Foreground
+        rename -disabledbackground -textbackground textBackground Background
+        rename -disabledforeground -textforeground textForeground Foreground
+        ignore -borderwidth -relief
+    }
+    pack $itk_component(entry) -side left -expand yes -fill both
+
+    bind $itk_component(entry) <KeyPress-Return> \
+        [itcl::code $this _entry apply]
+    bind $itk_component(entry) <ButtonPress> \
+        [itcl::code $this _entry click]
+
+    itk_component add ddlist {
+        Rappture::Dropdownchecks $itk_component(button).ddlist
+    }
+    bind $itk_component(ddlist) <<DropdownchecksSelect>> \
+        [itcl::code $this _fixDisplay]
+
+    $itk_component(button) configure -command \
+        [list $itk_component(ddlist) post $itk_component(hull) left]
+
+    eval itk_initialize $args
+}
+
+# ----------------------------------------------------------------------
+# USAGE: value ?<newval>?
+#
+# Clients use this to query/set the value for this widget.  With
+# no args, it returns the current value for the widget.  If the
+# <newval> is specified, it sets the value of the widget and
+# sends a <<Value>> event.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Combochecks::value {args} {
+    switch -- [llength $args] {
+        0 {
+            # return a list of checked optons
+            set max [$itk_component(ddlist) size]
+            set rlist ""
+            foreach val [$itk_component(ddlist) get -value] {
+                if {[$itk_component(ddlist) state $val]} {
+                    lappend rlist $val
+                }
+            }
+            return $rlist
+        }
+        1 {
+            set newval [lindex $args 0]
+
+            $itk_component(ddlist) reset
+            foreach part $newval {
+                $itk_component(ddlist) state $part 1
+            }
+            _fixDisplay
+
+            after 10 [list catch \
+                [list event generate $itk_component(hull) <<Value>>]]
+
+        }
+        default {
+            error "wrong # args: should be \"value ?newval?\""
+        }
+    }
+}
+
+# ----------------------------------------------------------------------
+# USAGE: choices insert <pos> ?<value1> <label1> ...?
+# USAGE: choices delete <first> ?<last>?
+# USAGE: choices get ?-value|-label|-both? ?<index>?
+# USAGE: choices state <value> ?on|off?
+# USAGE: choices index <value>
+#
+# Clients use this to manipulate the list of choices in the drop-down
+# list.  Each choice is represented by a (computer-friendly) value
+# and its corresponding (human-friendly) label.  The "get" option
+# returns information about options on the list, including the value,
+# the label, or both.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Combochecks::choices {option args} {
+    eval $itk_component(ddlist) $option $args
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _entry apply
+# USAGE: _entry click
+#
+# Used internally to handle the dropdown list for this combobox.  The
+# post/unpost options are invoked when the list is posted or unposted
+# to manage the relief of the controlling button.  The select option
+# is invoked whenever there is a selection from the list, to assign
+# the value back to the gauge.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Combochecks::_entry {option} {
+    switch -- $option {
+        apply {
+            if {$itk_option(-state) == "normal"} {
+                event generate $itk_component(hull) <<Value>>
+            }
+        }
+        click {
+            if {$itk_option(-state) == "normal"} {
+                $itk_component(button) configure -relief sunken
+                update idletasks; after 100
+                $itk_component(button) configure -relief raised
+
+                $itk_component(ddlist) post $itk_component(hull) left
+            }
+        }
+        default {
+            error "bad option \"$option\": should be apply, click"
+        }
+    }
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _fixDisplay
+#
+# Used internally to handle the dropdown list for this combobox.
+# Invoked whenever there is a selection from the list, to assign
+# the value back to the entry.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Combochecks::_fixDisplay {} {
+    $itk_component(entry) configure -state normal
+    $itk_component(entry) delete 0 end
+    $itk_component(entry) insert end [join [value] {, }]
+    $itk_component(entry) configure -state disabled
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _fixState
+#
+# Used internally to fix the widget state when the -state option
+# changes.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Combochecks::_fixState {} {
+    if {$itk_option(-state) == "normal"} {
+        $itk_component(button) configure -state normal
+        $itk_component(entry) configure \
+            -background $itk_option(-textbackground) \
+            -foreground $itk_option(-textforeground) \
+            -disabledbackground $itk_option(-textbackground) \
+            -disabledforeground $itk_option(-textforeground)
+    } else {
+        $itk_component(button) configure -state disabled
+        $itk_component(entry) configure \
+            -background $itk_option(-disabledbackground) \
+            -foreground $itk_option(-disabledforeground) \
+            -disabledbackground $itk_option(-disabledbackground) \
+            -disabledforeground $itk_option(-disabledforeground)
+    }
+
+    if {$itk_option(-state) != "normal"} {
+        # can't keep focus here -- move it along to the next widget
+        if {[focus] == $itk_component(entry)} {
+            focus [tk_focusNext [focus]]
+        }
+    }
+}
+
+# ----------------------------------------------------------------------
+# CONFIGURATION OPTION: -state
+# ----------------------------------------------------------------------
+itcl::configbody Rappture::Combochecks::state {
+    set valid {normal disabled}
+    if {[lsearch -exact $valid $itk_option(-state)] < 0} {
+        error "bad value \"$itk_option(-state)\": should be [join $valid {, }]"
+    }
+    _fixState
+}
Index: branches/blt4/gui/scripts/controls.tcl
===================================================================
--- branches/blt4/gui/scripts/controls.tcl	(revision 3091)
+++ branches/blt4/gui/scripts/controls.tcl	(revision 3102)
@@ -522,5 +522,5 @@
             set _name2info($name-label) $label
 
-            $_tabs insert end $label -fill both
+            $_tabs insert end $name -text $label -fill both
             incr gn
         }
Index: branches/blt4/gui/scripts/drawingentry.tcl
===================================================================
--- branches/blt4/gui/scripts/drawingentry.tcl	(revision 3091)
+++ branches/blt4/gui/scripts/drawingentry.tcl	(revision 3102)
@@ -27,5 +27,6 @@
     private variable _drawingWidth 0
     private variable _owner
-    private variable _parser ""
+    private variable _parser "";	# Slave interpreter where all 
+					# substituted variables are stored.
     private variable _path
     private variable _showing ""
@@ -40,6 +41,10 @@
     private variable _cursor ""
 
-    constructor {owner path args} { # defined below }
-
+    constructor {owner path args} { 
+	# defined below 
+    }
+    destructor {
+	# defined below
+    }
     public method value { args }
     public method label {}
@@ -48,4 +53,5 @@
     private method Activate { tag } 
     private method AdjustDrawingArea { xAspect yAspect } 
+    private method ControlValue {path {units ""}}
     private method Deactivate { tag } 
     private method Highlight { tag } 
@@ -69,5 +75,9 @@
     private method ScreenY { y } 
     private method XmlGet { path } 
-    private method Withdraw {} 
+    private method XmlGetSubst { path } 
+    private method Withdraw { cname } 
+    private method Hotspot { option cname item args } 
+    private method IsEnabled { path } 
+    private method NumControlsEnabled { cname } 
 }
 
@@ -99,8 +109,14 @@
     pack $itk_component(drawing) -expand yes -fill both
     bind $itk_component(drawing) <Configure> [itcl::code $this Redraw]
+    set _parser [interp create -safe]
     Redraw
     eval itk_initialize $args
 }
 
+itcl::body Rappture::DrawingEntry::destructor {} {
+    if { $_parser != "" } {
+	$_parser delete 
+    }
+}
 # ----------------------------------------------------------------------
 # USAGE: label
@@ -226,7 +242,7 @@
     }
     # Coords
-    set xcoords [XmlGet $cpath.xcoords]
+    set xcoords [XmlGetSubst $cpath.xcoords]
     set xcoords [string trim $xcoords]
-    set ycoords [XmlGet $cpath.ycoords]
+    set ycoords [XmlGetSubst $cpath.ycoords]
     set ycoords [string trim $ycoords]
     if { $ycoords == "" } {
@@ -275,5 +291,5 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	}
@@ -311,8 +327,8 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	} elseif { [string match "controls*" $attr] } {
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    lappend _cname2controls($cname) $value
 	    $_owner xml put $value.hide 1
@@ -320,5 +336,5 @@
     }
     # Coordinates
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [ScreenCoords $coords]
     if { $coords == "" } {
@@ -326,5 +342,5 @@
     } 
     set c $itk_component(drawing)
-    set img [Rappture::icon question_mark12]
+    set img [Rappture::icon hotspot_normal]
     foreach { x1 y1 } $coords break
     set id [$itk_component(drawing) create image $x1 $y1]
@@ -337,5 +353,7 @@
     $c bind $id <Leave> [itcl::code $this Deactivate $cname]
     #$c bind $id <ButtonPress-1> [itcl::code $this Depress $cname]
-    $c bind $id <ButtonRelease-1> [itcl::code $this Invoke $cname $x1 $y1]
+    set bbox [$c bbox $id]
+    set y1 [lindex $bbox 1]
+    $c bind $id <ButtonPress-1> [itcl::code $this Invoke $cname $x1 $y1]
 }
 
@@ -359,5 +377,5 @@
     # Coords
     set coords {}
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [string trim $coords]
     if { $coords == "" } {
@@ -371,5 +389,5 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	}
@@ -388,5 +406,5 @@
 	"outline"	"-outline"
 	"fill"		"-fill"
-	"linewidth"	"-linewidth"
+	"linewidth"	"-width"
     }
     #puts stderr "ParseOval owner=$_owner cpath=$cpath"
@@ -395,5 +413,5 @@
     array set options {
 	-fill blue
-	-linewidth 1 
+	-width 1 
 	-outline black
     }
@@ -401,5 +419,5 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	}
@@ -407,5 +425,5 @@
     # Coordinates
     set coords {}
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [string trim $coords]
     if { $coords == "" } {
@@ -413,6 +431,7 @@
     }
     foreach { x1 y1 x2 y2 } [ScreenCoords $coords] break
-    set id [eval $itk_component(drawing) create oval $coords]
+    set id [$itk_component(drawing) create oval $x1 $y1 $x2 $y2]
     set _cname2id($cname) $id
+    eval $itk_component(drawing) itemconfigure $id [array get options]
 }
 
@@ -432,9 +451,9 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	}
     }
-    set contents [XmlGet $cpath.contents]
+    set contents [XmlGetSubst $cpath.contents]
     set img ""
     if { [string compare -length 5 $contents "file:"] == 0 } {
@@ -453,9 +472,9 @@
     }
     # Coordinates
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [ScreenCoords $coords]
     if { [llength $coords] == 2 } {
 	foreach { x1 y1 } $coords break
-	set w [XmlGet $cpath.width]
+	set w [XmlGetSubst $cpath.width]
 	if { $w == "" || ![string is number $w] || $w <= 0.0 } {
 	    set width [expr [image width $img] / 4]
@@ -463,5 +482,5 @@
 	    set width [expr [ScreenX $w] - [ScreenX 0]]
 	}
-	set h [XmlGet $cpath.height]
+	set h [XmlGetSubst $cpath.height]
 	if { $h == "" || ![string is number $h] || $h <= 0.0 } {
 	    set height [expr [image height $img] / 4]
@@ -517,15 +536,14 @@
     array set attr2option {
 	"linewidth"	"-width"
-	"arrow"		"-arrow"
 	"color"		"-fill"
     }
     # Set default options first and then let tool.xml override them.
     array set options {
-	-arrow		none
-	-width		0
-	-fill		black
+	-width		1
+	-fill		blue
+	-outline	black
     }
     # Coords
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [string trim $coords]
     if { $coords == "" } {
@@ -542,5 +560,5 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	}
@@ -559,5 +577,6 @@
 	"outline"	"-outline"
 	"fill"		"-fill"
-	"linewidth"	"-linewidth"
+	"color"		"-fill"
+	"linewidth"	"-width"
     }
     #puts stderr "ParseRectangle owner=$_owner cpath=$cpath"
@@ -566,5 +585,5 @@
     array set options {
 	-fill blue
-	-linewidth 1 
+	-width 1 
 	-outline black
     }
@@ -572,10 +591,10 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    set value [XmlGetSubst $cpath.$attr]
 	    set options($option) $value
 	}
     }
     # Coordinates
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [string trim $coords]
     if { $coords == "" } {
@@ -583,6 +602,7 @@
     }
     foreach { x1 y1 x2 y2 } [ScreenCoords $coords] break
-    set id [eval $itk_component(drawing) create rectangle $coords]
+    set id [$itk_component(drawing) create rectangle $x1 $y1 $x2 $y2]
     set _cname2id($cname) $id
+    eval $itk_component(drawing) itemconfigure $id [array get options]
 }
 
@@ -593,5 +613,6 @@
     array set attr2option {
 	"font"		"-font"
-	"color"		"-fill"
+	"color"		"-foreground"
+	"valuecolor"	"-valueforeground"
 	"text"		"-text"
 	"anchor"	"-anchor"
@@ -601,7 +622,9 @@
     # Set default options first and then let tool.xml override them.
     array set options {
-	-font {Arial 8}
+	-font {Arial 12}
+	-valuefont {Arial 12}
+	-valueforeground blue3
 	-text {}
-	-fill black
+	-fill {}
 	-anchor c
     }
@@ -609,10 +632,14 @@
 	if { [info exists attr2option($attr)] } {
 	    set option $attr2option($attr)
-	    set value [XmlGet $cpath.$attr]
+	    if { $attr == "text" } {
+		set value [XmlGet $cpath.$attr]
+	    } else {
+		set value [XmlGetSubst $cpath.$attr]
+	    }
 	    set options($option) $value
 	}
     }
     # Coords
-    set coords [XmlGet $cpath.coords]
+    set coords [XmlGetSubst $cpath.coords]
     set coords [string trim $coords]
     if { $coords == "" } {
@@ -621,8 +648,73 @@
 	set coords [ScreenCoords $coords]
     }
+    set hotspot [XmlGetSubst $cpath.hotspot]
+    if { $hotspot == "inline" } {
+	set options(-showicons) 1 
+    }
+    set c $itk_component(drawing)
     set options(-tags) $cname
-    set id [eval $itk_component(drawing) create text $coords]
+    set img [Rappture::icon hotspot_normal]
+    set options(-image) $img
+    set img [Rappture::icon hotspot_active]
+    set options(-activeimage) $img
+    set id [eval $c create hotspot $coords]
     set _cname2id($cname) $id
-    eval $itk_component(drawing) itemconfigure $id [array get options]
+    set options(-interp) $_parser
+    eval $c itemconfigure $id [array get options]
+    if { $hotspot == "inline" } {
+	array unset _cname2controls $cname
+	foreach varName [Rappture::hotspot variables $c $id] {
+	    if { [info exists _name2path($varName)] } {
+		set path $_name2path($varName)
+		$_owner xml put $path.hide 1
+		lappend _cname2controls($cname) $path
+	    } else {
+		puts stderr "unknown varName=$varName"
+	    }
+	}
+	$c bind $id <Motion> \
+	    [itcl::code $this Hotspot watch $cname $id %x %y]
+	$c bind $id <Leave> \
+	    [itcl::code $this Hotspot deactivate $cname $id]
+	$c bind $id <Enter> \
+	    [itcl::code $this Hotspot activate $cname $id %x %y]
+	$c bind $id <ButtonRelease-1> \
+	    [itcl::code $this Hotspot invoke $cname $id %x %y]
+    }
+}
+
+
+itcl::body Rappture::DrawingEntry::Hotspot { option cname item args } {
+    if { [NumControlsEnabled $cname] == 0 } {
+	return
+    }
+    set c $itk_component(drawing)
+    switch -- $option {
+	"activate" {
+	    foreach { x y } $args break
+	    set varName  [Rappture::hotspot identify $c $item $x $y]
+	    $c itemconfigure $item -activevalue $varName
+	}
+	"deactivate" {
+	    $c itemconfigure $item -activevalue ""
+	}
+	"watch" {
+	    foreach { x y } $args break
+	    set active [$c itemcget $item -activevalue]
+	    set varName  [Rappture::hotspot identify $c $item $x $y]
+	    if { $varName != $active  } {
+		$c itemconfigure $item -activevalue $varName
+	    }
+	}
+	"invoke" {
+	    foreach { x y } $args break
+	    set active [$c itemcget $item -activevalue]
+	    set varName  [Rappture::hotspot identify $c $item $x $y]
+	    if { $varName != "" } {
+		set bbox [$c bbox $item]
+		Invoke $cname $x [lindex $bbox 1]
+	    }
+	}
+    }
 }
 
@@ -825,7 +917,7 @@
     set popup .drawingentrypopup
     if { ![winfo exists $popup] } {
-	# Create a popup for the print dialog
+	# Create a popup for the controls dialog
 	Rappture::Balloon $popup -title "Change values..." \
-	    -deactivatecommand [itcl::code $this Withdraw]
+	    -deactivatecommand [itcl::code $this Withdraw $cname]
 	set inner [$popup component inner]
 	Rappture::DrawingControls $inner.controls $_owner \
@@ -836,6 +928,13 @@
 	$inner.controls delete all
     }
+    set count 0
     foreach path $controls {
-	$inner.controls add $path
+	if { [IsEnabled $path] } {
+	    $inner.controls add $path
+	    incr count
+	}
+    }
+    if { $count == 0 } {
+	return
     }
     update
@@ -843,5 +942,4 @@
     incr x [winfo rootx $itk_component(drawing)]
     incr y [winfo rooty $itk_component(drawing)]
-    
     $popup activate @$x,$y above
 }
@@ -851,5 +949,7 @@
 #
 itcl::body Rappture::DrawingEntry::Activate { cname } {
-    $itk_component(drawing) configure -cursor center_ptr
+    $itk_component(drawing) configure -cursor center_ptr 
+    $itk_component(drawing) itemconfigure $_cname2id($cname) \
+	-image [Rappture::icon hotspot_active]
 }
 
@@ -858,11 +958,13 @@
 #
 itcl::body Rappture::DrawingEntry::Deactivate { cname } {
-    $itk_component(drawing) configure -cursor left_ptr
-}
-
-#
-# Invoke -- 
-#
-itcl::body Rappture::DrawingEntry::Withdraw {} {
+    $itk_component(drawing) configure -cursor left_ptr 
+    $itk_component(drawing) itemconfigure $_cname2id($cname) \
+	-image [Rappture::icon hotspot_normal]
+}
+
+#
+# Withdraw -- 
+#
+itcl::body Rappture::DrawingEntry::Withdraw { cname } {
     Redraw
 }
@@ -888,5 +990,4 @@
 itcl::body Rappture::DrawingEntry::InitSubstitutions {} {
     # Load a new parser with the variables representing the substitution
-    set _parser [interp create -safe]
     foreach name [array names _name2path] {
 	set path $_name2path($name)
@@ -903,8 +1004,92 @@
 itcl::body Rappture::DrawingEntry::XmlGet { path } {
     set value [$_owner xml get $path]
+    return [string trim $value]
+}
+
+itcl::body Rappture::DrawingEntry::XmlGetSubst { path } {
+    set value [$_owner xml get $path]
     if { $_parser == "" } {
 	return $value
     }
-    return [$_parser eval [list subst -nocommands $value]]
-}
-
+    return [string trim [$_parser eval [list subst -nocommands $value]]]
+}
+
+itcl::body Rappture::DrawingEntry::IsEnabled { path } {
+    set enable [string trim [$_owner xml get $path.about.enable]]
+    if {"" == $enable} {
+        return 1
+    }
+    if {![string is boolean $enable]} {
+        set re {([a-zA-Z_]+[0-9]*|\([^\(\)]+\)|[a-zA-Z_]+[0-9]*\([^\(\)]+\))(\.([a-zA-Z_]+[0-9]*|\([^\(\)]+\)|[a-zA-Z_]+[0-9]*\([^\(\)]+\)))*(:[-a-zA-Z0-9/]+)?}
+        set rest $enable
+        set enable ""
+        set deps ""
+        while {1} {
+            if {[regexp -indices $re $rest match]} {
+                foreach {s0 s1} $match break
+
+                if {[string index $rest [expr {$s0-1}]] == "\""
+                      && [string index $rest [expr {$s1+1}]] == "\""} {
+                    # string in ""'s? then leave it alone
+                    append enable [string range $rest 0 $s1]
+                    set rest [string range $rest [expr {$s1+1}] end]
+                } else {
+                    #
+                    # This is a symbol which should be substituted
+                    # it can be either:
+                    #   input.foo.bar
+                    #   input.foo.bar:units
+                    #
+                    set cpath [string range $rest $s0 $s1]
+                    set parts [split $cpath :]
+                    set ccpath [lindex $parts 0]
+                    set units [lindex $parts 1]
+
+                    # make sure we have the standard path notation
+                    set stdpath [$_owner regularize $ccpath]
+                    if {"" == $stdpath} {
+                        puts stderr "WARNING: don't recognize parameter $cpath in <enable> expression for $path.  This may be buried in a structure that is not yet loaded."
+                        set stdpath $ccpath
+                    }
+                    # substitute [_controlValue ...] call in place of path
+                    append enable [string range $rest 0 [expr {$s0-1}]]
+                    append enable [format {[ControlValue %s %s]} $stdpath $units]
+                    lappend deps $stdpath
+                    set rest [string range $rest [expr {$s1+1}] end]
+                }
+            } else {
+                append enable $rest
+                break
+            }
+        }
+    }
+    return [expr $enable]
+}
+
+# ----------------------------------------------------------------------
+# USAGE: ControlValue <path> ?<units>?
+#
+# Used internally to get the value of a control with the specified
+# <path>.  Returns the current value for the control.
+# ----------------------------------------------------------------------
+itcl::body Rappture::DrawingEntry::ControlValue {path {units ""}} {
+    if {"" != $_owner} {
+        set val [$_owner valuefor $path]
+         if {"" != $units} {
+            set val [Rappture::Units::convert $val -to $units -units off]
+        }
+        return $val
+    }
+    return ""
+}
+
+itcl::body Rappture::DrawingEntry::NumControlsEnabled { cname } {
+    set controls $_cname2controls($cname)
+    set count 0
+    foreach path $controls {
+	if { [IsEnabled $path] } {
+	    incr count
+	}
+    }
+    return $count
+}
Index: branches/blt4/gui/scripts/dropdownchecks.tcl
===================================================================
--- branches/blt4/gui/scripts/dropdownchecks.tcl	(revision 3102)
+++ branches/blt4/gui/scripts/dropdownchecks.tcl	(revision 3102)
@@ -0,0 +1,421 @@
+# ----------------------------------------------------------------------
+#  COMPONENT: dropdownchecks - drop-down list of checkbox items
+#
+#  This is the drop-down for the Combochecks widget, which maintains
+#  a list of options.
+#
+# ======================================================================
+#  AUTHOR:  Michael McLennan, Purdue University
+#  Copyright (c) 2004-2005  Purdue Research Foundation
+#
+#  See the file "license.terms" for information on usage and
+#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# ======================================================================
+package require Itk
+
+option add *Dropdownchecks.textBackground white widgetDefault
+option add *Dropdownchecks.foreground black widgetDefault
+option add *Dropdownchecks.outline black widgetDefault
+option add *Dropdownchecks.borderwidth 1 widgetDefault
+option add *Dropdownchecks.relief flat widgetDefault
+option add *Dropdownchecks.font \
+    -*-helvetica-medium-r-normal-*-12-* widgetDefault
+
+itcl::class Rappture::Dropdownchecks {
+    inherit Rappture::Dropdown
+
+    itk_option define -font font Font "" 
+    itk_option define -foreground foreground Foreground "" 
+
+    constructor {args} { # defined below }
+
+    public method insert {pos args}
+    public method delete {first {last ""}}
+    public method index {args}
+    public method get {args}
+    public method size {}
+    public method state {value {newval ""}}
+    public method reset {}
+
+    private variable _dispatcher "" ;# dispatcher for !events
+    private variable _values ""     ;# values for all elems in list
+    private variable _labels ""     ;# labels for each of the _values
+    private variable _states        ;# maps list item => on/off state
+    private variable _layout        ;# layout parameters for drawing
+
+    protected method _redraw {args}
+    protected method _layout {args}
+    protected method _adjust {{widget ""}}
+    protected method _react {x y}
+}
+
+itk::usual Dropdownchecks {
+    keep -background -outline -cursor -font
+}
+
+# ----------------------------------------------------------------------
+# CONSTRUCTOR
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::constructor {args} {
+    Rappture::dispatcher _dispatcher
+    $_dispatcher register !redraw
+    $_dispatcher dispatch $this !redraw [itcl::code $this _redraw]
+    $_dispatcher register !layout
+    $_dispatcher dispatch $this !layout [itcl::code $this _layout]
+
+    itk_component add scroller {
+        Rappture::Scroller $itk_interior.sc \
+            -xscrollmode off -yscrollmode auto
+    }
+    pack $itk_component(scroller) -expand yes -fill both
+
+    itk_component add list {
+        canvas $itk_component(scroller).list \
+            -highlightthickness 0 -width 3i -height 1.5i
+    } {
+        usual
+        rename -background -textbackground textBackground Background
+        ignore -highlightthickness -highlightbackground -highlightcolor
+        keep -relief
+    }
+    $itk_component(scroller) contents $itk_component(list)
+
+    # add bindings so the listbox can react to selections
+    bind RapptureDropdownchecks-$this <ButtonRelease-1> \
+        [itcl::code $this _react %x %y]
+    bind RapptureDropdownchecks-$this <KeyPress-Escape> \
+        [itcl::code $this unpost]
+
+    set btags [bindtags $itk_component(list)]
+    set i [lsearch $btags [winfo class $itk_component(list)]]
+    set btags [lreplace $btags $i $i RapptureDropdownchecks-$this]
+    bindtags $itk_component(list) $btags
+
+    eval itk_initialize $args
+
+    $_dispatcher event -idle !layout
+}
+
+# ----------------------------------------------------------------------
+# USAGE: insert <pos> ?<value1> <label1> <value2> <label2> ...?
+#
+# Inserts one or more values into this drop-down list.  Each value
+# has a keyword (computer-friendly value) and a label (human-friendly
+# value).  The labels appear in the listbox.  If the label is "--",
+# then the value is used as the label.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::insert {pos args} {
+    if {"end" == $pos} {
+        set pos [llength $_values]
+    } elseif {![string is integer -strict $pos]} {
+        error "bad index \"$pos\": should be integer or \"end\""
+    }
+
+    if {[llength $args] == 1} {
+        set args [lindex $args 0]
+    }
+    if {[llength $args] % 2 != 0} {
+        error "wrong # args: should be \"insert pos ?value label ...?\""
+    }
+
+    foreach {val label} $args {
+        if {$label == "--"} {
+            set label $val
+        }
+        set _values [linsert $_values $pos $val]
+        set _labels [linsert $_labels $pos $label]
+        set _states($val) 0
+        incr pos
+    }
+    $_dispatcher event -idle !redraw
+}
+
+# ----------------------------------------------------------------------
+# USAGE: delete <first> ?<last>?
+#
+# Deletes one or more values from this drop-down list.  The indices
+# <first> and <last> should be integers from 0 or the keyword "end".
+# All values in the specified range are deleted.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::delete {first {last ""}} {
+    if {$last == ""} {
+        set last $first
+    }
+    if {![regexp {^[0-9]+|end$} $first]} {
+        error "bad index \"$first\": should be integer or \"end\""
+    }
+    if {![regexp {^[0-9]+|end$} $last]} {
+        error "bad index \"$last\": should be integer or \"end\""
+    }
+
+    foreach val [lrange $_values $first $last] {
+        unset _states($val)
+    }
+    set _values [lreplace $_values $first $last]
+    set _labels [lreplace $_labels $first $last]
+
+    $_dispatcher event -idle !redraw
+}
+
+# ----------------------------------------------------------------------
+# USAGE: index ?-value|-label? <value>
+#
+# Returns the integer index for the position of the specified <value>
+# in the list.  Returns -1 if the value is not recognized.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::index {args} {
+    set format -value
+    set first [lindex $args 0]
+    if {$first == "-value" || $first == "-label"} {
+        set format $first
+        set args [lrange $args 1 end]
+    } elseif {[llength $args] > 1} {
+        error "bad option \"$first\": should be -value or -label"
+    }
+    if {[llength $args] != 1} {
+        error "wrong # args: should be \"index ?-value? ?-label? string\""
+    }
+    set value [lindex $args 0]
+
+    switch -- $format {
+        -value { return [lsearch -exact $_values $value] }
+        -label { return [lsearch -exact $_labels $value] }
+    }
+    return -1
+}
+
+# ----------------------------------------------------------------------
+# USAGE: get ?-value|-label|-both? ?<index>?
+#
+# Queries one or more values from the drop-down list.  With no args,
+# it returns a list of all values and labels in the list.  If the
+# index is specified, then it returns the value or label (or both)
+# for the specified index.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::get {args} {
+    set format -both
+    set first [lindex $args 0]
+    if {[string index $first 0] == "-"} {
+        set choices {-value -label -both}
+        if {[lsearch $choices $first] < 0} {
+            error "bad option \"$first\": should be [join [lsort $choices] {, }]"
+        }
+        set format $first
+        set args [lrange $args 1 end]
+    }
+
+    # return the whole list or just a single value
+    if {[llength $args] > 1} {
+        error "wrong # args: should be \"get ?-value|-label|-both? ?index?\""
+    }
+    if {[llength $args] == 0} {
+        set vlist $_values
+        set llist $_labels
+        set op lappend
+    } else {
+        set i [lindex $args 0]
+        set vlist [list [lindex $_values $i]]
+        set llist [list [lindex $_labels $i]]
+        set op set
+    }
+
+    # scan through and build up the return list
+    set rlist ""
+    foreach v $vlist l $llist {
+        switch -- $format {
+            -value { $op rlist $v }
+            -label { $op rlist $l }
+            -both  { lappend rlist $v $l }
+        }
+    }
+    return $rlist
+}
+
+# ----------------------------------------------------------------------
+# USAGE: size
+#
+# Returns the number of choices in this list.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::size {} {
+    return [llength $_values]
+}
+
+# ----------------------------------------------------------------------
+# USAGE: state <value> ?on|off?
+#
+# Used to query or set the state for the underlying values of the
+# listbox.  The <value> is the value associated with a label option.
+# With no other arg, it returns the current state for that value.
+# Otherwise, it sets the state for that value.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::state {val {newval ""}} {
+    if {$newval eq ""} {
+        if {[info exists _states($val)]} {
+            return $_states($val)
+        }
+        return ""
+    }
+
+    if {[info exists _states($val)]} {
+        if {$newval} {
+            set img [Rappture::icon cbon]
+        } else {
+            set img [Rappture::icon cboff]
+        }
+        $itk_component(list) itemconfigure box-$val -image $img
+        set _states($val) $newval
+    } else {
+        error "bad value \"$val\": should be one of [join $_values {, }]"
+    }
+}
+
+# ----------------------------------------------------------------------
+# USAGE: reset
+#
+# Resets the state of all checkboxes back to 0 (unchecked).
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::reset {} {
+    foreach val $_values {
+        set _states($val) 0
+    }
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _layout ?<eventData>?
+#
+# Used internally to recompute layout parameters whenever the font
+# changes.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::_layout {args} {
+    # figure out the sizes of the checkboxes
+    set wmax 0
+    set hmax 0
+    foreach icon {cbon cboff} {
+        set img [Rappture::icon $icon]
+        set w [image width $img]
+        if {$w > $wmax} {set wmax $w}
+        set h [image height $img]
+        if {$h > $hmax} {set hmax $h}
+    }
+    incr wmax 2
+
+    set fnt $itk_option(-font)
+    set lineh [expr {[font metrics $fnt -linespace]+2}]
+
+    if {$hmax > $lineh} {
+        set lineh $hmax
+    }
+
+    set _layout(boxwidth) $wmax
+    set _layout(lineh) $lineh
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _redraw ?<eventData>?
+#
+# Used internally to redraw the items in the list whenever new items
+# are added or removed.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::_redraw {args} {
+    set c $itk_component(list)
+    $c delete all
+
+    set fg $itk_option(-foreground)
+
+    if {[$_dispatcher ispending !layout]} {
+        $_dispatcher event -now !layout
+    }
+
+    set y 0
+    set _layout(ypos) ""
+    for {set i 0} {$i < [llength $_values]} {incr i} {
+        set x0 2
+        set val [lindex $_values $i]
+        set label [lindex $_labels $i]
+
+        if {$_states($val)} {
+            set img [Rappture::icon cbon]
+        } else {
+            set img [Rappture::icon cboff]
+        }
+
+        set ymid [expr {$y + $_layout(lineh)/2}]
+        $c create image $x0 $ymid -anchor w -image $img -tags box-$val
+        incr x0 $_layout(lineh)
+        $c create text $x0 $ymid -anchor w -text $label -fill $fg
+
+        lappend _layout(ypos) [incr y $_layout(lineh)]
+    }
+
+    # fix the overall size for scrolling
+    foreach {x0 y0 x1 y1} [$c bbox all] break
+    $c configure -scrollregion [list 0 0 $x1 $y1]
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _adjust ?<widget>?
+#
+# This method is invoked each time the dropdown is posted to adjust
+# its size and contents.  If the <widget> is specified, it is the
+# controlling widget.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::_adjust {{widget ""}} {
+    chain $widget
+
+    set c $itk_component(list)
+    set lineh $_layout(lineh)
+
+    foreach {x0 y0 x1 y1} [$c bbox all] break
+    set maxw [expr {$x1+2}]
+
+    set maxh [expr {10*$lineh + 2*[$c cget -borderwidth] + 2}]
+
+    if {$widget != ""} {
+        if {$maxw < [winfo width $widget]} { set maxw [winfo width $widget] }
+    }
+    $c configure -width $maxw -height $maxh
+
+    if {$widget != ""} {
+        set y [expr {[winfo rooty $widget]+[winfo height $widget]}]
+        set screenh [winfo screenheight $widget]
+        set lines [expr {round(floor(double($screenh-$y)/$lineh))}]
+        if {[llength $_labels] < $lines} {
+            set lines [llength $_labels]
+        }
+        set maxh [expr {[lindex $_layout(ypos) [expr {$lines-1}]]+2}]
+        $c configure -height $maxh
+    }
+
+    focus $c
+}
+
+# ----------------------------------------------------------------------
+# USAGE: _react <x> <y>
+#
+# Invoked automatically when the user has selected something from
+# the listbox.  Unposts the drop-down and generates an event letting
+# the client know that the selection has changed.
+# ----------------------------------------------------------------------
+itcl::body Rappture::Dropdownchecks::_react {x y} {
+    if {$x >= 0 && $x <= [winfo width $itk_component(hull)]
+          && $y >= 0 && $y <= [winfo height $itk_component(hull)]} {
+
+        set x [$itk_component(list) canvasx $x]
+        set y [$itk_component(list) canvasy $y]
+
+        for {set i 0} {$i < [llength $_values]} {incr i} {
+            if {$y < [lindex $_layout(ypos) $i]} {
+                set val [lindex $_values $i]
+                if {$_states($val)} {
+                    state $val 0
+                } else {
+                    state $val 1
+                }
+                break
+            }
+        }
+        event generate $itk_component(hull) <<DropdownchecksSelect>>
+    } else {
+        unpost
+    }
+}
Index: branches/blt4/gui/scripts/htmlviewer.tcl
===================================================================
--- branches/blt4/gui/scripts/htmlviewer.tcl	(revision 3091)
+++ branches/blt4/gui/scripts/htmlviewer.tcl	(revision 3102)
@@ -304,5 +304,7 @@
         if {"" != $node} {
             set bbox [$itk_component(html) bbox $node]
-            set realht [expr {[lindex $bbox 3]-[lindex $bbox 1]}]
+	    if { $bbox != "" } {
+		set realht [expr {[lindex $bbox 3]-[lindex $bbox 1]}]
+	    }
         }
         if {$itk_option(-maxlines) > 0} {
Index: branches/blt4/gui/scripts/vtkviewer.tcl
===================================================================
--- branches/blt4/gui/scripts/vtkviewer.tcl	(revision 3091)
+++ branches/blt4/gui/scripts/vtkviewer.tcl	(revision 3102)
@@ -1306,5 +1306,8 @@
         }
         "glyphs" {
-            #SendCmd "glyphs colormap $colormap $tag"
+            SendCmd "glyphs colormap $colormap $tag"
+        }
+        "molecule" {
+            SendCmd "molecule colormap $colormap $tag"
         }
     }
Index: branches/blt4/gui/src/Makefile.in
===================================================================
--- branches/blt4/gui/src/Makefile.in	(revision 3091)
+++ branches/blt4/gui/src/Makefile.in	(revision 3102)
@@ -51,4 +51,5 @@
 OBJS 		= \
 		RapptureGUI_Init.o \
+		RpCanvHotspot.o \
 		RpCanvPlacard.o \
 		RpDiffview.o \
Index: branches/blt4/gui/src/RapptureGUI_Init.c
===================================================================
--- branches/blt4/gui/src/RapptureGUI_Init.c	(revision 3091)
+++ branches/blt4/gui/src/RapptureGUI_Init.c	(revision 3102)
@@ -23,5 +23,7 @@
 
 extern Tcl_AppInitProc Rappturegui_Init;
+extern Tcl_AppInitProc RpCanvHotspot_Init;
 extern Tcl_AppInitProc RpCanvPlacard_Init;
+extern Tcl_AppInitProc RpConvertDxToVtk_Init;
 extern Tcl_AppInitProc RpDiffview_Init;
 
@@ -48,4 +50,7 @@
         return TCL_ERROR;
     }
+    if (RpCanvHotspot_Init(interp) != TCL_OK) {
+        return TCL_ERROR;
+    }
     return TCL_OK;
 }
Index: branches/blt4/gui/src/RpCanvHotspot.c
===================================================================
--- branches/blt4/gui/src/RpCanvHotspot.c	(revision 3102)
+++ branches/blt4/gui/src/RpCanvHotspot.c	(revision 3102)
@@ -0,0 +1,1824 @@
+
+/*
+ * ----------------------------------------------------------------------
+ *  RpCanvHotspot - canvas item with text and background box
+ *
+ *  This canvas item makes it easy to create a box with text inside.
+ *  The box is normally stretched around the text, but can be given
+ *  a max size and causing text to be clipped.
+ *
+ *    .c create hotspot <x> <y> -anchor <nsew> \
+ *        -text <text>         << text to be displayed
+ *        -font <name>         << font used for text
+ *        -image <image>       << image displayed on the right of text
+ *        -maxwidth <size>     << maximum size
+ *        -textcolor <color>   << text color
+ *        -substtextcolor <color>   << substituted text color
+ *        -background <color>  << fill for rect behind text
+ *        -outline <color>     << outline around text
+ *        -borderwidth <size>  << for 3D border
+ *        -relief <value>      << for 3D border (drawn under -outline)
+ *        -tags <taglist>      << usual Tk canvas tags
+ *	  -interp interp	Interpreter containing substitution variables.
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2009  Purdue Research Foundation
+ *
+ *  See the file "license.terms" for information on usage and
+ *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ * ======================================================================
+ */
+#include <string.h>
+#include <math.h>
+#include "tk.h"
+
+#define SEGMENT_TEXT	0
+#define SEGMENT_VALUE	1
+#define VAR_FLAGS (TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS)
+
+#define REDRAW_PENDING		(1<<0)
+#define LAYOUT_PENDING		(1<<1)
+#define SUBSTITUTIONS_PENDING	(1<<2)
+
+#define TRUE		1
+#define FALSE		0
+#undef MAX
+#define MAX(a,b)	(((a)>(b))?(a):(b))
+
+#undef ROUND
+#define ROUND(x) 	((int)(((double)(x)) + (((x)<0.0) ? -0.5 : 0.5)))
+
+#define IMAGE_PAD	3
+#define TEXT_PADX	3
+#define TEXT_PADY	1
+
+typedef struct _ItemSegment ItemSegment;
+typedef struct _HotspotItem HotspotItem;
+
+/*
+ * ItemSegment 
+ *
+ */
+struct _ItemSegment {
+    HotspotItem *itemPtr;
+    ItemSegment *nextPtr;
+    int type;				/* Image, normal text, or substituted
+					 * value. */
+    short int x, y;			/* Location of segment on canvas. */
+    short int width, height;		/* Dimension of segment. */
+    Tk_TextLayout layout;		/* Layout of normal or substituted 
+					 * value */
+    int length;				/* # of bytes in below string. */
+    int lineNum;			/* Line where segment if located. */
+    char *value;			/* Substitututed value. */
+    char text[1];
+};
+
+/*
+ * Record for each hotspot item:
+ */
+struct _HotspotItem  {
+    Tk_Item base;			/* Generic stuff that's the same for
+					 * all types.  MUST BE FIRST IN
+					 * STRUCTURE. */
+    unsigned int flags;
+    Tcl_Interp *interp;			/* Interp that owns this item */
+    Display *display;
+    Tk_Window tkwin;			/* Window that represents this
+					 * canvas */
+    Tk_Canvas canvas;			/* Canvas that owns this item */
+
+    /*
+     * Fields that are set by widget commands other than "configure":
+     */
+    double x, y;			/* Positioning point for item. This in
+					 * conjunction with -anchor determine
+					 * where the item is drawn on the
+					 * canvas. */
+    double x1, y1, x2, y2;
+    /*
+     * Configuration settings that are updated by Tk_ConfigureWidget:
+     */
+    Tcl_Interp *valueInterp;		/* If non-NULL, interpreter containing
+					 * substituted variables.   */
+    const char *text;			/* Text to be displayed */
+    Tk_Anchor anchor;			/* Where to anchor text relative to
+					 * (x,y) */
+    XColor *textColor;			/* Color for normal text */
+    XColor *valueColor;			/* Color for substitututed values. */
+    XColor *activeColor;		/* Color for active substitututed
+					 * values */
+    Tk_3DBorder activeBorder;		/* Background color for active
+					 * substitututed values */
+    XColor *outlineColor;		/* Color for outline of rectangle */
+    Tk_3DBorder border;			/* If non-NULL, background color of
+					 * rectangle enclosing the entire
+					 * hotspot item. */
+    int borderWidth;			/* 3D border width (drawn under
+					 * -outline) */
+    int relief;				/* Indicates whether hotspot as a
+					 * whole is raised, sunken, or flat */
+    Tk_Font font;			/* Font for drawing normal text. */
+    Tk_Font valueFont;			/* Font for drawing substituted
+					 * values. */
+
+    const char *imageName;		/* Name of normal hotspot image. */
+    const char *activeImageName;	/* Name of active hotspot image. */
+  
+    Tk_Image image;			/* The normal icon for a hotspot. */
+    Tk_Image activeImage;		/* If non-NULL, active icon. */
+
+    /*
+     * Fields whose values are derived from the current values of the
+     * configuration settings above:
+     */
+    GC normalGC;			/* Graphics context for drawing
+					 * text. */
+    GC valueGC;				/* GC for substituted values. */
+    GC activeGC;			/* Gc for active substituted
+					 * values. */
+    GC outlineGC;			/* GC for outline? */
+    ItemSegment *firstPtr, *lastPtr;	/* List of hotspot segments. */
+    int width, height;			/* Dimension of bounding box. */
+    const char *activeValue;		/* Active segment. */
+    short int maxImageWidth, maxImageHeight;
+    int numLines;
+    int showIcons;			/* Indicates whether to draw icons. */
+};
+
+/*
+ * Information used for parsing configuration specs:
+ */
+static Tk_CustomOption tagsOption = {
+    (Tk_OptionParseProc *) Tk_CanvasTagsParseProc,
+    Tk_CanvasTagsPrintProc, (ClientData) NULL
+};
+
+static Tk_OptionParseProc ValueInterpParseProc;
+static Tk_OptionPrintProc ValueInterpPrintProc;
+static Tk_CustomOption interpOption = {
+    (Tk_OptionParseProc *)ValueInterpParseProc, ValueInterpPrintProc, 
+    (ClientData) NULL
+};
+
+static Tk_ConfigSpec configSpecs[] = {
+    {TK_CONFIG_BORDER, "-activebackground", (char *)NULL, (char*)NULL,
+        "white", Tk_Offset(HotspotItem, activeBorder), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_COLOR, "-activeforeground", (char*)NULL, (char*)NULL,
+        "black", Tk_Offset(HotspotItem, activeColor), 0},
+    {TK_CONFIG_STRING, "-activeimage", "activeImage", (char*)NULL, (char*)NULL, 
+	Tk_Offset(HotspotItem, activeImageName), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_STRING, "-activevalue", "activeValue", (char*)NULL, (char*)NULL, 
+	Tk_Offset(HotspotItem, activeValue), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_ANCHOR, "-anchor", "anchor", (char*)NULL,
+        "center", Tk_Offset(HotspotItem, anchor), TK_CONFIG_DONT_SET_DEFAULT},
+    {TK_CONFIG_BORDER, "-background", (char *)NULL, (char*)NULL,
+        "", Tk_Offset(HotspotItem, border), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", (char*)NULL,
+        "0", Tk_Offset(HotspotItem, borderWidth), TK_CONFIG_DONT_SET_DEFAULT},
+    {TK_CONFIG_BORDER, "-fill", (char *)NULL, (char*)NULL,
+        "", Tk_Offset(HotspotItem, border), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_FONT, "-font", "font", (char*)NULL,
+        "helvetica -12", Tk_Offset(HotspotItem, font), 0},
+    {TK_CONFIG_COLOR, "-foreground", "foreground", (char*)NULL,
+        "black", Tk_Offset(HotspotItem, textColor), 0},
+    {TK_CONFIG_CUSTOM, "-interp", (char*)NULL, (char*)NULL, (char*)NULL, 
+	Tk_Offset(HotspotItem, valueInterp), TK_CONFIG_NULL_OK, &interpOption},
+    {TK_CONFIG_FONT, "-valuefont", (char*)NULL, (char*)NULL,
+        "helvetica -12 bold", Tk_Offset(HotspotItem, valueFont), 0},
+    {TK_CONFIG_COLOR, "-valueforeground", (char*)NULL, (char*)NULL,
+        "blue", Tk_Offset(HotspotItem, valueColor), 0},
+    {TK_CONFIG_STRING, "-image", (char*)NULL, (char*)NULL, (char*)NULL, 
+	Tk_Offset(HotspotItem, imageName), 0},
+    {TK_CONFIG_COLOR, "-outline", (char*)NULL, (char*)NULL,
+        "", Tk_Offset(HotspotItem, outlineColor), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_RELIEF, "-relief", (char*)NULL, (char*)NULL,
+        "flat", Tk_Offset(HotspotItem, relief), TK_CONFIG_DONT_SET_DEFAULT},
+    {TK_CONFIG_BOOLEAN, "-showicons", (char*)NULL, (char*)NULL,
+        "0", Tk_Offset(HotspotItem, showIcons), TK_CONFIG_DONT_SET_DEFAULT},
+    {TK_CONFIG_CUSTOM, "-tags", (char*)NULL, (char*)NULL,
+        (char*)NULL, 0, TK_CONFIG_NULL_OK, &tagsOption},
+    {TK_CONFIG_STRING, "-text", (char*)NULL, (char*)NULL,
+        "", Tk_Offset(HotspotItem, text), 0},
+    {TK_CONFIG_END, (char*)NULL, (char*)NULL, (char*)NULL,
+        (char*)NULL, 0, 0}
+};
+
+/*
+ * Prototypes for procedures defined in this file
+ */
+static Tk_ItemConfigureProc ConfigureProc;
+static Tk_ItemCreateProc CreateProc;
+static Tk_ItemDeleteProc DeleteProc;
+static Tk_ItemDisplayProc DisplayProc;
+static Tk_ItemScaleProc ScaleProc;
+static Tk_ItemTranslateProc TranslateProc;
+static Tk_ItemCoordProc CoordsProc;
+static Tk_ItemAreaProc AreaProc;
+static Tk_ItemPointProc PointProc;
+static Tk_ItemPostscriptProc PostscriptProc;
+
+static Tk_ImageChangedProc ImageChangedProc;
+
+/*
+ * The structures below defines the canvas item type:
+ */
+static Tk_ItemType hotspotType = {
+    "hotspot",                          /* name */
+    sizeof(HotspotItem),                /* itemSize */
+    CreateProc,
+    configSpecs,                        /* configSpecs */
+    ConfigureProc,
+    CoordsProc,			
+    DeleteProc,				
+    DisplayProc,			
+    TK_CONFIG_OBJS,                     /* flags */
+    PointProc,				
+    AreaProc,				
+    PostscriptProc,			
+    ScaleProc,				
+    TranslateProc,			
+    NULL,				/* indexProc */
+    NULL,				/* icursorProc */
+    NULL,				/* selectionProc */
+    NULL,				/* insertProc */
+    NULL,				/* dTextProc */
+    NULL,				/* nextPtr */
+};
+
+/*
+ * The record below describes a canvas widget.  It is made available
+ * to the item procedures so they can access certain shared fields such
+ * as the overall displacement and scale factor for the canvas.
+ */
+
+typedef struct TkCanvas {
+    Tk_Window tkwin;		/* Window that embodies the canvas.  NULL
+				 * means that the window has been destroyed
+				 * but the data structures haven't yet been
+				 * cleaned up.*/
+    Display *display;		/* Display containing widget;  needed, among
+				 * other things, to release resources after
+				 * tkwin has already gone away. */
+    Tcl_Interp *interp;		/* Interpreter associated with canvas. */
+    Tcl_Command widgetCmd;	/* Token for canvas's widget command. */
+    Tk_Item *firstItemPtr;	/* First in list of all items in canvas,
+				 * or NULL if canvas empty. */
+    Tk_Item *lastItemPtr;	/* Last in list of all items in canvas,
+				 * or NULL if canvas empty. */
+
+    /*
+     * Information used when displaying widget:
+     */
+
+    int borderWidth;		/* Width of 3-D border around window. */
+    Tk_3DBorder border;		/* Used for canvas background. */
+    int relief;			/* Indicates whether window as a whole is
+				 * raised, sunken, or flat. */
+    int highlightWidth;		/* Width in pixels of highlight to draw
+				 * around widget when it has the focus.
+				 * <= 0 means don't draw a highlight. */
+    XColor *highlightBgColorPtr;
+				/* Color for drawing traversal highlight
+				 * area when highlight is off. */
+    XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
+    int inset;			/* Total width of all borders, including
+				 * traversal highlight and 3-D border.
+				 * Indicates how much interior stuff must
+				 * be offset from outside edges to leave
+				 * room for borders. */
+    GC pixmapGC;		/* Used to copy bits from a pixmap to the
+				 * screen and also to clear the pixmap. */
+    int width, height;		/* Dimensions to request for canvas window,
+				 * specified in pixels. */
+    int redrawX1, redrawY1;	/* Upper left corner of area to redraw,
+				 * in pixel coordinates.  Border pixels
+				 * are included.  Only valid if
+				 * REDRAW_PENDING flag is set. */
+    int redrawX2, redrawY2;	/* Lower right corner of area to redraw,
+				 * in integer canvas coordinates.  Border
+				 * pixels will *not* be redrawn. */
+    int confine;		/* Non-zero means constrain view to keep
+				 * as much of canvas visible as possible. */
+
+    /*
+     * Information used to manage the selection and insertion cursor:
+     */
+
+    Tk_CanvasTextInfo textInfo; /* Contains lots of fields;  see tk.h for
+				 * details.  This structure is shared with
+				 * the code that implements individual items. */
+    int insertOnTime;		/* Number of milliseconds cursor should spend
+				 * in "on" state for each blink. */
+    int insertOffTime;		/* Number of milliseconds cursor should spend
+				 * in "off" state for each blink. */
+    Tcl_TimerToken insertBlinkHandler;
+				/* Timer handler used to blink cursor on and
+				 * off. */
+
+    /*
+     * Transformation applied to canvas as a whole:  to compute screen
+     * coordinates (X,Y) from canvas coordinates (x,y), do the following:
+     *
+     * X = x - xOrigin;
+     * Y = y - yOrigin;
+     */
+
+    int xOrigin, yOrigin;	/* Canvas coordinates corresponding to
+				 * upper-left corner of window, given in
+				 * canvas pixel units. */
+    int drawableXOrigin, drawableYOrigin;
+				/* During redisplay, these fields give the
+				 * canvas coordinates corresponding to
+				 * the upper-left corner of the drawable
+				 * where items are actually being drawn
+				 * (typically a pixmap smaller than the
+				 * whole window). */
+
+    /*
+     * Information used for event bindings associated with items.
+     */
+
+    Tk_BindingTable bindingTable;
+				/* Table of all bindings currently defined
+				 * for this canvas.  NULL means that no
+				 * bindings exist, so the table hasn't been
+				 * created.  Each "object" used for this
+				 * table is either a Tk_Uid for a tag or
+				 * the address of an item named by id. */
+    Tk_Item *currentItemPtr;	/* The item currently containing the mouse
+				 * pointer, or NULL if none. */
+    Tk_Item *newCurrentPtr;	/* The item that is about to become the
+				 * current one, or NULL.  This field is
+				 * used to detect deletions  of the new
+				 * current item pointer that occur during
+				 * Leave processing of the previous current
+				 * item.  */
+    double closeEnough;		/* The mouse is assumed to be inside an
+				 * item if it is this close to it. */
+    XEvent pickEvent;		/* The event upon which the current choice
+				 * of currentItem is based.  Must be saved
+				 * so that if the currentItem is deleted,
+				 * can pick another. */
+    int state;			/* Last known modifier state.  Used to
+				 * defer picking a new current object
+				 * while buttons are down. */
+
+    /*
+     * Information used for managing scrollbars:
+     */
+
+    char *xScrollCmd;		/* Command prefix for communicating with
+				 * horizontal scrollbar.  NULL means no
+				 * horizontal scrollbar.  Malloc'ed*/
+    char *yScrollCmd;		/* Command prefix for communicating with
+				 * vertical scrollbar.  NULL means no
+				 * vertical scrollbar.  Malloc'ed*/
+    int scrollX1, scrollY1, scrollX2, scrollY2;
+				/* These four coordinates define the region
+				 * that is the 100% area for scrolling (i.e.
+				 * these numbers determine the size and
+				 * location of the sliders on scrollbars).
+				 * Units are pixels in canvas coords. */
+    char *regionString;		/* The option string from which scrollX1
+				 * etc. are derived.  Malloc'ed. */
+    int xScrollIncrement;	/* If >0, defines a grid for horizontal
+				 * scrolling.  This is the size of the "unit",
+				 * and the left edge of the screen will always
+				 * lie on an even unit boundary. */
+    int yScrollIncrement;	/* If >0, defines a grid for horizontal
+				 * scrolling.  This is the size of the "unit",
+				 * and the left edge of the screen will always
+				 * lie on an even unit boundary. */
+
+    /*
+     * Information used for scanning:
+     */
+
+    int scanX;			/* X-position at which scan started (e.g.
+				 * button was pressed here). */
+    int scanXOrigin;		/* Value of xOrigin field when scan started. */
+    int scanY;			/* Y-position at which scan started (e.g.
+				 * button was pressed here). */
+    int scanYOrigin;		/* Value of yOrigin field when scan started. */
+
+    /*
+     * Information used to speed up searches by remembering the last item
+     * created or found with an item id search.
+     */
+
+    Tk_Item *hotPtr;		/* Pointer to "hot" item (one that's been
+				 * recently used.  NULL means there's no
+				 * hot item. */
+    Tk_Item *hotPrevPtr;	/* Pointer to predecessor to hotPtr (NULL
+				 * means item is first in list).  This is
+				 * only a hint and may not really be hotPtr's
+				 * predecessor. */
+
+    /*
+     * Miscellaneous information:
+     */
+
+    Tk_Cursor cursor;		/* Current cursor for window, or None. */
+    char *takeFocus;		/* Value of -takefocus option;  not used in
+				 * the C code, but used by keyboard traversal
+				 * scripts.  Malloc'ed, but may be NULL. */
+    double pixelsPerMM;		/* Scale factor between MM and pixels;
+				 * used when converting coordinates. */
+    int flags;			/* Various flags;  see below for
+				 * definitions. */
+    int nextId;			/* Number to use as id for next item
+				 * created in widget. */
+    Tk_PostscriptInfo psInfo;
+				/* Pointer to information used for generating
+				 * Postscript for the canvas.  NULL means
+				 * no Postscript is currently being
+				 * generated. */
+    Tcl_HashTable idTable;	/* Table of integer indices. */
+    /*
+     * Additional information, added by the 'dash'-patch
+     */
+    VOID *reserved1;
+    Tk_State canvas_state;	/* state of canvas */
+    VOID *reserved2;
+    VOID *reserved3;
+    Tk_TSOffset tsoffset;
+#ifndef USE_OLD_TAG_SEARCH
+    void *bindTagExprs; /* linked list of tag expressions used in bindings */
+#endif
+} TkCanvas;
+
+/* Forward declarations. */
+static void ComputeBbox(HotspotItem *itemPtr);
+
+
+static void
+EventuallyRedraw(HotspotItem *itemPtr)
+{
+    if ((itemPtr->flags & REDRAW_PENDING) == 0) {
+	itemPtr->flags |= REDRAW_PENDING;
+	Tk_CanvasEventuallyRedraw(itemPtr->canvas, itemPtr->base.x1, 
+		itemPtr->base.y1, itemPtr->base.x2, itemPtr->base.y2);
+    }
+}
+
+static int
+GetCoordFromObj(Tcl_Interp *interp, HotspotItem *itemPtr, Tcl_Obj *objPtr, 
+		double *valuePtr)
+{
+    const char *string;
+
+    string = Tcl_GetString(objPtr);
+    return Tk_CanvasGetCoord(interp, itemPtr->canvas, string, valuePtr);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ValueInterpParseProc --
+ *
+ *	Converts the name of an axis to a pointer to its axis structure.
+ *
+ * Results:
+ *	The return value is a standard Tcl result.  The axis flags are
+ *	written into the widget record.
+ *
+ *----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static int
+ValueInterpParseProc(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin,
+		     CONST84 char *string, char *widgRec, int offset)
+{
+    HotspotItem *itemPtr = (HotspotItem *)widgRec;
+
+    if (string[0] == '\0') {
+	itemPtr->valueInterp = NULL;
+	return TCL_OK;
+    }
+    itemPtr->valueInterp = Tcl_GetSlave(interp, string);
+    if (itemPtr->valueInterp == NULL) {
+	return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ValueInterpParseProc --
+ *
+ *	Convert the window coordinates into a string.
+ *
+ * Results:
+ *	The string representing the coordinate position is returned.
+ *
+ *----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static char *
+ValueInterpPrintProc(ClientData clientData, Tk_Window tkwin, char *widgRec,
+		     int offset, Tcl_FreeProc **freeProcPtr)
+{
+    HotspotItem *itemPtr = (HotspotItem *)widgRec;
+
+    if (itemPtr->valueInterp != NULL) {
+	Tcl_Obj *objPtr;
+
+	Tcl_GetInterpPath(itemPtr->interp, itemPtr->valueInterp);
+	objPtr = Tcl_GetObjResult(itemPtr->interp);
+	*freeProcPtr = TCL_VOLATILE;
+	return Tcl_GetString(objPtr);
+    }
+    return "";
+}
+
+/*
+ *---------------------------------------------------------------------------
+ * 
+ * TraceVarProc --
+ *
+ *	This procedure is invoked when someone changes the state variable
+ *	associated with a radiobutton or checkbutton entry.  The entry's
+ *	selected state is set to match the value of the variable.
+ *
+ * Results:
+ *	NULL is always returned.
+ *
+ * Side effects:
+ *	The combobox entry may become selected or deselected.
+ *
+ *---------------------------------------------------------------------------
+ */
+static char *
+TraceVarProc(
+    ClientData clientData,		/* Information about the item. */
+    Tcl_Interp *interp,			/* Interpreter containing variable. */
+    const char *name1,			/* First part of variable's name. */
+    const char *name2,			/* Second part of variable's name. */
+    int flags)				/* Describes what just happened. */
+{
+    HotspotItem *itemPtr = clientData;
+
+    if (flags & TCL_INTERP_DESTROYED) {
+    	return NULL;			/* Interpreter is going away. */
+
+    }
+    /*
+     * If the variable is being unset, then re-establish the trace.
+     */
+    if (flags & TCL_TRACE_UNSETS) {
+	if (flags & TCL_TRACE_DESTROYED) {
+	    Tcl_TraceVar(interp, name1, VAR_FLAGS, TraceVarProc, itemPtr);
+	}
+    }
+    itemPtr->flags |= SUBSTITUTIONS_PENDING;
+    EventuallyRedraw(itemPtr);
+    return NULL;			/* Done. */
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * ImageChangedProc --
+ *
+ *      This procedure is invoked by the image code whenever the manager
+ *      for an image does something that affects the image's size or
+ *      how it is displayed.
+ *
+ * Results:
+ *      None.
+ *
+ * Side effects:
+ *      Arranges for the canvas to get redisplayed.
+ *
+ * ----------------------------------------------------------------------
+ */
+static void
+ImageChangedProc(clientData, x, y, width, height, imgWidth, imgHeight)
+    ClientData clientData;              /* Pointer to canvas item for image. */
+    int x, y;                           /* Upper left pixel (within image)
+                                         * that must be redisplayed. */
+    int width, height;                  /* Dimensions of area to redisplay
+                                         * (may be <= 0). */
+    int imgWidth, imgHeight;            /* New dimensions of image. */
+{      
+    HotspotItem *itemPtr = (HotspotItem *) clientData;
+    
+    itemPtr->flags |= LAYOUT_PENDING;
+    EventuallyRedraw(itemPtr);
+    return;
+}
+
+static void
+AddTextSegment(HotspotItem *itemPtr, const char *text, int length)
+{
+    ItemSegment *segPtr;
+
+    segPtr = (ItemSegment *)Tcl_Alloc(sizeof(ItemSegment) + length + 1);
+    memset(segPtr, 0, sizeof(ItemSegment) + length + 1);
+    segPtr->itemPtr = itemPtr;
+    segPtr->lineNum = itemPtr->numLines;
+    segPtr->type = SEGMENT_TEXT;
+    strncpy(segPtr->text, text, length);
+    segPtr->text[length] = '\0';
+    segPtr->length = length;
+    segPtr->x = segPtr->y = 0;
+    segPtr->nextPtr = NULL;
+    if (itemPtr->firstPtr == NULL) {
+	itemPtr->firstPtr = segPtr;
+    }
+    if (itemPtr->lastPtr == NULL) {
+	itemPtr->lastPtr = segPtr;
+    } else {
+	itemPtr->lastPtr->nextPtr = segPtr;
+	itemPtr->lastPtr = segPtr;
+    }
+}
+
+static void
+AddValueSegment(HotspotItem *itemPtr, const char *varName, int length)
+{
+    ItemSegment *segPtr;
+    Tcl_Interp *interp;
+
+    segPtr = (ItemSegment *)Tcl_Alloc(sizeof(ItemSegment) + length + 1);
+    memset(segPtr, 0, sizeof(ItemSegment) + length + 1);
+    segPtr->itemPtr = itemPtr;
+    segPtr->lineNum = itemPtr->numLines;
+    segPtr->type = SEGMENT_VALUE;
+    strncpy(segPtr->text, varName, length);
+    segPtr->text[length] = '\0';
+    segPtr->length = length;
+    interp = itemPtr->valueInterp;
+    if (interp == NULL) {
+	interp = itemPtr->interp;
+    }
+    Tcl_TraceVar(interp, varName, VAR_FLAGS, TraceVarProc, itemPtr);
+    segPtr->value = NULL;
+    segPtr->x = segPtr->y = 0;
+    segPtr->nextPtr = NULL;
+    if (itemPtr->firstPtr == NULL) {
+	itemPtr->firstPtr = segPtr;
+    }
+    if (itemPtr->lastPtr == NULL) {
+	itemPtr->lastPtr = segPtr;
+    } else {
+	itemPtr->lastPtr->nextPtr = segPtr;
+	itemPtr->lastPtr = segPtr;
+    }
+}
+
+static void
+DestroySegment(ItemSegment *segPtr)
+{
+    HotspotItem *itemPtr;
+
+    itemPtr = segPtr->itemPtr;
+    if (segPtr->layout != NULL) {
+	Tk_FreeTextLayout(segPtr->layout);
+    }
+    if (segPtr->type == SEGMENT_VALUE) {
+	Tcl_UntraceVar(itemPtr->interp, segPtr->text, VAR_FLAGS, TraceVarProc, 
+		itemPtr);
+    }
+    if (segPtr->value != NULL) {
+	Tcl_Free((char *)segPtr->value);
+    }
+    Tcl_Free((char *)segPtr);
+}
+
+static void
+DestroySegments(HotspotItem *itemPtr)
+{
+    ItemSegment *segPtr, *nextPtr;
+
+    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = nextPtr) {
+	nextPtr = segPtr->nextPtr;
+	DestroySegment(segPtr);
+    }
+    itemPtr->firstPtr = itemPtr->lastPtr = NULL;
+}
+
+static void
+DoSubstitutions(HotspotItem *itemPtr)
+{
+    ItemSegment *segPtr;
+    Tcl_Interp *interp;
+
+    itemPtr->flags &= ~SUBSTITUTIONS_PENDING;
+    interp = itemPtr->valueInterp;
+    if (interp == NULL) {
+	interp = itemPtr->interp;
+    }
+    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
+	const char *value;
+
+	if (segPtr->type != SEGMENT_VALUE) {
+	    continue;
+	}
+	value = Tcl_GetVar(interp, segPtr->text, TCL_GLOBAL_ONLY);
+	if (value == NULL) {
+	    value = "???";
+	}
+	if (segPtr->value != NULL) {
+	    Tcl_Free((char *)segPtr->value);
+	}
+	segPtr->value = Tcl_Alloc(strlen(value) + 1);
+	strcpy(segPtr->value, value);
+    }
+    itemPtr->flags |= LAYOUT_PENDING;
+}
+
+static void
+ComputeGeometry(HotspotItem *itemPtr)
+{
+    int x, y, w, h;
+    ItemSegment *segPtr;
+    int imgWidth, imgHeight;
+    int maxWidth, maxHeight;
+    int justify;
+    int wrapLength;
+    int flags;
+
+    justify = TK_JUSTIFY_LEFT;
+    wrapLength = 0;
+    flags = 0;
+    /* Collect the individual segment sizes. */
+    imgWidth = itemPtr->maxImageWidth + IMAGE_PAD;
+    imgHeight = itemPtr->maxImageHeight + 2 * IMAGE_PAD;
+    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
+	if (segPtr->layout != NULL) {
+	    Tk_FreeTextLayout(segPtr->layout);
+	}
+	w = h = 0;
+	switch (segPtr->type) {
+	case SEGMENT_VALUE:
+	    segPtr->layout = Tk_ComputeTextLayout(itemPtr->valueFont, 
+			segPtr->value, -1, wrapLength, justify, flags, &w, &h);
+	    w += TEXT_PADX * 2;
+	    h += TEXT_PADY * 2;
+	    if (itemPtr->showIcons) {
+		w += imgWidth, h = MAX(h, imgHeight);
+	    }
+	    break;
+	case SEGMENT_TEXT:
+	    segPtr->layout = Tk_ComputeTextLayout(itemPtr->font, segPtr->text, 
+			-1, wrapLength, justify, flags, &w, &h);
+	    w += TEXT_PADX * 2;
+	    h += TEXT_PADY * 2;
+	    break;
+	default:
+	    Tcl_Panic("unknown hotspot segment type");
+	}
+	segPtr->width = w;
+	segPtr->height = h;
+#ifdef notdef
+	fprintf(stderr, "ComputeGeometry: item=(%s) segment=%x (%s) type=%d w=%d h=%d lineNum=%d nextPtr=%x\n",
+		itemPtr->text, segPtr, segPtr->text, segPtr->type, segPtr->width, 
+		segPtr->height, segPtr->lineNum, segPtr->nextPtr);
+#endif
+    }
+    /* Compute the relative positions of the segments. */
+    x = y = 0;
+    maxWidth = maxHeight = 0;
+    for (segPtr = itemPtr->firstPtr; segPtr != NULL; /*empty*/) {
+	int maxHeight, lineNum;
+	ItemSegment *startPtr;
+
+	maxHeight = 0;
+	lineNum = segPtr->lineNum;
+	startPtr = segPtr;
+	x = 0;
+
+	while ((segPtr != NULL) && (segPtr->lineNum == lineNum)) {
+	    if (segPtr->height > maxHeight) {
+		maxHeight = segPtr->height;
+	    }
+	    segPtr->x = x;
+	    x += segPtr->width;
+	    segPtr = segPtr->nextPtr;
+	}
+	if (x > maxWidth) {
+	    maxWidth = x;
+	}
+	segPtr = startPtr;
+	while ((segPtr != NULL) && (segPtr->lineNum == lineNum)) {
+	    segPtr->y = y + (maxHeight - segPtr->height) / 2;
+	    segPtr = segPtr->nextPtr;
+	}
+	y += maxHeight;
+    }
+    itemPtr->width = maxWidth;
+    itemPtr->height = y;
+    ComputeBbox(itemPtr);
+}
+
+static int
+ParseDescription(Tcl_Interp *interp, HotspotItem *itemPtr, 
+		const char *description)
+{
+    const char *p, *start;
+
+    if (itemPtr->firstPtr != NULL) {
+	DestroySegments(itemPtr);
+    }
+    start = description;
+    itemPtr->numLines = 1;
+    for (p = description; *p != '\0'; p++) {
+	if ((p[0] == '$') && (p[1] == '{')) {
+	    const char *varName;
+	    const char *q;
+
+	    if (p > start) {
+		/* Save the leading text. */
+		AddTextSegment(itemPtr, start, p - start);
+		start = p;
+	    }
+	    /* Start of substitution variable. */
+	    varName = p + 2;
+	    for (q = varName; q != '\0'; q++) {
+		if (*q == '}') {
+		    AddValueSegment(itemPtr, varName, q - varName);
+		    p = q;
+		    start = p + 1;
+		    goto next;
+		}
+	    }
+	    Tcl_AppendResult(interp, "back variable specification in \"",
+			     description, "\"", (char *)NULL);
+	    return TCL_ERROR;
+	} else if (p[0] == '\n') {
+	    if (p > start) {
+		AddTextSegment(itemPtr, start, p - start);
+		start = p + 1;
+		itemPtr->numLines++;
+	    }
+	}
+    next:
+	;
+    }
+    if (p > start) {
+	/* Save the trailing text. */
+	AddTextSegment(itemPtr, start, p - start);
+    }
+    itemPtr->flags |= SUBSTITUTIONS_PENDING;
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  CreateProc --
+ *
+ *    This procedure is invoked to create a new hotspot item
+ *    in a canvas.  A hotspot is a text item with a box behind it.
+ *
+ *  Results:
+ *    A standard Tcl return name.  If an error occurred in creating
+ *    the item then an error message is left in the interp's result.
+ *    In this case itemPtr is left uninitialized so it can be safely
+ *    freed by the caller.
+ *
+ *  Side effects:
+ *    A new item is created.
+ *
+ * ------------------------------------------------------------------------
+ */
+static int
+CreateProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr,
+	   int objc, Tcl_Obj *const *objv)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+    double x, y;
+    Tk_Window tkwin;
+
+    tkwin = Tk_CanvasTkwin(canvas);
+    if (objc < 2) {
+	Tcl_AppendResult(interp, "wrong # args: should be \"",
+	    Tk_PathName(tkwin), " create ", canvItemPtr->typePtr->name,
+	    " x y ?options?\"", (char *)NULL);
+	return TCL_ERROR;
+    }
+
+    /* Initialize all HotspotItem fields, expect for the the base. */
+    memset((char *)itemPtr + sizeof(Tk_Item), 0, 
+	   sizeof(HotspotItem) - sizeof(Tk_Item));
+    /*
+     * Carry out initialization that is needed in order to clean up after
+     * errors during the the remainder of this procedure.
+     */
+    itemPtr->anchor = TK_ANCHOR_CENTER;
+    itemPtr->canvas = canvas;
+    itemPtr->display = Tk_Display(tkwin);
+    itemPtr->interp = interp;
+    itemPtr->relief = TK_RELIEF_FLAT;
+    itemPtr->tkwin = tkwin;
+    itemPtr->valueInterp = interp;
+    itemPtr->showIcons = FALSE;
+
+    /* Process the arguments to fill in the item record. */
+    if ((GetCoordFromObj(interp, itemPtr, objv[0], &x) != TCL_OK) ||
+	(GetCoordFromObj(interp, itemPtr, objv[1], &y) != TCL_OK)) {
+	DeleteProc(canvas, canvItemPtr, itemPtr->display);
+	return TCL_ERROR;
+    }
+    itemPtr->x = x;
+    itemPtr->y = y;
+    ComputeBbox(itemPtr);
+    if (ConfigureProc(interp, canvas, canvItemPtr, objc - 2, objv + 2, 0) 
+	!= TCL_OK) {
+	DeleteProc(canvas, canvItemPtr, itemPtr->display);
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  CoordsProc --
+ *
+ *    This procedure is invoked to process the "coords" widget command on
+ *    hotspot items.  See the user documentation for details on what
+ *    it does.
+ *
+ *  Results:
+ *    Returns TCL_OK or TCL_ERROR, and sets the interp's result.
+ *
+ *  Side effects:
+ *    The coordinates for the given item may be changed.
+ *
+ * ------------------------------------------------------------------------
+ */
+static int
+CoordsProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr, 
+	   int objc, Tcl_Obj *const *objv)
+{
+    HotspotItem *itemPtr = (HotspotItem *) canvItemPtr;
+    double x, y;
+
+    if (objc == 0) {
+        Tcl_Obj *listObjPtr;
+
+	listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+        Tcl_ListObjAppendElement(interp, listObjPtr,
+		Tcl_NewDoubleObj(itemPtr->x));
+        Tcl_ListObjAppendElement(interp, listObjPtr,
+		Tcl_NewDoubleObj(itemPtr->y));
+        Tcl_SetObjResult(interp, listObjPtr);
+	return TCL_OK;
+    }
+    if (objc == 1) {
+	int elc;
+	Tcl_Obj **elv;
+
+	if (Tcl_ListObjGetElements(interp, objv[0], &elc, &elv) != TCL_OK) {
+	    return TCL_ERROR;
+        }
+        if ((GetCoordFromObj(interp, itemPtr, elv[0], &x) != TCL_OK) || 
+	    (GetCoordFromObj(interp, itemPtr, elv[1], &y) != TCL_OK)) {
+            return TCL_ERROR;
+        }
+    } else if (objc == 3) {
+        if ((GetCoordFromObj(interp, itemPtr, objv[0], &x) != TCL_OK) ||
+	    (GetCoordFromObj(interp, itemPtr, objv[1], &y) != TCL_OK)) {
+            return TCL_ERROR;
+        }
+    } else {
+	char buf[64 + TCL_INTEGER_SPACE];
+
+        sprintf(buf, "wrong # coordinates: expected 0 or 2, got %d", objc);
+        Tcl_AppendResult(interp, buf, (char *)NULL);
+        return TCL_ERROR;
+    }
+    itemPtr->x = x;
+    itemPtr->y = y;
+    ComputeBbox(itemPtr);
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  ConfigureProc --
+ *
+ *    This procedure is invoked to configure various aspects of a
+ *    hotspot item, such as its color and font.
+ *
+ *  Results:
+ *    A standard Tcl result code.  If an error occurs, then an error
+ *    message is left in the interp's result.
+ *
+ *  Side effects:
+ *    Configuration information, such as colors and GCs, may be set
+ *    for itemPtr.
+ * ------------------------------------------------------------------------
+ */
+static int
+ConfigureProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr,
+	      int objc, Tcl_Obj *CONST *objv, int flags)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+    XGCValues gcValues;
+    GC newGC;
+    unsigned long mask;
+    Tk_Window tkwin;
+    Tk_Image tkImage;
+
+    tkwin = Tk_CanvasTkwin(canvas);
+    if (Tk_ConfigureWidget(interp, tkwin, configSpecs, objc, 
+	(CONST char **)objv, (char*)itemPtr, flags|TK_CONFIG_OBJS) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    /* Normal text GC. */
+    mask = GCFont | GCForeground;
+    gcValues.font = Tk_FontId(itemPtr->font);
+    gcValues.foreground = itemPtr->textColor->pixel;
+    newGC = Tk_GetGC(tkwin, mask, &gcValues);
+    if (itemPtr->normalGC != None) {
+        Tk_FreeGC(Tk_Display(tkwin), itemPtr->normalGC);
+    }
+    itemPtr->normalGC = newGC;
+
+    /* Outline GC */
+    if (itemPtr->outlineColor != NULL) {
+	mask = GCLineWidth | GCForeground;
+	gcValues.foreground = itemPtr->outlineColor->pixel;
+	gcValues.line_width = 1;
+	newGC = Tk_GetGC(tkwin, mask, &gcValues);
+	if (itemPtr->outlineGC != None) {
+	    Tk_FreeGC(Tk_Display(tkwin), itemPtr->outlineGC);
+	}
+	itemPtr->outlineGC = newGC;
+    }
+
+    /* Substituted value GC. */
+    mask = GCFont | GCForeground;
+    gcValues.font = Tk_FontId(itemPtr->valueFont);
+    gcValues.foreground = itemPtr->valueColor->pixel;
+    newGC = Tk_GetGC(tkwin, mask, &gcValues);
+    if (itemPtr->valueGC != None) {
+        Tk_FreeGC(Tk_Display(tkwin), itemPtr->valueGC);
+    }
+    itemPtr->valueGC = newGC;
+
+    /* Active substituted value GC. */
+    mask = GCFont | GCForeground;
+    gcValues.font = Tk_FontId(itemPtr->valueFont);
+    gcValues.foreground = itemPtr->activeColor->pixel;
+    newGC = Tk_GetGC(tkwin, mask, &gcValues);
+    if (itemPtr->activeGC != None) {
+        Tk_FreeGC(Tk_Display(tkwin), itemPtr->activeGC);
+    }
+    itemPtr->activeGC = newGC;
+
+    itemPtr->maxImageWidth = itemPtr->maxImageHeight = 0;
+    /* Normal hotspot image. */
+    if (itemPtr->imageName != NULL) {
+	int iw, ih;
+
+        tkImage = Tk_GetImage(interp, tkwin, itemPtr->imageName,
+                ImageChangedProc, (ClientData)itemPtr);
+        if (tkImage == NULL) {
+            return TCL_ERROR;
+        }
+	Tk_SizeOfImage(tkImage, &iw, &ih);
+	if (iw > itemPtr->maxImageWidth) {
+	    itemPtr->maxImageWidth = iw;
+	}
+	if (ih > itemPtr->maxImageHeight) {
+	    itemPtr->maxImageHeight = ih;
+	}
+    } else {
+	tkImage = NULL;
+    }
+	
+    if (itemPtr->image != NULL) {
+        Tk_FreeImage(itemPtr->image);
+    }
+    itemPtr->image = tkImage;
+
+    /* Active hotspot image. */
+    if (itemPtr->activeImageName != NULL) {
+	int iw, ih;
+	
+        tkImage = Tk_GetImage(interp, tkwin, itemPtr->activeImageName,
+                ImageChangedProc, (ClientData)itemPtr);
+        if (tkImage == NULL) {
+            return TCL_ERROR;
+        }
+	Tk_SizeOfImage(tkImage, &iw, &ih);
+	if (iw > itemPtr->maxImageWidth) {
+	    itemPtr->maxImageWidth = iw;
+	}
+	if (ih > itemPtr->maxImageHeight) {
+	    itemPtr->maxImageHeight = ih;
+	}
+    } else {
+	tkImage = NULL;
+    } 
+    if (itemPtr->activeImage != NULL) {
+        Tk_FreeImage(itemPtr->activeImage);
+    }
+    itemPtr->activeImage = tkImage;
+
+    if (itemPtr->text != NULL) {
+	if (ParseDescription(interp, itemPtr, itemPtr->text) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+    }
+    ComputeBbox(itemPtr);
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  DeleteHotspot --
+ *
+ *    This procedure is called to clean up the data structure
+ *    associated with a hotspot item.
+ *
+ *  Results:
+ *    None.
+ *
+ *  Side effects:
+ *    Resources associated with itemPtr are released.
+ * ------------------------------------------------------------------------
+ */
+static void
+DeleteProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, Display *display)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+
+    if (itemPtr->textColor != NULL) {
+        Tk_FreeColor(itemPtr->textColor);
+    }
+    if (itemPtr->valueColor != NULL) {
+        Tk_FreeColor(itemPtr->valueColor);
+    }
+    if (itemPtr->activeColor != NULL) {
+        Tk_FreeColor(itemPtr->activeColor);
+    }
+    if (itemPtr->outlineColor != NULL) {
+        Tk_FreeColor(itemPtr->outlineColor);
+    }
+    if (itemPtr->border != NULL) {
+        Tk_Free3DBorder(itemPtr->border);
+    }
+    if (itemPtr->activeBorder != NULL) {
+        Tk_Free3DBorder(itemPtr->activeBorder);
+    }
+    if (itemPtr->text != NULL) {
+        Tcl_Free((char *)itemPtr->text);
+    }
+    if (itemPtr->imageName != NULL) {
+        Tcl_Free((char *)itemPtr->imageName);
+    }
+    if (itemPtr->activeImageName != NULL) {
+        Tcl_Free((char *)itemPtr->activeImageName);
+    }
+    if (itemPtr->image != NULL ) {
+        Tk_FreeImage(itemPtr->image);
+    }
+    if (itemPtr->activeImage != NULL ) {
+        Tk_FreeImage(itemPtr->activeImage);
+    }
+    if (itemPtr->font != NULL) {
+	Tk_FreeFont(itemPtr->font);
+    }
+    if (itemPtr->valueFont != NULL) {
+	Tk_FreeFont(itemPtr->valueFont);
+    }
+    if (itemPtr->normalGC != None) {
+        Tk_FreeGC(display, itemPtr->normalGC);
+    }
+    if (itemPtr->activeGC != None) {
+        Tk_FreeGC(display, itemPtr->activeGC);
+    }
+    if (itemPtr->outlineGC != None) {
+        Tk_FreeGC(display, itemPtr->outlineGC);
+    }
+    if (itemPtr->valueGC != None) {
+        Tk_FreeGC(display, itemPtr->valueGC);
+    }
+    DestroySegments(itemPtr);
+}
+
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TranslateAnchor --
+ *
+ * 	Translate the coordinates of a given bounding box based upon the
+ * 	anchor specified.  The anchor indicates where the given xy position
+ * 	is in relation to the bounding box.
+ *
+ *  		nw --- n --- ne
+ *  		|            |     x,y ---+
+ *  		w   center   e      |     |
+ *  		|            |      +-----+
+ *  		sw --- s --- se
+ *
+ * Results:
+ *	The translated coordinates of the bounding box are returned.
+ *
+ *---------------------------------------------------------------------------
+ */
+static void
+TranslateAnchor(HotspotItem *itemPtr, double *xPtr, double *yPtr)
+{
+    double x, y;
+
+    x = *xPtr, y = *yPtr;
+    switch (itemPtr->anchor) {
+    case TK_ANCHOR_NW:		/* Upper left corner */
+	break;
+    case TK_ANCHOR_W:		/* Left center */
+	y -= (itemPtr->height * 0.5);
+	break;
+    case TK_ANCHOR_SW:		/* Lower left corner */
+	y -= itemPtr->height;
+	break;
+    case TK_ANCHOR_N:		/* Top center */
+	x -= (itemPtr->width * 0.5);
+	break;
+    case TK_ANCHOR_CENTER:	/* Centered */
+	x -= (itemPtr->width * 0.5);
+	y -= (itemPtr->height * 0.5);
+	break;
+    case TK_ANCHOR_S:		/* Bottom center */
+	x -= (itemPtr->width * 0.5);
+	y -= itemPtr->height;
+	break;
+    case TK_ANCHOR_NE:		/* Upper right corner */
+	x -= itemPtr->width;
+	break;
+    case TK_ANCHOR_E:		/* Right center */
+	x -= itemPtr->width;
+	y -= (itemPtr->height * 0.5);
+	break;
+    case TK_ANCHOR_SE:		/* Lower right corner */
+	x -= itemPtr->width;
+	y -= itemPtr->height;
+	break;
+    }
+    *xPtr = x;
+    *yPtr = y;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  ComputeBbox --
+ *
+ *    This procedure is invoked to compute the bounding box of all the pixels
+ *    that may be drawn as part of a text item.  In addition, it recomputes
+ *    all of the geometry information used to display a text item or check for
+ *    mouse hits.
+ *
+ *  Results:
+ *    None.
+ *
+ *  Side effects:
+ *    The fields x1, y1, x2, and y2 are updated in the base structure
+ *    for itemPtr, and the linePtr structure is regenerated
+ *    for itemPtr.
+ *
+ * ------------------------------------------------------------------------
+ */
+static void
+ComputeBbox(HotspotItem *itemPtr)
+{
+    double x, y;
+
+    x = itemPtr->x, y = itemPtr->y;
+    TranslateAnchor(itemPtr, &x, &y);
+    itemPtr->x1 = x;
+    itemPtr->x2 = x + itemPtr->width;
+    itemPtr->y1 = y;
+    itemPtr->y2 = y + itemPtr->height;
+    itemPtr->base.x1 = ROUND(itemPtr->x1);
+    itemPtr->base.y1 = ROUND(itemPtr->y1);
+    itemPtr->base.x2 = ROUND(itemPtr->x2);
+    itemPtr->base.y2 = ROUND(itemPtr->y2);
+}
+
+static void 
+DrawSegment(ItemSegment *segPtr, Drawable drawable, int x, int y)
+{
+    HotspotItem *itemPtr;
+    GC gc;
+    int isActive;
+
+    itemPtr = segPtr->itemPtr;
+    if (segPtr->type == SEGMENT_TEXT) {
+	Tk_DrawTextLayout(Tk_Display(itemPtr->tkwin), drawable, 
+		itemPtr->normalGC, segPtr->layout, x + segPtr->x + TEXT_PADX, 
+		y + segPtr->y + TEXT_PADY, 0, -1);
+	return;
+    } 
+    isActive = ((itemPtr->activeValue != NULL) &&
+		(strcmp(itemPtr->activeValue, segPtr->text) == 0));
+	
+    if ((itemPtr->activeBorder != NULL) && (isActive)) {
+	Tk_Fill3DRectangle(itemPtr->tkwin, drawable, itemPtr->activeBorder, 
+		x + segPtr->x, y + segPtr->y, segPtr->width, segPtr->height, 
+		1, TK_RELIEF_SOLID);
+    }
+    if (itemPtr->showIcons) {
+	Tk_Image tkImage;
+	int w, h;
+	int ix, iy, ih, iw;
+
+	ih = itemPtr->maxImageHeight * 2 * IMAGE_PAD;
+	iw = itemPtr->maxImageWidth + IMAGE_PAD;
+	ix = x + segPtr->x + IMAGE_PAD;
+	iy = y + segPtr->y + IMAGE_PAD;
+	if (segPtr->height > ih) {
+	    iy += (segPtr->height - ih) / 2;
+	}
+	tkImage = (isActive) ? itemPtr->activeImage : itemPtr->image;
+	Tk_SizeOfImage(tkImage, &w, &h);
+	Tk_RedrawImage(tkImage, 0, 0, w, h, drawable, ix, iy);
+	x += iw;
+    }
+    gc = (isActive) ? itemPtr->activeGC : itemPtr->valueGC;
+    Tk_DrawTextLayout(Tk_Display(itemPtr->tkwin), drawable, gc, segPtr->layout,
+	 x + segPtr->x + TEXT_PADX, y + segPtr->y + TEXT_PADY, 0, -1);
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  DisplayProc --
+ *
+ *    This procedure is invoked to draw a text item in a given drawable.
+ *
+ *  Results:
+ *    None.
+ *
+ *  Side effects:
+ *    ItemPtr is drawn in drawable using the transformation information
+ *    in canvas.
+ *
+ * ------------------------------------------------------------------------
+ */
+static void
+DisplayProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, Display *display,
+	    Drawable drawable, int x, int y, int width, int height)
+{
+    HotspotItem *itemPtr = (HotspotItem *) canvItemPtr;
+    ItemSegment *segPtr;
+    short int drawX, drawY;
+    double tx, ty;
+
+    itemPtr->flags &= ~REDRAW_PENDING;
+    if (itemPtr->flags & SUBSTITUTIONS_PENDING) {
+	DoSubstitutions(itemPtr);
+    }
+    if (itemPtr->flags & LAYOUT_PENDING) {
+	ComputeGeometry(itemPtr);
+    }
+    tx = itemPtr->x, ty = itemPtr->y;
+    TranslateAnchor(itemPtr, &tx, &ty);
+
+    /* Convert canvas coordinates to drawable coordinates. */
+    Tk_CanvasDrawableCoords(canvas, tx, ty, &drawX, &drawY);
+    x = drawX, y = drawY;
+
+    if (itemPtr->border != NULL) {
+        Tk_Fill3DRectangle(itemPtr->tkwin, drawable, itemPtr->border, x, y, 
+		itemPtr->width, itemPtr->height, itemPtr->borderWidth, 
+		itemPtr->relief);
+    }
+    if (itemPtr->outlineColor != NULL) {
+        XDrawRectangle(display, drawable, itemPtr->outlineGC, x, y, 
+		itemPtr->width, itemPtr->height);
+    }
+    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
+	DrawSegment(segPtr, drawable, x, y);
+    }
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  PointProc --
+ *
+ *    Computes the distance from a given point to a given hotspot item, in
+ *    canvas units.
+ *
+ *  Results:
+ *    The return value is 0 if the point whose x and y coordinates
+ *    are pointPtr[0] and pointPtr[1] is inside the text item.  If
+ *    the point isn't inside the text item then the return value
+ *    is the distance from the point to the text item.
+ *
+ *  Side effects:
+ *    None.
+ *
+ * ------------------------------------------------------------------------
+ */
+static double
+PointProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double *pointPtr)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+    double dx, dy;
+
+    if (pointPtr[0] < itemPtr->x1) {
+        dx = itemPtr->x1 - pointPtr[0];
+    } else if (pointPtr[0] >= itemPtr->x2)  {
+        dx = pointPtr[0] + 1 - itemPtr->x2;
+    } else {
+        dx = 0;
+    }
+    if (pointPtr[1] < itemPtr->y1) {
+        dy = itemPtr->y1 - pointPtr[1];
+    } else if (pointPtr[1] >= itemPtr->y2)  {
+        dy = pointPtr[1] + 1 - itemPtr->y2;
+    } else {
+        dy = 0;
+    }
+    return hypot(dx, dy);
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  AreaProc --
+ *
+ *    This procedure is called to determine whether an item lies
+ *    entirely inside, entirely outside, or overlapping a given
+ *    rectangle.
+ *
+ *  Results:
+ *    -1 is returned if the item is entirely outside the area given
+ *    by rectPtr, 0 if it overlaps, and 1 if it is entirely inside
+ *    the given area.
+ *
+ *  Side effects:
+ *    None.
+ *
+ * ------------------------------------------------------------------------
+ */
+static int
+AreaProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double *rectPtr)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+
+    if ((rectPtr[2] <= itemPtr->x1) || (rectPtr[0] >= itemPtr->x2) || 
+	(rectPtr[3] <= itemPtr->y1) || (rectPtr[1] >= itemPtr->y2)) {
+	return -1;
+    }
+    if ((rectPtr[0] <= itemPtr->x1) && (rectPtr[1] <= itemPtr->y1) && 
+	(rectPtr[2] >= itemPtr->x2) && (rectPtr[3] >= itemPtr->y2)) {
+	return 1;
+    }
+    return 0;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  ScaleProc --
+ *
+ *    This procedure is invoked to rescale a text item.
+ *
+ * Results:
+ *    None.
+ *
+ * Side effects:
+ *    Scales the position of the text, but not the size of the font
+ *    for the text.
+ *
+ * ------------------------------------------------------------------------
+ */
+/* ARGSUSED */
+static void
+ScaleProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double x, double y, 
+	  double scaleX, double scaleY)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+
+    itemPtr->x = x + scaleX * (itemPtr->x - x);
+    itemPtr->y = y + scaleY * (itemPtr->y - y);
+    ComputeBbox(itemPtr);
+    return;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  TranslateProc --
+ *
+ *    This procedure is called to move a text item by a given amount.
+ *
+ *  Results:
+ *    None.
+ *
+ *  Side effects:
+ *    The position of the text item is offset by (xDelta, yDelta),
+ *    and the bounding box is updated in the generic part of the
+ *    item structure.
+ *
+ * ------------------------------------------------------------------------
+ */
+static void
+TranslateProc(Tk_Canvas canvas, Tk_Item *canvItemPtr, double dx,  double dy)
+{
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+
+    itemPtr->x += dx;
+    itemPtr->y += dy;
+    ComputeBbox(itemPtr);
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *
+ *  PostscriptProc --
+ *
+ *    This procedure is called to generate Postscript for hotspot items.
+ *
+ *  Results:
+ *    The return value is a standard Tcl result.  If an error occurs in
+ *    generating Postscript then an error message is left in the interp's
+ *    result, replacing whatever used to be there.  If no error occurs, then
+ *    Postscript for the item is appended to the result.
+ *
+ *  Side effects:
+ *    None.
+ *
+ * ------------------------------------------------------------------------
+ */
+static int
+PostscriptProc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *canvItemPtr, 
+	       int prepass)
+{
+#ifdef notdef
+    HotspotItem *itemPtr = (HotspotItem *)canvItemPtr;
+    Tk_Window canvasWin = Tk_CanvasTkwin(canvas);
+
+    int x, y;
+    double xpos;
+    Tk_FontMetrics fm;
+    char *justify;
+    char buffer[500];
+    int delta;
+
+    if (itemPtr->textColor == NULL || itemPtr->text == NULL
+          || *itemPtr->text == 0) {
+        return TCL_OK;
+    }
+
+    if (Tk_CanvasPsFont(interp, canvas, itemPtr->font) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+
+    if (!prepass && 
+	Tk_CanvasPsColor(interp, canvas, itemPtr->textColor) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    xpos = itemPtr->x;
+
+    /*
+     * Check for an image to print.
+     */
+
+    if (itemPtr->imageLeft) {
+      if(!prepass) {
+	delta = (itemPtr->base.y2 - itemPtr->base.y1)/2 - itemPtr->imageLeftH/2;
+	sprintf(buffer, "%.15g %.15g", xpos,
+		Tk_CanvasPsY(canvas, itemPtr->y+delta+2));
+	Tcl_AppendResult(interp, buffer, " translate\n", (char *) NULL);
+      }
+
+      Tk_PostscriptImage(itemPtr->imageLeft, interp, canvasWin,
+			 ((TkCanvas*)canvas)->psInfo, 0, 0,
+                         itemPtr->imageLeftW, itemPtr->imageLeftH, prepass);
+
+      if ( !prepass ) {
+	/*
+	 * This PS code is needed in order for the label to display
+	 * correctly
+	 */
+	
+	Tcl_AppendResult(interp,"grestore\ngsave\n",(char *)NULL);
+      }
+
+      xpos += itemPtr->imageLeftW + itemPtr->padding;
+    }
+
+    
+    if (prepass != 0 ) {
+      return TCL_OK;
+    }
+
+    /*
+     * Before drawing the text, reset the font and color information.
+     * Otherwise the text won't appear.
+     */
+
+    if ( itemPtr->imageLeft ){
+      if (Tk_CanvasPsFont(interp, canvas, itemPtr->font) != TCL_OK) {
+        return TCL_ERROR;
+      }
+      
+      if (Tk_CanvasPsColor(interp, canvas, itemPtr->textColor) != TCL_OK) {
+        return TCL_ERROR;
+      }
+    }
+
+
+    /*
+     * Draw the text for the hotspot.
+     */
+    sprintf(buffer, "%.15g %.15g [\n", xpos,
+        Tk_CanvasPsY(canvas, itemPtr->y));
+    Tcl_AppendResult(interp, buffer, (char *) NULL);
+
+    Tk_TextLayoutToPostscript(interp, itemPtr->textLayout);
+
+    x = 0;  y = 0;  justify = NULL;
+    switch (itemPtr->anchor) {
+        case TK_ANCHOR_NW:        x = 0; y = 0;        break;
+        case TK_ANCHOR_N:         x = 1; y = 0;        break;
+        case TK_ANCHOR_NE:        x = 2; y = 0;        break;
+        case TK_ANCHOR_E:         x = 2; y = 1;        break;
+        case TK_ANCHOR_SE:        x = 2; y = 2;        break;
+        case TK_ANCHOR_S:         x = 1; y = 2;        break;
+        case TK_ANCHOR_SW:        x = 0; y = 2;        break;
+        case TK_ANCHOR_W:         x = 0; y = 1;        break;
+        case TK_ANCHOR_CENTER:    x = 1; y = 1;        break;
+    }
+    justify = "0"; /* TK_JUSTIFY_LEFT */
+
+    Tk_GetFontMetrics(itemPtr->font, &fm);
+    sprintf(buffer, "] %d %g %g %s %s DrawText\n",
+            fm.linespace, x / -2.0, y / 2.0, justify,
+            "false" /* stipple */);
+    Tcl_AppendResult(interp, buffer, (char *) NULL);
+#endif
+    return TCL_OK;
+}
+
+static HotspotItem *
+GetHotspotItem(Tcl_Interp *interp, Tcl_Obj *canvasObjPtr, Tcl_Obj *itemObjPtr)
+{
+    const char *string;
+    Tk_Item *canvItemPtr;
+    HotspotItem *itemPtr;
+    Tcl_CmdInfo cmdInfo;
+    TkCanvas *canvasPtr;
+    int id;
+    Tcl_HashEntry *hPtr;
+    Tk_Window tkwin;
+
+    /* See if we can find the canvas window associated by the name. */
+    string = Tcl_GetString(canvasObjPtr);
+    tkwin = Tk_NameToWindow(interp, string, Tk_MainWindow(interp));
+    if (tkwin == NULL) {
+	Tcl_AppendResult(interp, "can't find canvas \"", string, "\"",
+			 (char *)NULL);
+	return NULL;
+    }
+    if (strcmp(Tk_Class(tkwin), "Canvas") != 0) {
+	Tcl_AppendResult(interp, "window \"", string, "\" isn't a canvas.",
+			 (char *)NULL);
+	return NULL;
+    }
+    /* If we're this far, then we can try to get the clientData. If the widget
+    * command was renamed, we're out of luck. */
+    if (!Tcl_GetCommandInfo(interp, string, &cmdInfo)) {
+	Tcl_AppendResult(interp, "can't find canvas \"", string, "\"",
+			 (char *)NULL);
+	return NULL;
+    }
+    canvasPtr = cmdInfo.objClientData;
+    if (Tcl_GetIntFromObj(interp, itemObjPtr, &id) != TCL_OK) {
+	return NULL;
+    }
+    string = Tcl_GetString(itemObjPtr);
+    hPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *)id);
+    if (hPtr == NULL) {
+	Tcl_AppendResult(interp, "can't find canvas item \"", string,
+		"\"", (char *)NULL);
+	return NULL;
+
+    }
+    canvItemPtr = Tcl_GetHashValue(hPtr);
+    /* The canvas item we find has to be a hotspot. */
+    if (canvItemPtr->typePtr != &hotspotType) {
+	Tcl_AppendResult(interp, "bad canvas item \"", string, 
+		"\": must be a hotspot", (char *)NULL);
+	return NULL;
+
+    }
+    return itemPtr = (HotspotItem *)canvItemPtr;
+}
+
+static const char *
+Identify(Tcl_Interp *interp, HotspotItem *itemPtr, double x, double y)
+{
+    ItemSegment *segPtr;
+
+    x -= itemPtr->x1;
+    y -= itemPtr->y1;
+    for (segPtr = itemPtr->firstPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
+	if (segPtr->type == SEGMENT_TEXT) {
+	    continue;			/* Ignore text segments. */
+	}
+	if ((x >= segPtr->x) && (x < (segPtr->x + segPtr->width)) && 
+	    (y >= segPtr->y) && (y < (segPtr->y + segPtr->height))) {
+	    return segPtr->text;
+	}
+    }
+    return "";
+}
+
+static int
+HotspotCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+	   Tcl_Obj *const *objv)
+{
+    const char *string;
+    HotspotItem *itemPtr;
+    int length;
+    char c;
+
+    if (objc < 3) {
+	return TCL_ERROR;
+    }
+    itemPtr = GetHotspotItem(interp, objv[2], objv[3]);
+    if (itemPtr == NULL) {
+	return TCL_ERROR;
+    }
+    string = Tcl_GetStringFromObj(objv[1], &length);
+    c = string[0];
+    if ((c == 'a') && (strncmp(string, "activate", length) == 0)) {
+	/* hotspot activate .c 0 varName */
+    } else if ((c == 'd') && (strncmp(string, "deactivate", length) == 0)) {
+	/* hotspot deactivate .c 0 */
+	itemPtr->activeValue = NULL;
+    } else if ((c == 'i') && (strncmp(string, "identify", length) == 0)) {
+	double x, y;
+	const char *token;
+	Tcl_Obj *objPtr;
+
+	/* hotspot identify .c 0 x y */
+	if ((Tcl_GetDoubleFromObj(interp, objv[4], &x) != TCL_OK) ||
+	    (Tcl_GetDoubleFromObj(interp, objv[5], &y) != TCL_OK)) {
+	    return TCL_ERROR;
+	}
+	token = Identify(interp, itemPtr, x, y);
+	objPtr = Tcl_NewStringObj(token, -1);
+        Tcl_SetObjResult(interp, objPtr);
+    } else if ((c == 'v') && (strncmp(string, "variables", length) == 0)) {
+        Tcl_Obj *listObjPtr;
+	ItemSegment *segPtr;
+
+	/* hotspot variables .c 0 */
+	listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+	for (segPtr = itemPtr->firstPtr; segPtr != NULL; 
+	     segPtr = segPtr->nextPtr) {
+	    if (segPtr->type == SEGMENT_VALUE) {
+		Tcl_Obj *objPtr;
+
+		objPtr = Tcl_NewStringObj(segPtr->text, -1);
+		Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+	    }
+	}
+        Tcl_SetObjResult(interp, listObjPtr);
+    } else {
+	Tcl_AppendResult(interp, "unknown hotspot option \"", string, 
+		"\": should be either activate, deactivate, identity, ",
+		"or variables", (char *)NULL);
+	return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpCanvHotspot_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "hotspot" item on the Tk canvas widget.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpCanvHotspot_Init(interp)
+    Tcl_Interp *interp;         /* interpreter being initialized */
+{
+    Tk_CreateItemType(&hotspotType);
+    Tcl_CreateObjCommand(interp, "Rappture::hotspot", HotspotCmd, NULL, NULL);
+    return TCL_OK;
+}
+
Index: branches/blt4/gui/src/RpDiffview.c
===================================================================
--- branches/blt4/gui/src/RpDiffview.c	(revision 3091)
+++ branches/blt4/gui/src/RpDiffview.c	(revision 3102)
@@ -2774,4 +2774,5 @@
         layoutPtr->lines = (DiffviewLayoutLine*)ckalloc(
             (unsigned)(layoutPtr->numLines * sizeof(DiffviewLayoutLine)));
+        layoutPtr->maxLines = layoutPtr->numLines;
     }
     layoutPtr->numLines = 0;
