Index: /branches/r9/packages/CanvasHotspot/RpCanvHotspot.c
===================================================================
--- /branches/r9/packages/CanvasHotspot/RpCanvHotspot.c	(revision 4841)
+++ /branches/r9/packages/CanvasHotspot/RpCanvHotspot.c	(revision 4841)
@@ -0,0 +1,1846 @@
+
+/*
+ * ----------------------------------------------------------------------
+ *  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-2012  HUBzero Foundation, LLC
+ *
+ *  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;
+    long lid;
+    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);
+
+    lid = id;
+    hPtr = Tcl_FindHashEntry(&canvasPtr->idTable, (char *)lid);
+    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 Tcl_Obj *
+Identify(Tcl_Interp *interp, HotspotItem *itemPtr, double x, double y)
+{
+    Tcl_Obj* resultPtr = NULL;
+    ItemSegment *segPtr;
+    Tcl_Obj* objPtr;
+
+    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))) {
+
+            /* build return list: {string x0 y0 x1 y1} */
+	    resultPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+
+            objPtr = Tcl_NewStringObj(segPtr->text, -1);
+            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
+
+            objPtr = Tcl_NewIntObj(itemPtr->x1 + segPtr->x);
+            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
+            objPtr = Tcl_NewIntObj(itemPtr->y1 + segPtr->y);
+            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
+
+            objPtr = Tcl_NewIntObj(itemPtr->x1 + segPtr->x + segPtr->width);
+            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
+            objPtr = Tcl_NewIntObj(itemPtr->y1 + segPtr->y + segPtr->height);
+            Tcl_ListObjAppendElement(interp, resultPtr, objPtr);
+
+	    return resultPtr;
+	}
+    }
+    return NULL;
+}
+
+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;
+	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;
+	}
+	objPtr = Identify(interp, itemPtr, x, y);
+        if (objPtr != NULL) {
+            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/r9/packages/CanvasPlacard/RpCanvPlacard.c
===================================================================
--- /branches/r9/packages/CanvasPlacard/RpCanvPlacard.c	(revision 4841)
+++ /branches/r9/packages/CanvasPlacard/RpCanvPlacard.c	(revision 4841)
@@ -0,0 +1,1460 @@
+/*
+ * ----------------------------------------------------------------------
+ *  RpCanvPlacard - 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 placard <x> <y> -anchor <nsew> \
+ *        -text <text>         << text to be displayed
+ *        -font <name>         << font used for text
+ *        -imageleft <image>   << image displayed on the left of text
+ *        -imageright <image>  << image displayed on the right of text
+ *        -maxwidth <size>     << maximum size
+ *        -textcolor <color>   << 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
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  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"
+
+/* used for text within the placard that needs to be truncated */
+#define ellipsis_width 9
+#define ellipsis_height 1
+static unsigned char ellipsis_bits[] = {
+   0x92, 0x00};
+
+/*
+ * Record for each placard item:
+ */
+typedef struct PlacardItem  {
+    Tk_Item header;             /* Generic stuff that's the same for all
+                                 * types.  MUST BE FIRST IN STRUCTURE. */
+    Tcl_Interp *interp;         /* Interp that owns this item */
+    Tk_Window tkwin;            /* Window that represents this canvas */
+    Tk_Canvas canvas;           /* Canvas that owns this item */
+    Pixmap dots;                /* ellipsis used for truncated text */
+
+    /*
+     * Fields that are set by widget commands other than "configure":
+     */
+    double x, y;                /* Positioning point for text */
+
+    /*
+     * Configuration settings that are updated by Tk_ConfigureWidget:
+     */
+    char *text;                 /* text to be displayed */
+    Tk_Anchor anchor;           /* Where to anchor text relative to (x,y) */
+    XColor *textColor;          /* Color for text */
+    XColor *lineColor;          /* Color for outline of rectangle */
+    Tk_3DBorder bgBorder;       /* used for drawing background rectangle */
+    Tk_Font tkfont;             /* Font for drawing text */
+    int width;                  /* Fixed width for placard (0 = not set) */
+    int maxWidth;               /* Maximum overall width for placard */
+    int borderWidth;            /* supports 3D border (drawn under -outline) */
+    int relief;                 /* indicates whether placard as a whole is
+                                 * raised, sunken, or flat */
+    int padding;                /* Padding around text and images */
+
+    char *imageLeftString;      /* Name of image displayed on the left */
+    char *imageRightString;     /* Name of image displayed on the right */
+  
+    /*
+     * Fields whose values are derived from the current values of the
+     * configuration settings above:
+     */
+    int textNumChars;           /* Length of text in characters. */
+    int textNumBytes;           /* Length of text in bytes. */
+    int textDrawChars;          /* Same as textNumChars or shorter, if
+                                 * text is too long for -maxwidth */
+    int textWidth;              /* Overall width of text (perhaps truncated)
+                                 * in pixels */
+    Tk_TextLayout textLayout;   /* Cached text layout information for value */
+
+    Tk_Image imageLeft;         /* icon drawn to left of text */
+    int imageLeftW;             /* width of image (if image is not NULL) */
+    int imageLeftH;             /* height of image (if image is not NULL) */
+
+    Tk_Image imageRight;        /* icon drawn to right of text */
+    int imageRightW;            /* width of image (if image is not NULL) */
+    int imageRightH;            /* height of image (if image is not NULL) */
+
+    int leftEdge;               /* Pixel location of the left edge of the
+                                 * item. (Where the left border of the
+                                 * text layout is drawn.) */
+    int rightEdge;              /* Pixel just to right of right edge of
+                                 * area of text item. */
+    GC gcText;                  /* Graphics context for drawing text. */
+    GC gcLineRect;              /* Graphics context for rectangle outline. */
+} PlacardItem;
+
+/*
+ * Information used for parsing configuration specs:
+ */
+static Tk_CustomOption tagsOption = {
+    (Tk_OptionParseProc *) Tk_CanvasTagsParseProc,
+    Tk_CanvasTagsPrintProc, (ClientData) NULL
+};
+
+static Tk_ConfigSpec configSpecs[] = {
+    {TK_CONFIG_ANCHOR, "-anchor", (char*)NULL, (char*)NULL,
+        "center", Tk_Offset(PlacardItem, anchor),
+        TK_CONFIG_DONT_SET_DEFAULT},
+    {TK_CONFIG_BORDER, "-background", (char*)NULL, (char*)NULL,
+        "", Tk_Offset(PlacardItem, bgBorder), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_PIXELS, "-borderwidth", (char*)NULL, (char*)NULL,
+        "0", Tk_Offset(PlacardItem, borderWidth), 0},
+    {TK_CONFIG_FONT, "-font", (char*)NULL, (char*)NULL,
+        "helvetica -12", Tk_Offset(PlacardItem, tkfont), 0},
+    {TK_CONFIG_COLOR, "-foreground", (char*)NULL, (char*)NULL,
+        "black", Tk_Offset(PlacardItem, textColor), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_STRING, "-imageleft", (char*)NULL, (char*)NULL,
+        (char*)NULL, Tk_Offset(PlacardItem, imageLeftString),
+        TK_CONFIG_NULL_OK},
+    {TK_CONFIG_STRING, "-imageright", (char*)NULL, (char*)NULL,
+        (char*)NULL, Tk_Offset(PlacardItem, imageRightString),
+        TK_CONFIG_NULL_OK},
+    {TK_CONFIG_PIXELS, "-maxwidth", (char*)NULL, (char*)NULL,
+        "0", Tk_Offset(PlacardItem, maxWidth), 0},
+    {TK_CONFIG_COLOR, "-outline", (char*)NULL, (char*)NULL,
+        "", Tk_Offset(PlacardItem, lineColor), TK_CONFIG_NULL_OK},
+    {TK_CONFIG_PIXELS, "-padding", (char*)NULL, (char*)NULL,
+        "4", Tk_Offset(PlacardItem, padding), 0},
+    {TK_CONFIG_RELIEF, "-relief", (char*)NULL, (char*)NULL,
+        "flat", Tk_Offset(PlacardItem, relief), 0},
+    {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(PlacardItem, text), 0},
+    {TK_CONFIG_PIXELS, "-width", (char*)NULL, (char*)NULL,
+        "0", Tk_Offset(PlacardItem, width), 0},
+    {TK_CONFIG_END, (char*)NULL, (char*)NULL, (char*)NULL,
+        (char*)NULL, 0, 0}
+};
+
+/*
+ * Prototypes for procedures defined in this file
+ */
+static void ComputePlacardBbox _ANSI_ARGS_((Tk_Canvas canvas,
+    PlacardItem *plPtr));
+static int ConfigurePlacard _ANSI_ARGS_((Tcl_Interp *interp,
+    Tk_Canvas canvas, Tk_Item *itemPtr, int argc,
+    Tcl_Obj *CONST argv[], int flags));
+static int CreatePlacard _ANSI_ARGS_((Tcl_Interp *interp,
+    Tk_Canvas canvas, struct Tk_Item *itemPtr,
+    int argc, Tcl_Obj *CONST argv[]));
+static void DeletePlacard _ANSI_ARGS_((Tk_Canvas canvas,
+    Tk_Item *itemPtr, Display *display));
+static void DisplayCanvPlacard _ANSI_ARGS_((Tk_Canvas canvas,
+    Tk_Item *itemPtr, Display *display, Drawable dst,
+    int x, int y, int width, int height));
+static void ScalePlacard _ANSI_ARGS_((Tk_Canvas canvas,
+    Tk_Item *itemPtr, double originX, double originY,
+    double scaleX, double scaleY));
+static void TranslatePlacard _ANSI_ARGS_((Tk_Canvas canvas,
+    Tk_Item *itemPtr, double deltaX, double deltaY));
+static int PlacardCoords _ANSI_ARGS_((Tcl_Interp *interp,
+    Tk_Canvas canvas, Tk_Item *itemPtr,
+    int argc, Tcl_Obj *CONST argv[]));
+static int PlacardToArea _ANSI_ARGS_((Tk_Canvas canvas,
+    Tk_Item *itemPtr, double *rectPtr));
+static double PlacardToPoint _ANSI_ARGS_((Tk_Canvas canvas,
+    Tk_Item *itemPtr, double *pointPtr));
+static int PlacardToPostscript _ANSI_ARGS_((Tcl_Interp *interp,
+    Tk_Canvas canvas, Tk_Item *itemPtr, int prepass));
+
+static void ImageLeftChangedProc _ANSI_ARGS_((ClientData clientData,
+    int x, int y, int width, int height, int imgWidth,
+    int imgHeight));
+static void ImageRightChangedProc _ANSI_ARGS_((ClientData clientData,
+    int x, int y, int width, int height, int imgWidth,
+    int imgHeight));
+
+/*
+ * The structures below defines the canvas item type:
+ */
+Tk_ItemType rpPlacardType = {
+    "placard",                          /* name */
+    sizeof(PlacardItem),                /* itemSize */
+    CreatePlacard,                      /* createProc */
+    configSpecs,                        /* configSpecs */
+    ConfigurePlacard,                   /* configureProc */
+    PlacardCoords,                      /* coordProc */
+    DeletePlacard,                      /* deleteProc */
+    DisplayCanvPlacard,                 /* displayProc */
+    TK_CONFIG_OBJS,                     /* flags */
+    PlacardToPoint,                     /* pointProc */
+    PlacardToArea,                      /* areaProc */
+    PlacardToPostscript,                /* postscriptProc */
+    ScalePlacard,                       /* scaleProc */
+    TranslatePlacard,                   /* translateProc */
+    (Tk_ItemIndexProc *) NULL,          /* indexProc */
+    (Tk_ItemCursorProc *) NULL,         /* icursorProc */
+    (Tk_ItemSelectionProc *) NULL,      /* selectionProc */
+    (Tk_ItemInsertProc *) NULL,         /* insertProc */
+    (Tk_ItemDCharsProc *) NULL,         /* dTextProc */
+    (Tk_ItemType *) 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 bgBorder;	/* 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;
+
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpCanvPlacard_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "placard" 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
+RpCanvPlacard_Init(interp)
+    Tcl_Interp *interp;         /* interpreter being initialized */
+{
+    Tk_CreateItemType(&rpPlacardType);
+
+    Tk_DefineBitmap(interp, Tk_GetUid("rp_ellipsis"),
+        (char*)ellipsis_bits, ellipsis_width, ellipsis_height);
+
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  CreatePlacard --
+ *
+ *    This procedure is invoked to create a new placard item
+ *    in a canvas.  A placard 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
+CreatePlacard(interp, canvas, itemPtr, argc, argv)
+    Tcl_Interp *interp;           /* interpreter for error reporting */
+    Tk_Canvas canvas;             /* canvas to hold new item */
+    Tk_Item *itemPtr;             /* record to hold new item; header has been
+                                   * initialized by caller */
+    int argc;                     /* number of arguments in argv */
+    Tcl_Obj *CONST argv[];        /* arguments describing item */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    int i;
+
+    if (argc == 1) {
+        i = 1;
+    } else {
+        char *arg = Tcl_GetStringFromObj(argv[1], NULL);
+        if ((argc > 1) && (arg[0] == '-')
+                && (arg[1] >= 'a') && (arg[1] <= 'z')) {
+            i = 1;
+        } else {
+            i = 2;
+        }
+    }
+
+    if (argc < i) {
+        Tcl_AppendResult(interp, "wrong # args: should be \"",
+            Tk_PathName(Tk_CanvasTkwin(canvas)), " create ",
+            itemPtr->typePtr->name, " x y ?options?\"", (char *) NULL);
+        return TCL_ERROR;
+    }
+
+    /*
+     * Carry out initialization that is needed in order to clean up after
+     * errors during the the remainder of this procedure.
+     */
+    plPtr->interp           = interp;
+    plPtr->tkwin            = Tk_CanvasTkwin(canvas);
+    plPtr->canvas           = canvas;
+    plPtr->dots             = Tk_GetBitmap(interp, plPtr->tkwin,
+                                  Tk_GetUid("rp_ellipsis"));
+    plPtr->text             = NULL;
+    plPtr->anchor           = TK_ANCHOR_CENTER;
+    plPtr->textColor        = NULL;
+    plPtr->bgBorder         = NULL;
+    plPtr->lineColor        = NULL;
+    plPtr->tkfont           = NULL;
+    plPtr->width            = 0;
+    plPtr->maxWidth         = 0;
+    plPtr->borderWidth      = 0;
+    plPtr->relief           = TK_RELIEF_FLAT;
+    plPtr->padding          = 0;
+    plPtr->imageLeftString  = NULL;
+    plPtr->imageRightString = NULL;
+    plPtr->imageLeft        = NULL;
+    plPtr->imageRight       = NULL;
+    plPtr->textNumChars     = 0;
+    plPtr->textNumBytes     = 0;
+    plPtr->textDrawChars    = 0;
+    plPtr->textWidth        = 0;
+    plPtr->textLayout       = NULL;
+    plPtr->leftEdge         = 0;
+    plPtr->rightEdge        = 0;
+    plPtr->gcText           = None;
+    plPtr->gcLineRect       = None;
+
+    /*
+     * Process the arguments to fill in the item record.
+     */
+    if ((PlacardCoords(interp, canvas, itemPtr, i, argv) != TCL_OK)) {
+        goto error;
+    }
+    if (ConfigurePlacard(interp, canvas, itemPtr,
+            argc-i, argv+i, 0) == TCL_OK) {
+
+        return TCL_OK;
+    }
+
+error:
+    DeletePlacard(canvas, itemPtr, Tk_Display(plPtr->tkwin));
+    return TCL_ERROR;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  PlacardCoords --
+ *
+ *    This procedure is invoked to process the "coords" widget command on
+ *    placard 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
+PlacardCoords(interp, canvas, itemPtr, argc, argv)
+    Tcl_Interp *interp;     /* Used for error reporting. */
+    Tk_Canvas canvas;       /* Canvas containing item. */
+    Tk_Item *itemPtr;       /* Item whose coordinates are to be read or
+                             * modified. */
+    int argc;               /* Number of coordinates supplied in argv. */
+    Tcl_Obj *CONST argv[];  /* Array of coordinates: x1, y1, x2, y2, ... */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    char buf[64 + TCL_INTEGER_SPACE];
+
+    if (argc == 0) {
+        Tcl_Obj *obj = Tcl_NewObj();
+        Tcl_Obj *subobj = Tcl_NewDoubleObj(plPtr->x);
+        Tcl_ListObjAppendElement(interp, obj, subobj);
+        subobj = Tcl_NewDoubleObj(plPtr->y);
+        Tcl_ListObjAppendElement(interp, obj, subobj);
+        Tcl_SetObjResult(interp, obj);
+    }
+    else if (argc < 3) {
+        if (argc == 1) {
+            if (Tcl_ListObjGetElements(interp, argv[0], &argc,
+                    (Tcl_Obj ***) &argv) != TCL_OK) {
+                return TCL_ERROR;
+            } else if (argc != 2) {
+
+                sprintf(buf, "wrong # coordinates: expected 2, got %d", argc);
+                Tcl_SetResult(interp, buf, TCL_VOLATILE);
+                return TCL_ERROR;
+            }
+        }
+        if ((Tk_CanvasGetCoordFromObj(interp, canvas, argv[0],
+                   &plPtr->x) != TCL_OK)
+                || (Tk_CanvasGetCoordFromObj(interp, canvas, argv[1],
+                          &plPtr->y) != TCL_OK)) {
+            return TCL_ERROR;
+        }
+        ComputePlacardBbox(canvas, plPtr);
+    } else {
+        sprintf(buf, "wrong # coordinates: expected 0 or 2, got %d", argc);
+        Tcl_SetResult(interp, buf, TCL_VOLATILE);
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  ConfigurePlacard --
+ *
+ *    This procedure is invoked to configure various aspects of a
+ *    placard 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
+ConfigurePlacard(interp, canvas, itemPtr, argc, argv, flags)
+    Tcl_Interp *interp;           /* Interpreter for error reporting. */
+    Tk_Canvas canvas;             /* Canvas containing itemPtr. */
+    Tk_Item *itemPtr;             /* Rectangle item to reconfigure. */
+    int argc;                     /* Number of elements in argv.  */
+    Tcl_Obj *CONST argv[];        /* Args describing things to configure. */
+    int flags;                    /* Flags to pass to Tk_ConfigureWidget. */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    XGCValues gcValues;
+    GC newGC;
+    unsigned long mask;
+    Tk_Window tkwin;
+    XColor *color;
+    Tk_Image image;
+
+    tkwin = Tk_CanvasTkwin(canvas);
+    if (Tk_ConfigureWidget(interp, tkwin, configSpecs,
+            argc, (CONST char **) argv,
+            (char*)plPtr, flags|TK_CONFIG_OBJS) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    /*
+     * A few of the options require additional processing, such as
+     * graphics contexts.
+     */
+    color = plPtr->textColor;
+    newGC = None;
+    if (plPtr->tkfont != NULL) {
+        gcValues.font = Tk_FontId(plPtr->tkfont);
+        mask = GCFont;
+        if (color != NULL) {
+            gcValues.foreground = color->pixel;
+            mask |= GCForeground;
+            newGC = Tk_GetGC(tkwin, mask, &gcValues);
+        }
+    }
+    if (plPtr->gcText != None) {
+        Tk_FreeGC(Tk_Display(tkwin), plPtr->gcText);
+    }
+    plPtr->gcText = newGC;
+
+
+    newGC = None;
+    if (plPtr->lineColor != NULL) {
+        gcValues.foreground = plPtr->lineColor->pixel;
+        gcValues.line_width = 1;
+        mask = GCForeground | GCLineWidth;
+        newGC = Tk_GetGC(tkwin, mask, &gcValues);
+    }
+    if (plPtr->gcLineRect != None) {
+        Tk_FreeGC(Tk_Display(tkwin), plPtr->gcLineRect);
+    }
+    plPtr->gcLineRect = newGC;
+
+
+    if (plPtr->imageLeftString != NULL) {
+        image = Tk_GetImage(interp, tkwin, plPtr->imageLeftString,
+                ImageLeftChangedProc, (ClientData)plPtr);
+        if (image == NULL) {
+            return TCL_ERROR;
+        }
+    } else {
+        image = NULL;
+    }
+    if (plPtr->imageLeft != NULL) {
+        Tk_FreeImage(plPtr->imageLeft);
+    }
+    plPtr->imageLeft = image;
+
+    if (image != NULL) {
+        Tk_SizeOfImage(image,&plPtr->imageLeftW, &plPtr->imageLeftH);
+    }
+
+
+    if (plPtr->imageRightString != NULL) {
+        image = Tk_GetImage(interp, tkwin, plPtr->imageRightString,
+                ImageRightChangedProc, (ClientData)plPtr);
+        if (image == NULL) {
+            return TCL_ERROR;
+        }
+    } else {
+        image = NULL;
+    }
+    if (plPtr->imageRight != NULL) {
+        Tk_FreeImage(plPtr->imageRight);
+    }
+    plPtr->imageRight = image;
+
+    if (image != NULL) {
+        Tk_SizeOfImage(image,&plPtr->imageRightW, &plPtr->imageRightH);
+    }
+
+
+    if (plPtr->text != NULL) {
+        plPtr->textNumBytes = strlen(plPtr->text);
+        plPtr->textNumChars = Tcl_NumUtfChars(plPtr->text, plPtr->textNumBytes);
+    };
+
+    ComputePlacardBbox(canvas, plPtr);
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  DeletePlacard --
+ *
+ *    This procedure is called to clean up the data structure
+ *    associated with a placard item.
+ *
+ *  Results:
+ *    None.
+ *
+ *  Side effects:
+ *    Resources associated with itemPtr are released.
+ * ------------------------------------------------------------------------
+ */
+static void
+DeletePlacard(canvas, itemPtr, display)
+    Tk_Canvas canvas;          /* Info about overall canvas widget. */
+    Tk_Item *itemPtr;          /* Item that is being deleted. */
+    Display *display;          /* Display containing window for canvas. */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+
+    if (plPtr->textColor != NULL) {
+        Tk_FreeColor(plPtr->textColor);
+    }
+    if (plPtr->lineColor != NULL) {
+        Tk_FreeColor(plPtr->lineColor);
+    }
+    if (plPtr->text != NULL) {
+        ckfree(plPtr->text);
+    }
+
+
+    if (plPtr->imageLeftString != NULL) {
+        ckfree(plPtr->imageLeftString);
+    }
+    if (plPtr->imageLeft != NULL ) {
+        Tk_FreeImage(plPtr->imageLeft);
+    }
+    if (plPtr->imageRightString != NULL) {
+        ckfree(plPtr->imageRightString);
+    }
+    if (plPtr->imageRight != NULL ) {
+        Tk_FreeImage(plPtr->imageRight);
+    }
+
+    Tk_FreeTextLayout(plPtr->textLayout);
+
+    Tk_FreeFont(plPtr->tkfont);
+
+    if (plPtr->gcText != None) {
+        Tk_FreeGC(display, plPtr->gcText);
+    }
+    if (plPtr->gcLineRect != None) {
+        Tk_FreeGC(display, plPtr->gcLineRect);
+    }
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  ComputePlacardBbox --
+ *
+ *    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 header
+ *    for itemPtr, and the linePtr structure is regenerated
+ *    for itemPtr.
+ * ------------------------------------------------------------------------
+ */
+static void
+ComputePlacardBbox(canvas, plPtr)
+    Tk_Canvas canvas;           /* Canvas that contains item. */
+    PlacardItem *plPtr;       /* Item whose bbox is to be recomputed. */
+{
+    int leftX, topY, width, height, maxwidth, ellw, ellh;
+
+    /*
+     * Get the layout for the placard text.
+     */
+    plPtr->textDrawChars = plPtr->textNumChars;
+
+    Tk_FreeTextLayout(plPtr->textLayout);
+    plPtr->textLayout = Tk_ComputeTextLayout(plPtr->tkfont,
+            plPtr->text, plPtr->textNumChars, -1,
+            TK_JUSTIFY_LEFT, 0, &width, &height);
+
+    plPtr->textWidth = width;
+
+    if (plPtr->textColor == NULL) {
+        width = height = 0;
+    } else {
+        width += 2*plPtr->padding;
+        height += 2*plPtr->padding;
+    }
+
+    /*
+     * If the current value has an image, add on its width.
+     */
+    if (plPtr->imageLeft) {
+        width += plPtr->imageLeftW;
+        if (plPtr->textWidth > 0) {
+            width += plPtr->padding;
+        }
+        if (plPtr->imageLeftH > height) {
+            height = plPtr->imageLeftH;
+        }
+    }
+    if (plPtr->imageRight) {
+        width += plPtr->imageRightW;
+        if (plPtr->textWidth > 0) {
+            width += plPtr->padding;
+        }
+        if (plPtr->imageRightH > height) {
+            height = plPtr->imageRightH;
+        }
+    }
+
+    /*
+     * Add on extra size for the 3D border (if there is one).
+     */
+    if (plPtr->borderWidth > 0) {
+        width += 2*plPtr->borderWidth;
+        height += 2*plPtr->borderWidth;
+    }
+
+    /*
+     * If the overall width exceeds the -maxwidth, then we must
+     * truncate the text to make it fit.
+     */
+    maxwidth = 0;
+    if (plPtr->maxWidth > 0 && plPtr->width > 0) {
+        maxwidth = (plPtr->width < plPtr->maxWidth) ? plPtr->width : plPtr->maxWidth;
+    } else if (plPtr->maxWidth > 0) {
+        maxwidth = plPtr->maxWidth;
+    } else if (plPtr->width > 0) {
+        maxwidth = plPtr->width;
+    }
+
+    if (maxwidth > 0 && width > maxwidth) {
+        Tk_SizeOfBitmap(Tk_Display(plPtr->tkwin), plPtr->dots, &ellw, &ellh);
+        width = maxwidth - 2*plPtr->padding - 2*plPtr->borderWidth - ellw;
+        if (plPtr->imageLeft) {
+            width -= plPtr->imageLeftW + plPtr->padding;
+        }
+        if (plPtr->imageRight) {
+            width -= plPtr->imageRightW + plPtr->padding;
+        }
+        if (width < 0) { width = 0; }
+
+        plPtr->textDrawChars = Tk_MeasureChars(plPtr->tkfont,
+            plPtr->text, plPtr->textNumChars, width, /*flags*/ 0,
+            &plPtr->textWidth);
+
+        /* recompute layout for truncated text */
+        Tk_FreeTextLayout(plPtr->textLayout);
+        plPtr->textLayout = Tk_ComputeTextLayout(plPtr->tkfont,
+                plPtr->text, plPtr->textDrawChars, -1,
+                TK_JUSTIFY_LEFT, 0, NULL, NULL);
+
+        width = maxwidth;  /* fit everything in this width */
+    }
+
+    /*
+     * If there's a fixed -width, then use that instead.
+     */
+    if (plPtr->width > 0) {
+        width = plPtr->width;
+    }
+
+    /*
+     * Use overall geometry information to compute the top-left corner
+     * of the bounding box for the text item.
+     */
+    leftX = (int) (plPtr->x + 0.5);
+    topY = (int) (plPtr->y + 0.5);
+    switch (plPtr->anchor) {
+        case TK_ANCHOR_NW:
+        case TK_ANCHOR_N:
+        case TK_ANCHOR_NE:
+            break;
+
+        case TK_ANCHOR_W:
+        case TK_ANCHOR_CENTER:
+        case TK_ANCHOR_E:
+            topY -= height / 2;
+            break;
+
+        case TK_ANCHOR_SW:
+        case TK_ANCHOR_S:
+        case TK_ANCHOR_SE:
+            topY -= height;
+            break;
+    }
+    switch (plPtr->anchor) {
+        case TK_ANCHOR_NW:
+        case TK_ANCHOR_W:
+        case TK_ANCHOR_SW:
+            break;
+
+        case TK_ANCHOR_N:
+        case TK_ANCHOR_CENTER:
+        case TK_ANCHOR_S:
+            leftX -= width / 2;
+            break;
+
+        case TK_ANCHOR_NE:
+        case TK_ANCHOR_E:
+        case TK_ANCHOR_SE:
+            leftX -= width;
+            break;
+    }
+
+    plPtr->leftEdge  = leftX;
+    plPtr->rightEdge = leftX + width;
+
+    /*
+     * Last of all, update the bounding box for the item.
+     */
+    plPtr->header.x1 = leftX;
+    plPtr->header.y1 = topY;
+    plPtr->header.x2 = leftX + width;
+    plPtr->header.y2 = topY + height;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  DisplayCanvPlacard --
+ *
+ *    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
+DisplayCanvPlacard(canvas, itemPtr, display, drawable, x, y, width, height)
+    Tk_Canvas canvas;           /* Canvas that contains item. */
+    Tk_Item *itemPtr;           /* Item to be displayed. */
+    Display *display;           /* Display on which to draw item. */
+    Drawable drawable;          /* Pixmap or window in which to draw item. */
+    int x, y, width, height;    /* Describes region of canvas that must be
+                                 * redisplayed (not used). */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    int imagew, delta, ellx, elly, ellw, ellh;
+    short drawableX, drawableY;
+    Tk_FontMetrics fm;
+
+    if (plPtr->gcText == None) {
+        return;
+    }
+
+    /*
+     * If there's a background rectangle, draw it first.
+     */
+    Tk_CanvasDrawableCoords(canvas, (double)plPtr->leftEdge,
+            (double)plPtr->header.y1, &drawableX, &drawableY);
+
+    if (plPtr->bgBorder) {
+        Tk_Fill3DRectangle(plPtr->tkwin, drawable, plPtr->bgBorder,
+            (int)drawableX, (int)drawableY,
+            (int)(plPtr->rightEdge - plPtr->leftEdge),
+            (int)(plPtr->header.y2 - plPtr->header.y1),
+            plPtr->borderWidth, plPtr->relief);
+    }
+
+    if (plPtr->gcLineRect) {
+        XDrawRectangle(display, drawable, plPtr->gcLineRect,
+            (int)drawableX, (int)drawableY,
+            (unsigned int)(plPtr->rightEdge - plPtr->leftEdge - 1),
+            (unsigned int)(plPtr->header.y2 - plPtr->header.y1 - 1));
+    }
+
+    /*
+     * If there's a right-hand image, draw it first.
+     */
+    imagew = 0;
+    if (plPtr->imageRight) {
+        Tk_CanvasDrawableCoords(canvas, (double)plPtr->rightEdge,
+                (double)plPtr->header.y1, &drawableX, &drawableY);
+
+        delta = (plPtr->header.y2 - plPtr->header.y1)/2 - plPtr->imageRightH/2;
+        drawableX -= plPtr->imageRightW + plPtr->padding + plPtr->borderWidth;
+        Tk_RedrawImage(plPtr->imageRight, 0, 0,
+                       plPtr->imageRightW, plPtr->imageRightH,
+                       drawable,
+                       (int)drawableX, (int)(drawableY+delta));
+        imagew += plPtr->imageRightW;
+    }
+
+    /*
+     * If there's a left-hand image, draw it next.
+     */
+    Tk_CanvasDrawableCoords(canvas, (double)plPtr->leftEdge,
+            (double)plPtr->header.y1, &drawableX, &drawableY);
+
+    drawableX += plPtr->padding + plPtr->borderWidth;
+    drawableY += plPtr->padding + plPtr->borderWidth;
+
+    if (plPtr->imageLeft) {
+        delta = (plPtr->header.y2 - plPtr->header.y1)/2
+                       - plPtr->imageLeftH/2 - plPtr->padding
+                       - plPtr->borderWidth;
+
+        Tk_RedrawImage(plPtr->imageLeft, 0, 0,
+                       plPtr->imageLeftW, plPtr->imageLeftH,
+                       drawable,
+                       (int)drawableX, (int)(drawableY+delta));
+        drawableX += plPtr->imageLeftW + plPtr->padding;
+        imagew += plPtr->imageLeftW;
+    }
+
+    /*
+     * Draw the text.
+     */
+    Tk_DrawTextLayout(display, drawable, plPtr->gcText, plPtr->textLayout,
+            drawableX, drawableY, 0, -1);
+
+    if (plPtr->textDrawChars < plPtr->textNumChars
+          && plPtr->rightEdge - plPtr->leftEdge - imagew > ellw) {
+        Tk_SizeOfBitmap(Tk_Display(plPtr->tkwin), plPtr->dots, &ellw, &ellh);
+        Tk_GetFontMetrics(plPtr->tkfont, &fm);
+        ellx = drawableX+plPtr->textWidth;
+        elly = drawableY+fm.ascent-1;
+
+        XSetClipMask(display, plPtr->gcText, plPtr->dots);
+        XSetClipOrigin(display, plPtr->gcText, ellx, elly);
+
+        XCopyPlane(display, plPtr->dots, drawable, plPtr->gcText, 0, 0,
+            (unsigned int)ellw, (unsigned int)ellh, ellx, elly, 1);
+
+        XSetClipOrigin(display, plPtr->gcText, 0, 0);
+        XSetClipMask(display, plPtr->gcText, None);
+    }
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  PlacardToPoint --
+ *
+ *    Computes the distance from a given point to a given placard
+ *    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
+PlacardToPoint(canvas, itemPtr, pointPtr)
+    Tk_Canvas canvas;                /* Canvas containing itemPtr. */
+    Tk_Item *itemPtr;                /* Item to check against point. */
+    double *pointPtr;                /* Pointer to x and y coordinates. */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    double value, x0, y0, x1, y1, xDiff, yDiff;
+
+    if (plPtr->bgBorder || plPtr->lineColor) {
+        /* have a rectangle -- compare directly against that */
+        x0 = plPtr->leftEdge;
+        y0 = plPtr->header.y1;
+        x1 = plPtr->rightEdge;
+        y1 = plPtr->header.y2;
+    }
+    else if (plPtr->imageLeft || plPtr->imageRight) {
+        /* have images -- compare against a rectangle around image/text */
+        x0 = plPtr->leftEdge + plPtr->padding + plPtr->borderWidth;
+        y0 = plPtr->header.y1 + plPtr->padding + plPtr->borderWidth;
+        x1 = plPtr->rightEdge - plPtr->padding - plPtr->borderWidth;
+        y1 = plPtr->header.y2 - plPtr->padding - plPtr->borderWidth;
+    }
+    else {
+        value = (double) Tk_DistanceToTextLayout(plPtr->textLayout,
+                    (int) pointPtr[0] - plPtr->leftEdge,
+                    (int) pointPtr[1] - plPtr->header.y1);
+
+        if ((plPtr->textColor == NULL) || (plPtr->text == NULL)
+              || (*plPtr->text == 0)) {
+            value = 1.0e36;
+        }
+        return value;
+    }
+
+    if (pointPtr[0] < x0) {
+        xDiff = x0 - pointPtr[0];
+    } else if (pointPtr[0] >= x1)  {
+        xDiff = pointPtr[0] + 1 - x1;
+    } else {
+        xDiff = 0;
+    }
+
+    if (pointPtr[1] < y0) {
+        yDiff = y0 - pointPtr[1];
+    } else if (pointPtr[1] >= y1)  {
+        yDiff = pointPtr[1] + 1 - y1;
+    } else {
+        yDiff = 0;
+    }
+
+    return hypot(xDiff, yDiff);
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  PlacardToArea --
+ *
+ *    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
+PlacardToArea(canvas, itemPtr, rectPtr)
+    Tk_Canvas canvas;       /* Canvas containing itemPtr. */
+    Tk_Item *itemPtr;       /* Item to check against rectangle. */
+    double *rectPtr;        /* Pointer to array of four coordinates
+                             * (x1, y1, x2, y2) describing rectangular
+                             * area.  */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    int x0, y0, x1, y1;
+
+    if (plPtr->bgBorder || plPtr->lineColor) {
+        /* have a rectangle -- compare directly against that */
+        x0 = plPtr->leftEdge;
+        y0 = plPtr->header.y1;
+        x1 = plPtr->rightEdge;
+        y1 = plPtr->header.y2;
+    }
+    else if (plPtr->imageLeft || plPtr->imageRight) {
+        /* have images -- compare against a rectangle around image/text */
+        x0 = plPtr->leftEdge + plPtr->padding + plPtr->borderWidth;
+        y0 = plPtr->header.y1 + plPtr->padding + plPtr->borderWidth;
+        x1 = plPtr->rightEdge - plPtr->padding - plPtr->borderWidth;
+        y1 = plPtr->header.y2 - plPtr->padding - plPtr->borderWidth;
+    }
+    else {
+        /* have only text -- compare against text layout */
+        return Tk_IntersectTextLayout(plPtr->textLayout,
+            (int) (rectPtr[0] + 0.5) - (plPtr->leftEdge + plPtr->padding + plPtr->borderWidth),
+            (int) (rectPtr[1] + 0.5) - (plPtr->header.y1 + plPtr->padding + plPtr->borderWidth),
+            (int) (rectPtr[2] - rectPtr[0] + 0.5),
+            (int) (rectPtr[3] - rectPtr[1] + 0.5));
+    }
+
+    /* compare against rectangle... */
+    if (rectPtr[0] > x1 || rectPtr[1] > y1
+          || rectPtr[2] < x0 || rectPtr[3] < y0) {
+        return -1;  /* completely outside */
+    }
+    if (rectPtr[0] >= x0 && rectPtr[1] >= y0
+          && rectPtr[2] <= x1 && rectPtr[3] <= y1) {
+        return 1;  /* completely inside */
+    }
+    return 0;  /* must be overlapping */
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  ScalePlacard --
+ *
+ *    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
+ScalePlacard(canvas, itemPtr, originX, originY, scaleX, scaleY)
+    Tk_Canvas canvas;             /* Canvas containing rectangle. */
+    Tk_Item *itemPtr;             /* Rectangle to be scaled. */
+    double originX, originY;      /* Origin about which to scale rect. */
+    double scaleX;                /* Amount to scale in X direction. */
+    double scaleY;                /* Amount to scale in Y direction. */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+
+    plPtr->x = originX + scaleX*(plPtr->x - originX);
+    plPtr->y = originY + scaleY*(plPtr->y - originY);
+    ComputePlacardBbox(canvas, plPtr);
+    return;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  TranslatePlacard --
+ *
+ *    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
+TranslatePlacard(canvas, itemPtr, deltaX, deltaY)
+    Tk_Canvas canvas;             /* Canvas containing item. */
+    Tk_Item *itemPtr;             /* Item that is being moved. */
+    double deltaX, deltaY;        /* Amount by which item is to be moved. */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+
+    plPtr->x += deltaX;
+    plPtr->y += deltaY;
+    ComputePlacardBbox(canvas, plPtr);
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  PlacardToPostscript --
+ *
+ *    This procedure is called to generate Postscript for placard
+ *    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
+PlacardToPostscript(interp, canvas, itemPtr, prepass)
+    Tcl_Interp *interp;         /* Leave Postscript or error message here. */
+    Tk_Canvas canvas;           /* Information about overall canvas. */
+    Tk_Item *itemPtr;           /* Item for which Postscript is wanted. */
+    int prepass;                /* 1 means this is a prepass to collect
+                                 * font information; 0 means final Postscript
+                                 * is being created. */
+{
+    PlacardItem *plPtr = (PlacardItem *) itemPtr;
+    Tk_Window canvasWin = Tk_CanvasTkwin(canvas);
+
+    int x, y;
+    double xpos;
+    Tk_FontMetrics fm;
+    char *justify;
+    char buffer[500];
+    int delta;
+
+    if (plPtr->textColor == NULL || plPtr->text == NULL
+          || *plPtr->text == 0) {
+        return TCL_OK;
+    }
+
+    if (Tk_CanvasPsFont(interp, canvas, plPtr->tkfont) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+
+    if (!prepass && 
+	Tk_CanvasPsColor(interp, canvas, plPtr->textColor) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    xpos = plPtr->x;
+
+    /*
+     * Check for an image to print.
+     */
+
+    if (plPtr->imageLeft) {
+      if(!prepass) {
+	delta = (plPtr->header.y2 - plPtr->header.y1)/2 - plPtr->imageLeftH/2;
+	sprintf(buffer, "%.15g %.15g", xpos,
+		Tk_CanvasPsY(canvas, plPtr->y+delta+2));
+	Tcl_AppendResult(interp, buffer, " translate\n", (char *) NULL);
+      }
+
+      Tk_PostscriptImage(plPtr->imageLeft, interp, canvasWin,
+			 ((TkCanvas*)canvas)->psInfo, 0, 0,
+                         plPtr->imageLeftW, plPtr->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 += plPtr->imageLeftW + plPtr->padding;
+    }
+
+    
+    if (prepass != 0 ) {
+      return TCL_OK;
+    }
+
+    /*
+     * Before drawing the text, reset the font and color information.
+     * Otherwise the text won't appear.
+     */
+
+    if ( plPtr->imageLeft ){
+      if (Tk_CanvasPsFont(interp, canvas, plPtr->tkfont) != TCL_OK) {
+        return TCL_ERROR;
+      }
+      
+      if (Tk_CanvasPsColor(interp, canvas, plPtr->textColor) != TCL_OK) {
+        return TCL_ERROR;
+      }
+    }
+
+
+    /*
+     * Draw the text for the placard.
+     */
+    sprintf(buffer, "%.15g %.15g [\n", xpos,
+        Tk_CanvasPsY(canvas, plPtr->y));
+    Tcl_AppendResult(interp, buffer, (char *) NULL);
+
+    Tk_TextLayoutToPostscript(interp, plPtr->textLayout);
+
+    x = 0;  y = 0;  justify = NULL;
+    switch (plPtr->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(plPtr->tkfont, &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);
+
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * ImageLeftChangedProc --
+ *
+ *      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
+ImageLeftChangedProc(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. */
+{      
+    PlacardItem *plPtr = (PlacardItem *) clientData;
+    
+    plPtr->imageLeftW = imgWidth;
+    plPtr->imageLeftH = imgHeight;
+    ComputePlacardBbox(plPtr->canvas, plPtr);
+    return;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * ImageRightChangedProc --
+ *
+ *      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
+ImageRightChangedProc(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. */
+{      
+    PlacardItem *plPtr = (PlacardItem *) clientData;
+    
+    plPtr->imageRightW = imgWidth;
+    plPtr->imageRightH = imgHeight;
+    ComputePlacardBbox(plPtr->canvas, plPtr);
+    return;
+}
Index: /branches/r9/packages/DicomToVtk/RpDicomToVtk.cc
===================================================================
--- /branches/r9/packages/DicomToVtk/RpDicomToVtk.cc	(revision 4841)
+++ /branches/r9/packages/DicomToVtk/RpDicomToVtk.cc	(revision 4841)
@@ -0,0 +1,278 @@
+/* 
+ * ----------------------------------------------------------------------
+ *  RpDicomToVtk - 
+ *
+ * ======================================================================
+ *  AUTHOR:  Leif Delgass, Purdue University
+ *  Copyright (c) 2014  HUBzero Foundation, LLC
+ *
+ *  See the file "license.terms" for information on usage and
+ *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ * ======================================================================
+ */
+
+#include <vtkSmartPointer.h>
+#include <vtkStringArray.h>
+#include <vtkIntArray.h>
+#include <vtkDataSetWriter.h>
+#include <vtkDirectory.h>
+#ifdef USE_VTK_DICOM_PACKAGE
+#include <vtkDICOMSorter.h>
+#include <vtkDICOMReader.h>
+#include <vtkDICOMMetaData.h>
+#else
+#include <vtkDICOMImageReader.h>
+#endif
+
+#include <stdio.h>
+#include "tcl.h"
+
+static int
+DicomToVtkCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    if (objc != 3) {
+        Tcl_AppendResult(interp, "wrong # args: should be \"",
+                         Tcl_GetString(objv[0]), " dir|files fileList\"", (char*)NULL);
+        return TCL_ERROR;
+    }
+    bool isDir = false;
+    char *arg = Tcl_GetString(objv[1]);
+    if (arg[0] == 'd' && strcmp(arg, "dir") == 0) {
+        isDir = true;
+    }
+
+    char *dirName = NULL;
+    vtkSmartPointer<vtkStringArray> pathArray = vtkSmartPointer<vtkStringArray>::New();
+    if (isDir) {
+        dirName = Tcl_GetString(objv[2]); 
+    } else {
+        // Get path array from Tcl list
+        int pathListc;
+        Tcl_Obj **pathListv = NULL;
+        if (Tcl_ListObjGetElements(interp, objv[2], &pathListc, &pathListv) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        pathArray->SetNumberOfValues(pathListc);
+        for (int i = 0; i < pathListc; i++) {
+            pathArray->SetValue(i, Tcl_GetString(pathListv[i]));
+        }
+    }
+
+#ifdef USE_VTK_DICOM_PACKAGE
+    vtkSmartPointer<vtkDICOMReader> reader = vtkSmartPointer<vtkDICOMReader>::New();
+    vtkSmartPointer<vtkDICOMSorter> sorter = vtkSmartPointer<vtkDICOMSorter>::New();
+    if (isDir) {
+        vtkSmartPointer<vtkDirectory> dir = vtkSmartPointer<vtkDirectory>::New();
+        if (!dir->Open(dirName)) {
+            return TCL_ERROR;
+        }
+
+        int numFiles = dir->GetNumberOfFiles();
+        for (int i = 0; i < numFiles; i++) {
+            if (dir->GetFile(i)[0] == '.') {
+                continue;
+            }
+
+            std::string path(dirName);
+            path += "/";
+            path += dir->GetFile(i);
+
+            if (!dir->FileIsDirectory(path.c_str()) &&
+                reader->CanReadFile(path.c_str())) {
+                pathArray->InsertNextValue(path);
+            } else {
+                fprintf(stderr, "Skipping file %s\n", path.c_str());
+            }
+        }
+    }
+
+    sorter->SetInputFileNames(pathArray);
+    sorter->Update();
+
+    int numStudies = sorter->GetNumberOfStudies();
+    int study = 0;
+    int series = 0;
+
+    fprintf(stderr, "Num Studies: %d\n", numStudies);
+    vtkStringArray *files;
+#if 0
+    for (int i = 0; i < numStudies; i++) {
+        int numSeries = sorter->GetNumberOfSeriesInStudy(i);
+        fprintf(stderr, "Study %d: %d series\n", i, numSeries);
+        int k = sorter->GetFirstSeriesInStudy(i);
+        for (int j = 0; j < numSeries; j++) {
+            files = sorter->GetFileNamesForSeries(k+j);
+        }
+    }
+#else
+    if (study < 0 || study > sorter->GetNumberOfStudies()-1) {
+        return TCL_ERROR;
+    }
+    if (series < 0 || series > sorter->GetNumberOfSeriesInStudy(study)-1) {
+        return TCL_ERROR;
+    }
+    files = sorter->GetFileNamesForSeries(sorter->GetFirstSeriesInStudy(study)+series);
+#endif
+
+    Tcl_Obj *objPtr = Tcl_NewListObj(0, NULL);
+    Tcl_Obj *metaDataObj = Tcl_NewListObj(0, NULL);
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("num_studies", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(numStudies));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("study", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(study));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("num_series", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(sorter->GetNumberOfSeriesInStudy(study)));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("series", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(series));
+
+    reader->SetFileNames(files);
+#else
+    Tcl_Obj *objPtr = Tcl_NewListObj(0, NULL);
+    Tcl_Obj *metaDataObj = Tcl_NewListObj(0, NULL);
+
+    vtkSmartPointer<vtkDICOMImageReader> reader = vtkSmartPointer<vtkDICOMImageReader>::New();
+    if (isDir) {
+        reader->SetDirectoryName(dirName);
+    } else {
+        reader->SetFileName(pathArray->GetValue(0));
+    }
+#endif
+    reader->Update();
+
+    Tcl_Obj *eltObj;
+#ifdef USE_VTK_DICOM_PACKAGE
+    vtkStringArray *ids = reader->GetStackIDs();
+    for (int i = 0; i < ids->GetNumberOfValues(); i++) {
+        fprintf(stderr, "Stack: %s\n", ids->GetValue(i).c_str());
+    }
+    vtkIntArray *fidxArray = reader->GetFileIndexArray();
+    vtkDICOMMetaData *md = reader->GetMetaData();
+    int numComp = fidxArray->GetNumberOfComponents();
+#if 0
+    for (int i = 0; i < fidxArray->GetNumberOfTuples(); i++) {
+        fprintf(stderr, "%d:", i);
+        for (int j = 0; j < fidxArray->GetNumberOfComponents(); j++) {
+            int idx = (int)fidxArray->GetComponent(i, j);
+            fprintf(stderr, " %d", idx);
+        }
+        fprintf(stderr, "\n");
+    }
+#endif
+    fprintf(stderr, "Number of data elements: %d\n", md->GetNumberOfDataElements());
+
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("num_files", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(md->GetNumberOfInstances()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("num_components", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(numComp));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("modality", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::Modality).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("patient_name", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::PatientName).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("transfer_syntax_uid", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::TransferSyntaxUID).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("study_uid", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::StudyInstanceUID).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("study_id", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::StudyID).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("series_number", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(md->GetAttributeValue(DC::SeriesNumber).AsInt()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("series_in_study", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(md->GetAttributeValue(DC::SeriesInStudy).AsInt()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("series_uid", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::SeriesInstanceUID).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("bits_allocated", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(md->GetAttributeValue(DC::BitsAllocated).AsInt()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("bits_stored", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(md->GetAttributeValue(DC::BitsStored).AsInt()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("pixel_representation", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(md->GetAttributeValue(DC::PixelRepresentation).AsString().c_str(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("rescale_slope", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewDoubleObj(md->GetAttributeValue(DC::RescaleSlope).AsDouble()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("rescale_intercept", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewDoubleObj(md->GetAttributeValue(DC::RescaleIntercept).AsDouble()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("time_dimension", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(reader->GetTimeDimension()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("time_spacing", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewDoubleObj(reader->GetTimeSpacing()));
+#else
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("patient_name", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(reader->GetPatientName(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("transfer_syntax_uid", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(reader->GetTransferSyntaxUID(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("study_uid", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(reader->GetStudyUID(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("study_id", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj(reader->GetStudyID(), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("num_components", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(reader->GetNumberOfComponents()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("bits_allocated", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewIntObj(reader->GetBitsAllocated()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("pixel_representation", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj,
+                             Tcl_NewStringObj((reader->GetPixelRepresentation() ? "signed" : "unsigned"), -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("rescale_slope", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewDoubleObj(reader->GetRescaleSlope()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("rescale_offset", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewDoubleObj(reader->GetRescaleOffset()));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewStringObj("gantry_angle", -1));
+    Tcl_ListObjAppendElement(interp, metaDataObj, Tcl_NewDoubleObj(reader->GetGantryAngle()));
+#if 0
+    fprintf(stderr, "Position: %g %g %g\n",
+            reader->GetImagePositionPatient()[0],
+            reader->GetImagePositionPatient()[1],
+            reader->GetImagePositionPatient()[2]);
+    fprintf(stderr, "Orientation: %g %g %g, %g %g %g\n",
+            reader->GetImageOrientationPatient()[0], 
+            reader->GetImageOrientationPatient()[1], 
+            reader->GetImageOrientationPatient()[2], 
+            reader->GetImageOrientationPatient()[3], 
+            reader->GetImageOrientationPatient()[4], 
+            reader->GetImageOrientationPatient()[5]);
+#endif
+#endif
+
+    Tcl_ListObjAppendList(interp, objPtr, metaDataObj);
+
+    fprintf(stderr, "writing VTK\n");
+
+    vtkSmartPointer<vtkDataSetWriter> writer = vtkSmartPointer<vtkDataSetWriter>::New();
+    writer->SetInputConnection(reader->GetOutputPort());
+    writer->SetFileTypeToBinary();
+    writer->WriteToOutputStringOn();
+    writer->Update();
+
+    fprintf(stderr, "writing VTK...done\n");
+
+    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj("vtkdata", -1));
+
+    eltObj = Tcl_NewByteArrayObj((unsigned char *)writer->GetBinaryOutputString(),
+                                 writer->GetOutputStringLength());
+    Tcl_ListObjAppendElement(interp, objPtr, eltObj);
+
+    Tcl_SetObjResult(interp, objPtr);
+    return TCL_OK;
+}
+
+extern "C" {
+/*
+ * ------------------------------------------------------------------------
+ *  RpDicomToVtk_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "DicomToVtk" command into the interpreter.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpDicomToVtk_Init(Tcl_Interp *interp)
+{
+    /* install the widget command */
+    Tcl_CreateObjCommand(interp, "Rappture::DicomToVtk", DicomToVtkCmd,
+                         NULL, NULL);
+    return TCL_OK;
+}
+
+}
Index: /branches/r9/packages/DiffView/RpDiffview.c
===================================================================
--- /branches/r9/packages/DiffView/RpDiffview.c	(revision 4841)
+++ /branches/r9/packages/DiffView/RpDiffview.c	(revision 4841)
@@ -0,0 +1,2797 @@
+/* 
+ * ----------------------------------------------------------------------
+ *  RpDiffview - like a textbox, but for showing diffs
+ *
+ *  Loads two strings, performs a diff, and shows the results.  Use
+ *  it as follows:
+ *
+ *  Rappture::Diffview .dv \
+ *      -addedbackground blue -addedforeground white \
+ *      -deletedbackground red -deletedforeground white -overstrike true \
+ *      -changedbackground black -changedforeground yellow \
+ *      -xscrollcommand {.x set} -yscrollcommand {.y set}
+ *  
+ *  scrollbar .x -orient horizontal -command {.dv xview}
+ *  scrollbar .y -orient vertical -command {.dv yview}
+ *  
+ *  .dv text 1 [exec cat /tmp/file1]
+ *  .dv text 2 [exec cat /tmp/file2]
+ *  .dv configure -diff 2->1 -layout sidebyside
+ *  
+ *  puts "DIFF OUTPUT:"
+ *  puts [.dv diffs -std]
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  See the file "license.terms" for information on usage and
+ *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ * ======================================================================
+ */
+#include "tk.h"
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * Special options for controlling diffs.
+ */
+enum diffdir {
+    DIFF_1TO2, DIFF_2TO1
+};
+static char *diffdirStrings[] = {
+    "1->2", "2->1", (char*)NULL
+};
+
+enum layoutStyle {
+    LAYOUT_INLINE, LAYOUT_SIDEBYSIDE
+};
+static char *layoutStyleStrings[] = {
+    "inline", "sidebyside", (char*)NULL
+};
+
+enum scancommand {
+    SCAN_MARK, SCAN_DRAGTO
+};
+static CONST char *scanCommandNames[] = {
+    "mark", "dragto", (char *) NULL
+};
+
+
+/*
+ * Data structure for line start/end for all lines in a buffer.
+ */
+typedef struct {
+    int numLines;		/* number of lines */
+    int maxLines;		/* max size of storage in startPtr/endPtr */
+    char **startPtr;		/* array of start pointers for each line */
+    int *lenPtr;		/* array of lengths for each line */
+} DiffviewLines;
+
+/*
+ * Data structure for text buffers within the widget:
+ */
+typedef struct {
+    Tcl_Obj *textObj;		/* actual text within this buffer */
+    DiffviewLines *lineLimits;	/* breakdown of textObj into line starts/ends */
+} DiffviewBuffer;
+
+/*
+ * Data structure used internally by diff routine:
+ */
+typedef struct subseq {
+    int index1;			/* index in buffer #1 */
+    int index2;			/* index in buffer #2 */
+    int next;			/* next candidate match in the chain */
+} DiffviewSubseq;
+
+/*
+ * Data structure returned by diff routine:
+ */
+typedef struct {
+    int op;			/* operation: 'a'=add 'd'=del 'c'=change */
+    int fromIndex1;		/* starts at this line in buffer 1 */
+    int toIndex1;		/* ends at this line in buffer 1 */
+    int fromIndex2;		/* starts at this line in buffer 2 */
+    int toIndex2;		/* ends at this line in buffer 2 */
+    int fromWorld;		/* starts at this line in world view */
+    int toWorld;		/* ends at this line in world view */
+} DiffviewDiffOp;
+
+typedef struct {
+    int maxDiffs;		/* maximum storage space for diffs */
+    int numDiffs;		/* number of diffs stored in ops */
+    DiffviewDiffOp *ops;	/* list of diffs */
+} DiffviewDiffs;
+
+/*
+ * Data structure used for each line of the final layout:
+ */
+typedef struct {
+    char style;			/* layout style: 'a'=add, 'd'=del, etc. */
+    char bnum;			/* show line from this buffer */
+    int bline;			/* line number in buffer bnum */
+    int diffNum;		/* line is part of this diff */
+} DiffviewLayoutLine;
+
+typedef struct {
+    int maxLines;		/* maximum number of lines allocated */
+    int numLines;		/* current number of lines in layout */
+    DiffviewLayoutLine *lines;	/* info for all lines in world view */
+} DiffviewLayout;
+
+/*
+ * Data structure for the widget:
+ */
+typedef struct {
+    Tk_Window tkwin;		/* tk window for the widget */
+    Display *display;		/* display containing the widget */
+    Tcl_Interp *interp;		/* interpreter containing the widget command */
+    Tcl_Command widgetCmd;	/* token for the widget command */
+    Tk_OptionTable optionTable;	/* configuration options */
+
+    /*
+     * Used to display the widget:
+     */
+    Tk_Cursor cursor;		/* cursor for the widget */
+    Tk_3DBorder normalBorder;	/* border around the widget */
+    int borderWidth;		/* width of the border in pixels */
+    int relief;			/* relief: raised, sunken, etc. */
+    int highlightWidth;		/* width of border showing focus highlight */
+    XColor *highlightBgColor;	/* color for focus highlight bg */
+    XColor *highlightColor;	/* color for focus highlight */
+    int inset;			/* width of all borders--offset to ul corner */
+
+    DiffviewBuffer buffer[2];	/* text to diff -- buffers #1 and #2 */
+    DiffviewDiffs *diffsPtr;    /* diff: longest common subseq btwn buffers */
+    DiffviewLayout worldview;   /* array mapping each real line in world
+				 * coordinates to the corresponding line
+				 * in buffer #1/#2, along with its style */
+
+    enum diffdir diffdir;	/* diff between these buffers */
+    enum layoutStyle layout;	/* layout style (side-by-side or inline) */
+    int maxWidth;		/* maximum width of world view in pixels */
+    int maxHeight;		/* maximum height of world view in pixels */
+    int lineHeight;		/* height of a line with current font */
+    int lineAscent;		/* ascent of a line with current font */
+    int topLine;		/* index of topmost line in view */
+    int btmLine;		/* index of bottommost line in view */
+    int fullLines;		/* number of full lines that fit in view */
+
+    int xOffset;		/* offset in pixels to left edge of view */
+    int xScrollUnit;		/* num pixels for one unit of horizontal
+				 * scrolling (one average character) */
+    int yOffset;		/* offset in pixels to top edge of view */
+    int yScrollUnit;		/* num pixels for one unit of vertical
+				 * scrolling (one average line) */
+    char *takeFocus;		/* value of -takefocus option */
+    char *yScrollCmd;		/* command prefix for scrolling */
+    char *xScrollCmd;		/* command prefix for scrolling */
+
+    int scanMarkX;		/* x-coord for "scan mark" */
+    int scanMarkY;		/* y-coord for "scan mark" */
+    int scanMarkXStart;		/* xOffset at start of scan */
+    int scanMarkYStart;		/* yOffset at start of scan */
+
+
+    Tk_Font tkfont;		/* text font for content of widget */
+    Tk_Font tklastfont;		/* previous text font (so we detect changes) */
+    XColor *fgColorPtr;		/* normal foreground color */
+    XColor *addBgColorPtr;	/* background for "added" text in diff */
+    XColor *addFgColorPtr;	/* foreground for "added" text in diff */
+    XColor *delBgColorPtr;	/* background for "deleted" text in diff */
+    XColor *delFgColorPtr;	/* foreground for "deleted" text in diff */
+    XColor *chgBgColorPtr;	/* background for "changed" text in diff */
+    XColor *chgFgColorPtr;	/* foreground for "changed" text in diff */
+    int overStrDel;		/* non-zero => overstrike deleted text */
+    GC normGC;			/* GC for drawing normal text */
+    GC addFgGC;			/* GC for drawing "added" text in diff */
+    GC delFgGC;			/* GC for drawing "deleted" text in diff */
+    GC chgFgGC;			/* GC for drawing "changed" text in diff */
+    int width;			/* overall width of widget, in pixels */
+    int height;			/* overall height of widget, in pixels */
+
+    int flags;			/* flag bits for redraw, etc. */
+} Diffview;
+
+/*
+ * Tk widget flag bits:
+ *
+ * REDRAW_PENDING:	DoWhenIdle handler has been queued to redraw
+ * UPDATE_V_SCROLLBAR:	non-zero => vertical scrollbar needs updating
+ * UPDATE_H_SCROLLBAR:	non-zero => horizontal scrollbar needs updating
+ * GOT_FOCUS:		non-zero => widget has input focus
+ * FONT_CHANGED:	font changed, so layout needs to be recomputed
+ * WIDGET_DELETED:	widget has been destroyed, so don't update
+ */
+
+#define REDRAW_PENDING		1
+#define UPDATE_V_SCROLLBAR	2
+#define UPDATE_H_SCROLLBAR	4
+#define GOT_FOCUS		8
+#define FONT_CHANGED		16
+#define WIDGET_DELETED		32
+
+
+/*
+ * Default option values.
+ */
+#define BLACK				"#000000"
+#define WHITE				"#ffffff"
+#define NORMAL_BG			"#d9d9d9"
+
+#define DEF_DIFFVIEW_ADDBG		"#ccffcc"
+#define DEF_DIFFVIEW_ADDFG		BLACK
+#define DEF_DIFFVIEW_BG_COLOR		NORMAL_BG
+#define DEF_DIFFVIEW_BG_MONO		WHITE
+#define DEF_DIFFVIEW_BORDERWIDTH	"2"
+#define DEF_DIFFVIEW_CHGBG		"#ffffcc"
+#define DEF_DIFFVIEW_CHGFG		BLACK
+#define DEF_DIFFVIEW_CURSOR		""
+#define DEF_DIFFVIEW_DELBG		"#ffcccc"
+#define DEF_DIFFVIEW_DELFG		"#666666"
+#define DEF_DIFFVIEW_DIFF		"1->2"
+#define DEF_DIFFVIEW_FG			BLACK
+#define DEF_DIFFVIEW_FONT		"Courier -12"
+#define DEF_DIFFVIEW_HEIGHT		"2i"
+#define DEF_DIFFVIEW_HIGHLIGHT_BG	NORMAL_BG
+#define DEF_DIFFVIEW_HIGHLIGHT		BLACK
+#define DEF_DIFFVIEW_HIGHLIGHT_WIDTH	"1"
+#define DEF_DIFFVIEW_LAYOUT		"inline"
+#define DEF_DIFFVIEW_OVERSTRDEL		"true"
+#define DEF_DIFFVIEW_RELIEF		"sunken"
+#define DEF_DIFFVIEW_TAKE_FOCUS		(char*)NULL
+#define DEF_DIFFVIEW_WIDTH		"2i"
+#define DEF_DIFFVIEW_SCROLL_CMD		""
+
+/*
+ * Configuration options for this widget.
+ * (must be in alphabetical order)
+ */
+static Tk_OptionSpec optionSpecs[] = {
+    {TK_OPTION_COLOR, "-addedbackground", "addedBackground", "Background",
+	 DEF_DIFFVIEW_ADDBG, -1, Tk_Offset(Diffview, addBgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-addedforeground", "addedForeground", "Foreground",
+	 DEF_DIFFVIEW_ADDFG, -1, Tk_Offset(Diffview, addFgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_BORDER, "-background", "background", "Background",
+	 DEF_DIFFVIEW_BG_COLOR, -1, Tk_Offset(Diffview, normalBorder),
+	 0, (ClientData) DEF_DIFFVIEW_BG_MONO, 0},
+    {TK_OPTION_SYNONYM, "-bd", (char*)NULL, (char*)NULL,
+	 (char*)NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
+    {TK_OPTION_SYNONYM, "-bg", (char*)NULL, (char*)NULL,
+	 (char*)NULL, 0, -1, 0, (ClientData) "-background", 0},
+    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
+	 DEF_DIFFVIEW_BORDERWIDTH, -1, Tk_Offset(Diffview, borderWidth),
+	 0, 0, 0},
+    {TK_OPTION_COLOR, "-changedbackground", "changedBackground", "Background",
+	 DEF_DIFFVIEW_CHGBG, -1, Tk_Offset(Diffview, chgBgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-changedforeground", "changedForeground", "Foreground",
+	 DEF_DIFFVIEW_CHGFG, -1, Tk_Offset(Diffview, chgFgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
+	 DEF_DIFFVIEW_CURSOR, -1, Tk_Offset(Diffview, cursor),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-deletedbackground", "deletedBackground", "Background",
+	 DEF_DIFFVIEW_DELBG, -1, Tk_Offset(Diffview, delBgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-deletedforeground", "deletedForeground", "Foreground",
+	 DEF_DIFFVIEW_DELFG, -1, Tk_Offset(Diffview, delFgColorPtr),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_STRING_TABLE, "-diff", "diff", "Diff",
+        DEF_DIFFVIEW_DIFF, -1, Tk_Offset(Diffview, diffdir),
+        0, (ClientData)diffdirStrings, 0},
+    {TK_OPTION_SYNONYM, "-fg", "foreground", (char*)NULL,
+	 (char*)NULL, 0, -1, 0, (ClientData) "-foreground", 0},
+    {TK_OPTION_FONT, "-font", "font", "Font",
+	 DEF_DIFFVIEW_FONT, -1, Tk_Offset(Diffview, tkfont), 0, 0, 0},
+    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
+	 DEF_DIFFVIEW_FG, -1, Tk_Offset(Diffview, fgColorPtr), 0, 0, 0},
+    {TK_OPTION_PIXELS, "-height", "height", "Height",
+	 DEF_DIFFVIEW_HEIGHT, -1, Tk_Offset(Diffview, height), 0, 0, 0},
+    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
+	 "HighlightBackground", DEF_DIFFVIEW_HIGHLIGHT_BG, -1, 
+	 Tk_Offset(Diffview, highlightBgColor), 0, 0, 0},
+    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
+	 DEF_DIFFVIEW_HIGHLIGHT, -1, Tk_Offset(Diffview, highlightColor),
+	 0, 0, 0},
+    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
+	 "HighlightThickness", DEF_DIFFVIEW_HIGHLIGHT_WIDTH, -1,
+	 Tk_Offset(Diffview, highlightWidth), 0, 0, 0},
+    {TK_OPTION_STRING_TABLE, "-layout", "layout", "Layout",
+        DEF_DIFFVIEW_LAYOUT, -1, Tk_Offset(Diffview, layout),
+        0, (ClientData)layoutStyleStrings, 0},
+    {TK_OPTION_BOOLEAN, "-overstrike", "overstrike", "Overstrike",
+	 DEF_DIFFVIEW_OVERSTRDEL, -1, Tk_Offset(Diffview, overStrDel), 0, 0, 0},
+    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
+	 DEF_DIFFVIEW_RELIEF, -1, Tk_Offset(Diffview, relief), 0, 0, 0},
+    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
+	 DEF_DIFFVIEW_TAKE_FOCUS, -1, Tk_Offset(Diffview, takeFocus),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_PIXELS, "-width", "width", "Width",
+	 DEF_DIFFVIEW_WIDTH, -1, Tk_Offset(Diffview, width), 0, 0, 0},
+    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
+	 DEF_DIFFVIEW_SCROLL_CMD, -1, Tk_Offset(Diffview, xScrollCmd),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
+	 DEF_DIFFVIEW_SCROLL_CMD, -1, Tk_Offset(Diffview, yScrollCmd),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_END, (char*)NULL, (char*)NULL, (char*)NULL,
+	(char*)NULL, 0, -1, 0, 0, 0}
+};
+
+/*
+ * Widget commands:
+ */
+static CONST char *commandNames[] = {
+    "bbox", "cget", "configure",
+    "diffs", "scan", "see",
+    "text", "xview", "yview",
+    (char*)NULL
+};
+
+enum command {
+    COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE,
+    COMMAND_DIFFS, COMMAND_SCAN, COMMAND_SEE,
+    COMMAND_TEXT, COMMAND_XVIEW, COMMAND_YVIEW
+};
+
+/*
+ * Forward declarations for remaining procedures.
+ */
+static int		DiffviewObjCmd _ANSI_ARGS_((ClientData clientData,
+			    Tcl_Interp *interp, int objc,
+			    Tcl_Obj *CONST objv[]));
+static int		DiffviewWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
+	                    Tcl_Interp *interp, int objc,
+	                    Tcl_Obj *CONST objv[]));
+static int		DiffviewBboxSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
+static int		DiffviewDiffsSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+	                    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
+static int		DiffviewTextSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
+static int		DiffviewXviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
+static int		DiffviewYviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
+static int		DiffviewGetIndex _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Diffview *dvPtr, Tcl_Obj *objPtr, int *linePtr,
+			    DiffviewDiffOp **diffOpPtrPtr));
+
+static void		DestroyDiffview _ANSI_ARGS_((char *memPtr));
+static int		ConfigureDiffview _ANSI_ARGS_((Tcl_Interp *interp,
+			    Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[],
+			    int flags));
+static void		DiffviewWorldChanged _ANSI_ARGS_((
+			    ClientData instanceData));
+static void		EventuallyRedraw _ANSI_ARGS_((Diffview *dvPtr));
+static void		DisplayDiffview _ANSI_ARGS_((ClientData clientData));
+static void		DiffviewComputeGeometry _ANSI_ARGS_((Diffview *dvPtr));
+static void		DiffviewEventProc _ANSI_ARGS_((ClientData clientData,
+			    XEvent *eventPtr));
+static void		DiffviewCmdDeletedProc _ANSI_ARGS_((
+			    ClientData clientData));
+static void		ChangeDiffviewView _ANSI_ARGS_((Diffview *dvPtr,
+			    int lnum));
+static void		DiffviewScanTo _ANSI_ARGS_((Diffview *dvPtr,
+			    int x, int y));
+static void		DiffviewUpdateLayout _ANSI_ARGS_((
+    			    Diffview *dvPtr));
+static void		DiffviewUpdateVScrollbar _ANSI_ARGS_((
+			    Diffview *dvPtr));
+static void		DiffviewUpdateHScrollbar _ANSI_ARGS_((
+    			    Diffview *dvPtr));
+static DiffviewLines*	DiffviewLinesCreate _ANSI_ARGS_((char *textPtr,
+			    int textLen));
+static void		DiffviewLinesFree _ANSI_ARGS_((
+			    DiffviewLines *lineLimitsPtr));
+static DiffviewDiffs*	DiffviewDiffsCreate _ANSI_ARGS_((
+			    char *textPtr1, DiffviewLines *limsPtr1,
+			    char *textPtr2, DiffviewLines *limsPtr2));
+static void		DiffviewDiffsAppend _ANSI_ARGS_((
+			    DiffviewDiffs *diffsPtr, int op,
+			    int fromIndex1, int toIndex1,
+			    int fromIndex2, int toIndex2));
+static void		DiffviewDiffsFree _ANSI_ARGS_((
+			    DiffviewDiffs *diffsPtr));
+static void		DiffviewLayoutAdd _ANSI_ARGS_((
+			    DiffviewLayout *layoutPtr,
+			    DiffviewLayoutLine *linePtr));
+static void		DiffviewLayoutClear _ANSI_ARGS_((
+			    DiffviewLayout *layoutPtr));
+static void		DiffviewLayoutFree _ANSI_ARGS_((
+			    DiffviewLayout *layoutPtr));
+
+/*
+ * Standard Tk procs invoked from generic window code.
+ */
+static Tk_ClassProcs diffviewClass = {
+    sizeof(Tk_ClassProcs),	/* size */
+    DiffviewWorldChanged,	/* worldChangedProc */
+};
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpDiffview_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "diffview" widget into the interpreter.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpDiffview_Init(interp)
+    Tcl_Interp *interp;         /* interpreter being initialized */
+{
+    static char *script = "source [file join $RapptureGUI::library scripts diffview.tcl]";
+
+    /* install the widget command */
+    Tcl_CreateObjCommand(interp, "Rappture::Diffview", DiffviewObjCmd,
+        NULL, NULL);
+
+    /* load the default bindings */
+    if (Tcl_Eval(interp, script) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewObjCmd()
+ *
+ * Called whenever a Diffview object is created to create the widget
+ * and install the Tcl access command.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewObjCmd(clientData, interp, objc, objv)
+    ClientData clientData;        /* not used */
+    Tcl_Interp *interp;                /* current interpreter */
+    int objc;                        /* number of command arguments */
+    Tcl_Obj *CONST objv[];        /* command argument objects */
+{
+    Diffview *dvPtr;
+    Tk_Window tkwin;
+
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
+        return TCL_ERROR;
+    }
+
+    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
+                Tcl_GetString(objv[1]), (char*)NULL);
+
+    if (tkwin == NULL) {
+        return TCL_ERROR;
+    }
+
+    /*
+     * Create a data structure for the widget objects and set good
+     * initial values.
+     */
+    dvPtr = (Diffview*)ckalloc(sizeof(Diffview));
+    memset((void*)dvPtr, 0, (sizeof(Diffview)));
+
+    dvPtr->tkwin   = tkwin;
+    dvPtr->display = Tk_Display(tkwin);
+    dvPtr->interp  = interp;
+    dvPtr->widgetCmd = Tcl_CreateObjCommand(interp,
+            Tk_PathName(dvPtr->tkwin), DiffviewWidgetObjCmd,
+            (ClientData)dvPtr, DiffviewCmdDeletedProc);
+    dvPtr->optionTable = Tk_CreateOptionTable(interp, optionSpecs);
+
+    dvPtr->buffer[0].textObj = NULL;
+    dvPtr->buffer[0].lineLimits = NULL;
+    dvPtr->buffer[1].textObj = NULL;
+    dvPtr->buffer[1].lineLimits = NULL;
+    dvPtr->diffdir = DIFF_1TO2;
+
+    dvPtr->cursor = None;
+    dvPtr->relief = TK_RELIEF_SUNKEN;
+    dvPtr->normGC = None;
+    dvPtr->addFgGC = None;
+    dvPtr->delFgGC = None;
+    dvPtr->chgFgGC = None;
+    dvPtr->xScrollUnit = 1;
+    dvPtr->yScrollUnit = 1;
+
+    /*
+     * Use reference counting to decide when this data should be
+     * cleaned up.
+     */
+    Tcl_Preserve((ClientData) dvPtr->tkwin);
+
+    Tk_SetClass(dvPtr->tkwin, "Diffview");
+    Tk_SetClassProcs(dvPtr->tkwin, &diffviewClass, (ClientData)dvPtr);
+
+    Tk_CreateEventHandler(dvPtr->tkwin,
+            ExposureMask|StructureNotifyMask|FocusChangeMask,
+            DiffviewEventProc, (ClientData)dvPtr);
+
+    if (Tk_InitOptions(interp, (char*)dvPtr, dvPtr->optionTable, tkwin)
+            != TCL_OK) {
+        Tk_DestroyWindow(dvPtr->tkwin);
+        return TCL_ERROR;
+    }
+
+    if (ConfigureDiffview(interp, dvPtr, objc-2, objv+2, 0) != TCL_OK) {
+        Tk_DestroyWindow(dvPtr->tkwin);
+        return TCL_ERROR;
+    }
+
+    Tcl_SetResult(interp, Tk_PathName(dvPtr->tkwin), TCL_STATIC);
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewWidgetObjCmd()
+ *
+ * Called to process the operations associated with the widget.  Handles
+ * the widget access command, using the operations defined in the
+ * commandNames[] array.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewWidgetObjCmd(clientData, interp, objc, objv)
+    ClientData clientData;    /* widget data */
+    Tcl_Interp *interp;       /* interp handling this command */
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+    Diffview *dvPtr = (Diffview*)clientData;
+    int result = TCL_OK;
+    int cmdToken;
+    
+    if (objc < 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
+        return TCL_ERROR;
+    }
+
+    /*
+     * Look up the command option and map it to an enumerated value.
+     */
+    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
+            "option", 0, &cmdToken);
+
+    if (result != TCL_OK) {
+        return result;
+    }
+
+    /* hang on to the widget data until we exit... */
+    Tcl_Preserve((ClientData)dvPtr);
+
+    switch (cmdToken) {
+        case COMMAND_BBOX: {
+            result = DiffviewBboxSubCmd(interp, dvPtr, objc, objv);
+            break;
+        }
+
+        case COMMAND_CGET: {
+            Tcl_Obj *objPtr;
+            if (objc != 3) {
+                Tcl_WrongNumArgs(interp, 2, objv, "option");
+                result = TCL_ERROR;
+                break;
+            }
+
+            objPtr = Tk_GetOptionValue(interp, (char*)dvPtr,
+                    dvPtr->optionTable, objv[2], dvPtr->tkwin);
+            if (objPtr == NULL) {
+                result = TCL_ERROR;
+                break;
+            }
+            Tcl_SetObjResult(interp, objPtr);
+            result = TCL_OK;
+            break;
+        }
+        
+        case COMMAND_CONFIGURE: {
+            Tcl_Obj *objPtr;
+            if (objc <= 3) {
+                /* query the value of an option */
+                objPtr = Tk_GetOptionInfo(interp, (char*)dvPtr,
+                        dvPtr->optionTable,
+                        (objc == 3) ? objv[2] : (Tcl_Obj*)NULL,
+                        dvPtr->tkwin);
+                if (objPtr == NULL) {
+                    result = TCL_ERROR;
+                    break;
+                } else {
+                    Tcl_SetObjResult(interp, objPtr);
+                    result = TCL_OK;
+                }
+            } else {
+                /* set one or more configuration options */
+                result = ConfigureDiffview(interp, dvPtr, objc-2, objv+2, 0);
+            }
+            break;
+        }
+
+        case COMMAND_DIFFS: {
+            result = DiffviewDiffsSubCmd(interp, dvPtr, objc, objv);
+            break;
+        }
+
+        case COMMAND_SCAN: {
+            int x, y, scanCmdIndex;
+
+            if (objc != 5) {
+                Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y");
+                result = TCL_ERROR;
+                break;
+            }
+
+            if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK
+                    || Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+                result = TCL_ERROR;
+                break;
+            }
+
+            result = Tcl_GetIndexFromObj(interp, objv[2], scanCommandNames,
+                    "option", 0, &scanCmdIndex);
+            if (result != TCL_OK) {
+                break;
+            }
+            switch (scanCmdIndex) {
+                case SCAN_MARK: {
+                    dvPtr->scanMarkX = x;
+                    dvPtr->scanMarkY = y;
+                    dvPtr->scanMarkXStart = dvPtr->xOffset;
+                    dvPtr->scanMarkYStart = dvPtr->yOffset;
+                    break;
+                }
+                case SCAN_DRAGTO: {
+                    DiffviewScanTo(dvPtr, x, y);
+                    break;
+                }
+            }
+            result = TCL_OK;
+            break;
+        }
+
+        case COMMAND_SEE: {
+            DiffviewDiffOp *diffOpPtr;
+            int line1, line2, topline;
+
+            if (objc != 3) {
+                Tcl_WrongNumArgs(interp, 2, objv, "index");
+                result = TCL_ERROR;
+                break;
+            }
+            result = DiffviewGetIndex(interp, dvPtr, objv[2],
+                &line1, &diffOpPtr);
+            if (result != TCL_OK) {
+                break;
+            }
+
+            if (line1 < 0 && diffOpPtr != NULL) {
+                line1 = diffOpPtr->fromWorld;
+                line2 = diffOpPtr->toWorld;
+            } else {
+                line2 = line1;
+            }
+
+            if (line1 >= dvPtr->worldview.numLines) {
+                line1 = line2 = dvPtr->worldview.numLines-1;
+            }
+            if (line1 < 0) {
+                line1 = line2 = 0;
+            }
+
+            if (line1 < dvPtr->topLine || line1 > dvPtr->btmLine) {
+                /* location off screen? then center it in y-view */
+                if (line2-line1 >= dvPtr->fullLines) {
+                    topline = line1;
+                } else {
+                    topline = (line1+line2)/2 - dvPtr->fullLines/2;
+                    if (topline < 0) topline = 0;
+                }
+                ChangeDiffviewView(dvPtr, topline);
+            }
+            result = TCL_OK;
+            break;
+        }
+
+        case COMMAND_TEXT: {
+            result = DiffviewTextSubCmd(interp, dvPtr, objc, objv);
+            break;
+        }
+
+        case COMMAND_XVIEW: {
+            result = DiffviewXviewSubCmd(interp, dvPtr, objc, objv);
+            break;
+        }
+        
+        case COMMAND_YVIEW: {
+            result = DiffviewYviewSubCmd(interp, dvPtr, objc, objv);
+            break;
+        }
+    }
+
+    /* okay, release the widget data and return */
+    Tcl_Release((ClientData)dvPtr);
+    return result;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewBboxSubCmd()
+ *
+ * Handles the "bbox" operation on the widget.  Returns a bounding
+ * box for the specified part of the widget.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewBboxSubCmd(interp, dvPtr, objc, objv)
+    Tcl_Interp *interp;       /* interp handling this command */
+    Diffview *dvPtr;          /* widget data */
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+    char *opt, buf[256];
+    DiffviewDiffOp *diffOpPtr;
+    int line, bnum, bline, x1, y0, y1, wd;
+    char *textPtr; int textLen;
+
+    if (objc != 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "index");
+        return TCL_ERROR;
+    }
+
+    /* make sure that our layout info is up to date for queries below */
+    DiffviewUpdateLayout(dvPtr);
+
+    opt = Tcl_GetString(objv[2]);
+    if (*opt == 'a' && strcmp(opt,"all") == 0) {
+        sprintf(buf, "0 0 %d %d", dvPtr->maxWidth, dvPtr->maxHeight);
+        Tcl_SetResult(interp, buf, TCL_VOLATILE);
+        return TCL_OK;
+    }
+
+    if (DiffviewGetIndex(interp, dvPtr, objv[2], &line, &diffOpPtr) != TCL_OK) {
+        Tcl_AppendResult(interp, " or all", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    /* return the bounding box for a line in world coords */
+    if (line >= 0) {
+        y0 = line*dvPtr->lineHeight;
+        y1 = (line+1)*dvPtr->lineHeight;
+        x1 = 0;
+        if (line < dvPtr->worldview.numLines) {
+            bnum = dvPtr->worldview.lines[line].bnum;
+            bline = dvPtr->worldview.lines[line].bline;
+            if (bline >= 0) {
+                textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
+                textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
+                x1 = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
+            }
+        }
+        sprintf(buf, "0 %d %d %d", y0, x1, y1);
+        Tcl_SetResult(interp, buf, TCL_VOLATILE);
+        return TCL_OK;
+    }
+
+    /* return the bounding box for a diff in world coords */
+    if (diffOpPtr) {
+        y0 = diffOpPtr->fromWorld*dvPtr->lineHeight;
+        y1 = (diffOpPtr->toWorld+1)*dvPtr->lineHeight;
+        x1 = 0;
+
+        for (line=diffOpPtr->fromWorld; line <= diffOpPtr->toWorld; line++) {
+            if (line < dvPtr->worldview.numLines) {
+                bnum = dvPtr->worldview.lines[line].bnum;
+                bline = dvPtr->worldview.lines[line].bline;
+                if (bline >= 0) {
+                    textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
+                    textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
+                    wd = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
+                    if (wd > x1) {
+                        x1 = wd;
+                    }
+                }
+            }
+        }
+        sprintf(buf, "0 %d %d %d", y0, x1, y1);
+        Tcl_SetResult(interp, buf, TCL_VOLATILE);
+    }
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewDiffsSubCmd()
+ *
+ * Handles the "diffs" operation on the widget with the following
+ * syntax:
+ *
+ *   widget diffs ?-std|-debug|-number?
+ *
+ * The default is "diffs -std", which returns the usual output from
+ * the Unix "diff" command.  The -debug option returns a more detailed
+ * output that is useful for testing/debugging.  The -number option
+ * returns the total number of diffs.  This is useful for commands
+ * such as "see #2", where you can bring a particular diff into view.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewDiffsSubCmd(interp, dvPtr, objc, objv)
+    Tcl_Interp *interp;       /* interp handling this command */
+    Diffview *dvPtr;          /* widget data */
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+    Tcl_Obj *resultPtr;
+    char range1[128], range2[128];
+    int i, n;
+
+    static CONST char *options[] = {
+        "-std",		"-debug",	"-number",	(char*)NULL
+    };
+    enum options {
+        DIFFS_STD,	DIFFS_DEBUG,	DIFFS_NUMBER
+    };
+    int op = DIFFS_STD;
+
+
+    if (objc > 3) {
+        Tcl_WrongNumArgs(interp, 2, objv, "?-std? ?-debug? ?-number?");
+        return TCL_ERROR;
+    }
+
+    /* decode the option that controls the return result */
+    if (objc > 2) {
+        if (Tcl_GetIndexFromObj(interp, objv[2], options, "option", 0,
+                &op) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+
+    /* make sure that our layout info is up to date for queries below */
+    DiffviewUpdateLayout(dvPtr);
+
+    switch ((enum options)op) {
+        case DIFFS_STD: {
+            DiffviewDiffOp curr, *currOpPtr;
+            DiffviewLines *lines1, *lines2;
+
+            if (dvPtr->diffsPtr) {
+                resultPtr = Tcl_GetObjResult(interp);
+                for (i=0; i < dvPtr->diffsPtr->numDiffs; i++) {
+                    currOpPtr = &dvPtr->diffsPtr->ops[i];
+
+                    /* if we're diff'ing the other way, reverse the diff */
+                    if (dvPtr->diffdir == DIFF_1TO2) {
+                        memcpy((VOID*)&curr, (VOID*)currOpPtr,
+                            sizeof(DiffviewDiffOp));
+                        lines1 = dvPtr->buffer[0].lineLimits;
+                        lines2 = dvPtr->buffer[1].lineLimits;
+                    } else {
+                        switch (currOpPtr->op) {
+                            case 'a': curr.op = 'd'; break;
+                            case 'd': curr.op = 'a'; break;
+                            default:  curr.op = currOpPtr->op; break;
+                        }
+                        curr.fromIndex1 = currOpPtr->fromIndex2;
+                        curr.toIndex1   = currOpPtr->toIndex2;
+                        curr.fromIndex2 = currOpPtr->fromIndex1;
+                        curr.toIndex2   = currOpPtr->toIndex1;
+                        lines1 = dvPtr->buffer[1].lineLimits;
+                        lines2 = dvPtr->buffer[0].lineLimits;
+                    }
+
+                    /* append the diff info onto the output */
+                    if (curr.fromIndex1 == curr.toIndex1) {
+                        if (curr.op == 'a') {
+                            /* for append, use first index at raw value */
+                            sprintf(range1, "%d", curr.fromIndex1);
+                        } else {
+                            sprintf(range1, "%d", curr.fromIndex1+1);
+                        }
+                    } else {
+                        sprintf(range1, "%d,%d", curr.fromIndex1+1,
+                            curr.toIndex1+1);
+                    }
+
+                    if (curr.fromIndex2 == curr.toIndex2) {
+                        if (curr.op == 'd') {
+                            /* for delete, use second index at raw value */
+                            sprintf(range2, "%d", curr.fromIndex2);
+                        } else {
+                            sprintf(range2, "%d", curr.fromIndex2+1);
+                        }
+                    } else {
+                        sprintf(range2, "%d,%d", curr.fromIndex2+1,
+                            curr.toIndex2+1);
+                    }
+
+                    switch (curr.op) {
+                        case 'a': {
+                            Tcl_AppendToObj(resultPtr, range1, -1);
+                            Tcl_AppendToObj(resultPtr, "a", 1);
+                            Tcl_AppendToObj(resultPtr, range2, -1);
+                            Tcl_AppendToObj(resultPtr, "\n", 1);
+                            for (n=curr.fromIndex2; n <= curr.toIndex2; n++) {
+                                Tcl_AppendToObj(resultPtr, "> ", 2);
+                                Tcl_AppendToObj(resultPtr, lines2->startPtr[n],
+                                    lines2->lenPtr[n]);
+                                Tcl_AppendToObj(resultPtr, "\n", 1);
+                            }
+                            break;
+                        }
+                        case 'd': {
+                            Tcl_AppendToObj(resultPtr, range1, -1);
+                            Tcl_AppendToObj(resultPtr, "d", 1);
+                            Tcl_AppendToObj(resultPtr, range2, -1);
+                            Tcl_AppendToObj(resultPtr, "\n", 1);
+                            for (n=curr.fromIndex1; n <= curr.toIndex1; n++) {
+                                Tcl_AppendToObj(resultPtr, "< ", 2);
+                                Tcl_AppendToObj(resultPtr, lines1->startPtr[n],
+                                    lines1->lenPtr[n]);
+                                Tcl_AppendToObj(resultPtr, "\n", 1);
+                            }
+                            break;
+                        }
+                        case 'c': {
+                            Tcl_AppendToObj(resultPtr, range1, -1);
+                            Tcl_AppendToObj(resultPtr, "c", 1);
+                            Tcl_AppendToObj(resultPtr, range2, -1);
+                            Tcl_AppendToObj(resultPtr, "\n", 1);
+                            for (n=curr.fromIndex1; n <= curr.toIndex1; n++) {
+                                Tcl_AppendToObj(resultPtr, "< ", 2);
+                                Tcl_AppendToObj(resultPtr, lines1->startPtr[n],
+                                    lines1->lenPtr[n]);
+                                Tcl_AppendToObj(resultPtr, "\n", 1);
+                            }
+                            Tcl_AppendToObj(resultPtr, "---\n", 4);
+                            for (n=curr.fromIndex2; n <= curr.toIndex2; n++) {
+                                Tcl_AppendToObj(resultPtr, "> ", 2);
+                                Tcl_AppendToObj(resultPtr, lines2->startPtr[n],
+                                    lines2->lenPtr[n]);
+                                Tcl_AppendToObj(resultPtr, "\n", 1);
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+            break;
+        }
+        case DIFFS_DEBUG: {
+            DiffviewDiffOp *c;
+            char elem[256];
+
+            if (dvPtr->diffsPtr) {
+                for (i=0; i < dvPtr->diffsPtr->numDiffs; i++) {
+                    c = &dvPtr->diffsPtr->ops[i];
+
+                    /* append the diff info onto the output */
+                    if (c->fromIndex1 == c->toIndex1) {
+                        sprintf(range1, "%d", c->fromIndex1);
+                    } else {
+                        sprintf(range1, "%d,%d", c->fromIndex1, c->toIndex1);
+                    }
+
+                    if (c->fromIndex2 == c->toIndex2) {
+                        sprintf(range2, "%d", c->fromIndex2);
+                    } else {
+                        sprintf(range2, "%d,%d", c->fromIndex2, c->toIndex2);
+                    }
+
+                    sprintf(elem, "%c %s %s", c->op, range1, range2);
+                    Tcl_AppendElement(interp, elem);
+                }
+            }
+            break;
+        }
+        case DIFFS_NUMBER: {
+            int num = 0;
+            if (dvPtr->diffsPtr) {
+                num = dvPtr->diffsPtr->numDiffs;
+            }
+            resultPtr = Tcl_NewIntObj(num);
+            Tcl_SetObjResult(interp, resultPtr);
+            break;
+        }
+    }
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewTextSubCmd()
+ *
+ * Handles the "text" operation on the widget with the following
+ * syntax:
+ *
+ *   widget text 1 ?string?
+ *   widget text 2 ?string?
+ *
+ * Used to get/set the text being diff'd and displayed in the widget.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewTextSubCmd(interp, dvPtr, objc, objv)
+    Tcl_Interp *interp;       /* interp handling this command */
+    Diffview *dvPtr;          /* widget data */
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+    int bufferNum;
+    DiffviewBuffer *slotPtr;
+
+    if (objc < 2 || objc > 4) {
+        Tcl_WrongNumArgs(interp, 2, objv, "bufferNum ?string?");
+        return TCL_ERROR;
+    }
+
+    /* decode the buffer number -- right now only buffers 1 & 2 */
+    if (Tcl_GetIntFromObj(interp, objv[2], &bufferNum) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (bufferNum < 1 || bufferNum > 2) {
+        Tcl_AppendResult(interp, "bad buffer number \"",
+            Tcl_GetString(objv[2]), "\": should be 1 or 2",
+            (char*)NULL);
+        return TCL_ERROR;
+    }
+    slotPtr = &dvPtr->buffer[bufferNum-1];
+
+    if (objc == 3) {
+        /* return current contents of the buffer */
+        if (slotPtr->textObj != NULL) {
+            Tcl_SetObjResult(interp, slotPtr->textObj);
+        } else {
+            Tcl_ResetResult(interp);
+        }
+        return TCL_OK;
+    }
+
+    /* set and return the new contents of the buffer */
+    if (slotPtr->textObj != NULL) {
+        Tcl_DecrRefCount(slotPtr->textObj);
+        DiffviewLinesFree(slotPtr->lineLimits);
+        slotPtr->lineLimits = NULL;
+    }
+    slotPtr->textObj = objv[3];
+    Tcl_IncrRefCount(slotPtr->textObj);
+
+    EventuallyRedraw(dvPtr);
+
+    Tcl_SetObjResult(interp, slotPtr->textObj);
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewXviewSubCmd()
+ *
+ * Handles the "xview" operation on the widget.  Adjusts the x-axis view
+ * according to some request, which usually comes from a scrollbar.
+ * Supports the standard Tk operations for "xview".
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewXviewSubCmd(interp, dvPtr, objc, objv)
+    Tcl_Interp *interp;       /* interp handling this command */
+    Diffview *dvPtr;          /* widget data */
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+
+    int offset = 0;
+    int index, count, type, viewWidth, windowUnits, maxOffset;
+    double fraction, fraction2;
+    
+    viewWidth = Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset;
+
+    if (objc == 2) {
+        /*
+         * COMMAND: widget xview
+         * return current limits as fractions 0-1
+         */
+        if (dvPtr->maxWidth == 0) {
+            Tcl_SetResult(interp, "0 1", TCL_STATIC);
+        } else {
+            char buf[TCL_DOUBLE_SPACE*2+2];
+
+            fraction = dvPtr->xOffset/((double)dvPtr->maxWidth);
+            fraction2 = (dvPtr->xOffset + viewWidth)
+                /((double)dvPtr->maxWidth);
+            if (fraction2 > 1.0) {
+                fraction2 = 1.0;
+            }
+            sprintf(buf, "%g %g", fraction, fraction2);
+            Tcl_SetResult(interp, buf, TCL_VOLATILE);
+        }
+    } else {
+        if (objc == 3) {
+            /*
+             * COMMAND: widget xview index
+             * set left edge to character at index
+             */
+            if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            offset = index*dvPtr->xScrollUnit;
+        } else {
+            /*
+             * COMMAND: widget xview moveto fraction
+             * COMMAND: widget xview scroll number what
+             * handles more complex scrolling movements
+             */
+            type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
+            switch (type) {
+                case TK_SCROLL_ERROR:
+                    return TCL_ERROR;
+                case TK_SCROLL_MOVETO:
+                    offset = (int)(fraction*dvPtr->maxWidth + 0.5);
+                    break;
+                case TK_SCROLL_PAGES:
+                    windowUnits = viewWidth/dvPtr->xScrollUnit;
+                    if (windowUnits > 2) {
+                        offset = dvPtr->xOffset
+                            + count*dvPtr->xScrollUnit*(windowUnits-2);
+                    } else {
+                        offset = dvPtr->xOffset + count*dvPtr->xScrollUnit;
+                    }
+                    break;
+                case TK_SCROLL_UNITS:
+                    offset = dvPtr->xOffset + count*dvPtr->xScrollUnit;
+                    break;
+            }
+        }
+        maxOffset = dvPtr->maxWidth
+            - (Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset)
+            + (dvPtr->xScrollUnit - 1);
+        if (offset > maxOffset) { offset = maxOffset; }
+        if (offset < 0) { offset = 0; }
+
+        /* nudge back to an even boundary */
+        offset -= offset % dvPtr->xScrollUnit;
+
+        if (offset != dvPtr->xOffset) {
+            dvPtr->xOffset = offset;
+            dvPtr->flags |= UPDATE_H_SCROLLBAR;
+            EventuallyRedraw(dvPtr);
+        }
+    }
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewYviewSubCmd()
+ *
+ * Handles the "yview" operation on the widget.  Adjusts the y-axis view
+ * according to some request, which usually comes from a scrollbar.
+ * Supports the standard Tk operations for "yview".
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewYviewSubCmd(interp, dvPtr, objc, objv)
+    Tcl_Interp *interp;       /* interp handling this command */
+    Diffview *dvPtr;          /* widget data */
+    int objc;                 /* number of command arguments */
+    Tcl_Obj *CONST objv[];    /* command arguments */
+{
+    int index, yLines, count, type;
+    double fraction, fraction2;
+
+    /* make sure that our layout info is up to date */
+    DiffviewUpdateLayout(dvPtr);
+
+    if (objc == 2) {
+        /*
+         * COMMAND: widget yview
+         * return current limits as fractions 0-1
+         */
+        if (dvPtr->maxHeight == 0) {
+            Tcl_SetResult(interp, "0 1", TCL_STATIC);
+        } else {
+            char buf[TCL_DOUBLE_SPACE * 2+2];
+
+            yLines = dvPtr->maxHeight/dvPtr->lineHeight;
+            fraction = dvPtr->topLine/((double)yLines);
+            fraction2 = (dvPtr->topLine+dvPtr->fullLines)/((double)yLines);
+            if (fraction2 > 1.0) {
+                fraction2 = 1.0;
+            }
+            sprintf(buf, "%g %g", fraction, fraction2);
+            Tcl_SetResult(interp, buf, TCL_VOLATILE);
+        }
+    } else if (objc == 3) {
+        /*
+         * COMMAND: widget yview index
+         * set top edge to line at index
+         */
+        if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        ChangeDiffviewView(dvPtr, index);
+    } else {
+        /*
+         * COMMAND: widget yview moveto fraction
+         * COMMAND: widget yview scroll number what
+         * handles more complex scrolling movements
+         */
+        type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
+        switch (type) {
+            case TK_SCROLL_ERROR:
+                return TCL_ERROR;
+            case TK_SCROLL_MOVETO:
+                yLines = dvPtr->maxHeight/dvPtr->lineHeight;
+                index = (int)(yLines*fraction + 0.5);
+                break;
+            case TK_SCROLL_PAGES:
+                yLines = dvPtr->maxHeight/dvPtr->lineHeight;
+                if (dvPtr->fullLines > 2) {
+                    index = dvPtr->topLine + count*(dvPtr->fullLines-2);
+                } else {
+                    index = dvPtr->topLine + count;
+                }
+                break;
+            case TK_SCROLL_UNITS:
+                index = dvPtr->topLine + count;
+                break;
+        }
+        ChangeDiffviewView(dvPtr, index);
+    }
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewGetIndex()
+ *
+ * Used by many of the widget operations to parse an index into the
+ * widget view.  Handles the following syntax:
+ *   #NN ...... particular diff number, starting from 1
+ *   NN ....... particular line number in world view, starting from 0
+ *
+ * Returns TCL_OK if successful, and TCL_ERROR (along with an error
+ * message) if anything goes wrong.
+ * ----------------------------------------------------------------------
+ */
+static int
+DiffviewGetIndex(interp, dvPtr, objPtr, linePtr, diffOpPtrPtr)
+    Tcl_Interp *interp;             /* interp handling this command */
+    Diffview *dvPtr;                /* widget data */
+    Tcl_Obj *objPtr;                /* argument being parsed */
+    int *linePtr;                   /* returns: line number or -1 */
+    DiffviewDiffOp **diffOpPtrPtr;  /* returns: specific diff or NULL */
+{
+    int result = TCL_OK;
+    char *opt = Tcl_GetString(objPtr);
+    char *tail;
+    int dnum;
+
+    /* clear the return values */
+    if (linePtr) *linePtr = -1;
+    if (diffOpPtrPtr) *diffOpPtrPtr = NULL;
+
+    if (*opt == '#') {
+        dnum = strtol(opt+1, &tail, 10);
+        if (*tail != '\0' || dnum <= 0) {
+           result = TCL_ERROR;
+        }
+        else if (dvPtr->diffsPtr == NULL
+                   || dnum > dvPtr->diffsPtr->numDiffs) {
+           Tcl_AppendResult(interp, "diff \"", opt, "\" doesn't exist",
+               (char*)NULL);
+           return TCL_ERROR;
+        }
+        *diffOpPtrPtr = &dvPtr->diffsPtr->ops[dnum-1];
+    } else {
+        result = Tcl_GetIntFromObj(interp, objPtr, linePtr);
+    }
+
+    if (result != TCL_OK) {
+        Tcl_ResetResult(interp);
+        Tcl_AppendResult(interp, "bad index \"", opt, "\": should be "
+            "\"#n\" for diff, or an integer value for line number",
+            (char*)NULL);
+    }
+    return result;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DestroyDiffview()
+ *
+ * Used by Tcl_Release/Tcl_EventuallyFree to clean up the data associated
+ * with a widget when it is no longer being used.  This happens at the
+ * end of a widget destruction sequence.
+ * ----------------------------------------------------------------------
+ */
+static void
+DestroyDiffview(memPtr)
+    char *memPtr;             /* widget data */
+{
+    Diffview *dvPtr = (Diffview*)memPtr;
+    int bnum;
+
+    /*
+     * Clean up text associated with the diffs.
+     */
+    for (bnum=0; bnum < 2; bnum++) {
+        if (dvPtr->buffer[bnum].textObj != NULL) {
+            Tcl_DecrRefCount(dvPtr->buffer[bnum].textObj);
+        }
+        if (dvPtr->buffer[bnum].lineLimits != NULL) {
+            DiffviewLinesFree(dvPtr->buffer[bnum].lineLimits);
+        }
+    }
+
+    if (dvPtr->diffsPtr) {
+        DiffviewDiffsFree(dvPtr->diffsPtr);
+    }
+    DiffviewLayoutFree(&dvPtr->worldview);
+
+    /*
+     * Free up GCs and configuration options.
+     */
+    if (dvPtr->normGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
+    }
+    if (dvPtr->addFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
+    }
+    if (dvPtr->delFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
+    }
+    if (dvPtr->chgFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
+    }
+
+    Tk_FreeConfigOptions((char*)dvPtr, dvPtr->optionTable, dvPtr->tkwin);
+    Tcl_Release((ClientData) dvPtr->tkwin);
+    dvPtr->tkwin = NULL;
+    ckfree((char*)dvPtr);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * ConfigureDiffview()
+ *
+ * Takes a list of configuration options in objc/objv format and applies
+ * the settings to the widget.  This is the underlying "configure"
+ * operation for the widget.
+ * ----------------------------------------------------------------------
+ */
+static int
+ConfigureDiffview(interp, dvPtr, objc, objv, flags)
+    Tcl_Interp *interp;       /* interp handling this command */
+    Diffview *dvPtr;          /* widget data */
+    int objc;                 /* number of configuration option words */
+    Tcl_Obj *CONST objv[];    /* configuration option words */
+    int flags;                /* flags for Tk_ConfigureWidget */
+{
+    Tcl_Obj *errorResult = NULL;
+    int status;
+    Tk_SavedOptions savedOptions;
+
+    status = Tk_SetOptions(interp, (char*)dvPtr, dvPtr->optionTable,
+        objc, objv, dvPtr->tkwin, &savedOptions, (int*)NULL);
+
+    if (status != TCL_OK) {
+        errorResult = Tcl_GetObjResult(interp);
+        Tcl_IncrRefCount(errorResult);
+        Tk_RestoreSavedOptions(&savedOptions);
+
+        Tcl_SetObjResult(interp, errorResult);
+        Tcl_DecrRefCount(errorResult);
+        return TCL_ERROR;
+    }
+
+    /*
+     * Fix up the widget to react to any new configuration values.
+     */
+    Tk_SetBackgroundFromBorder(dvPtr->tkwin, dvPtr->normalBorder);
+
+    if (dvPtr->highlightWidth < 0) {
+        dvPtr->highlightWidth = 0;
+    }
+    dvPtr->inset = dvPtr->highlightWidth + dvPtr->borderWidth;
+
+    if (dvPtr->tklastfont != dvPtr->tkfont) {
+        /* the font changed, so re-measure everything */
+        dvPtr->flags |= FONT_CHANGED;
+        dvPtr->tklastfont = dvPtr->tkfont;
+    }
+
+    Tk_FreeSavedOptions(&savedOptions);
+
+    DiffviewWorldChanged((ClientData)dvPtr);
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewWorldChanged()
+ *
+ * Called by Tk when the world has changed in some way that causes
+ * all GCs and other things to be recomputed.  Reinitializes the
+ * widget and gets it ready to draw.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewWorldChanged(cdata)
+    ClientData cdata;        /* widget data */
+{
+    Diffview *dvPtr = (Diffview*)cdata;
+    GC gc; XGCValues gcValues;
+    unsigned long mask;
+
+    /* GC for normal widget text */
+    gcValues.foreground = dvPtr->fgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->normGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
+    }
+    dvPtr->normGC = gc;
+
+    /* GC for added diff text */
+    gcValues.foreground = dvPtr->addFgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->addFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
+    }
+    dvPtr->addFgGC = gc;
+
+    /* GC for deleted diff text */
+    gcValues.foreground = dvPtr->delFgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->delFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
+    }
+    dvPtr->delFgGC = gc;
+
+    /* GC for changed diff text */
+    gcValues.foreground = dvPtr->chgFgColorPtr->pixel;
+    gcValues.graphics_exposures = False;
+    gcValues.font = Tk_FontId(dvPtr->tkfont);
+    mask = GCForeground | GCFont | GCGraphicsExposures;
+
+    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
+    if (dvPtr->chgFgGC != None) {
+        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
+    }
+    dvPtr->chgFgGC = gc;
+
+    /* this call comes when the font changes -- fix the layout */
+    dvPtr->flags |= FONT_CHANGED;
+
+    /* get ready to redraw */
+    DiffviewComputeGeometry(dvPtr);
+    dvPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
+    EventuallyRedraw(dvPtr);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * EventuallyRedraw()
+ *
+ * Arranges for the widget to redraw itself at the next idle point.
+ * ----------------------------------------------------------------------
+ */
+static void
+EventuallyRedraw(dvPtr)
+    Diffview *dvPtr;          /* widget data */
+{
+    if ((dvPtr->flags & REDRAW_PENDING) || (dvPtr->flags & WIDGET_DELETED)
+            || !Tk_IsMapped(dvPtr->tkwin)) {
+        return;  /* no need to redraw */
+    }
+
+    dvPtr->flags |= REDRAW_PENDING;
+    Tcl_DoWhenIdle(DisplayDiffview, (ClientData)dvPtr);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DisplayDiffview()
+ *
+ * Redraws the widget based on the current view and all data.
+ * ----------------------------------------------------------------------
+ */
+static void
+DisplayDiffview(cdata)
+    ClientData cdata;        /* widget data */
+{
+    Diffview *dvPtr = (Diffview*)cdata;
+    Tk_Window tkwin = dvPtr->tkwin;
+
+    int i, bnum, bline, x, xw, y, ymid, width;
+    char *textPtr; int textLen;
+    Pixmap pixmap;
+    GC bg, fg;
+
+    /* handling redraw now -- no longer pending */
+    dvPtr->flags &= ~REDRAW_PENDING;
+
+    if (dvPtr->flags & WIDGET_DELETED) {
+        /* widget is being torn down -- bail out! */
+        return;
+    }
+
+    /* handle any pending layout changes */
+    DiffviewUpdateLayout(dvPtr);
+
+    /*
+     * Hang onto the widget data until we're done updating scrollbars.
+     * The -xscrollcommand and -yscrollcommand options may take us into
+     * code that deletes the widget.
+     */
+    Tcl_Preserve((ClientData)dvPtr);
+
+    if (dvPtr->flags & UPDATE_V_SCROLLBAR) {
+        DiffviewUpdateVScrollbar(dvPtr);
+        if ((dvPtr->flags & WIDGET_DELETED) || !Tk_IsMapped(tkwin)) {
+            Tcl_Release((ClientData)dvPtr);
+            return;
+        }
+    }
+    if (dvPtr->flags & UPDATE_H_SCROLLBAR) {
+        DiffviewUpdateHScrollbar(dvPtr);
+        if ((dvPtr->flags & WIDGET_DELETED) || !Tk_IsMapped(tkwin)) {
+            Tcl_Release((ClientData)dvPtr);
+            return;
+        }
+    }
+    dvPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);
+    Tcl_Release((ClientData)dvPtr);
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+    /*
+     * Best solution is to draw everything into a temporary pixmap
+     * and copy that to the screen in one shot.  That avoids flashing.
+     */
+    pixmap = Tk_GetPixmap(dvPtr->display, Tk_WindowId(tkwin),
+            Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
+#else
+    pixmap = Tk_WindowId(tkwin);
+#endif
+
+    Tk_Fill3DRectangle(tkwin, pixmap, dvPtr->normalBorder, 0, 0,
+            Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
+
+    /*
+     * Find the top line in the view in each buffer
+     */
+    y = dvPtr->inset + dvPtr->topLine*dvPtr->lineHeight + dvPtr->lineAscent
+          - dvPtr->yOffset;
+    x = dvPtr->inset - dvPtr->xOffset;
+
+    if (Tk_Width(tkwin) > dvPtr->maxWidth) {
+        width = Tk_Width(tkwin)+10;
+    } else {
+        width = dvPtr->maxWidth+10;
+    }
+
+    for (i=dvPtr->topLine;
+         i <= dvPtr->btmLine && i < dvPtr->worldview.numLines;
+         i++) {
+
+        /* draw any diff rectangle for this line */
+        fg = dvPtr->normGC;
+        bg = None;
+
+        switch (dvPtr->worldview.lines[i].style) {
+            case 'a': {
+                fg = dvPtr->addFgGC;
+                bg = Tk_GCForColor(dvPtr->addBgColorPtr, pixmap);
+                break;
+            }
+            case 'd': {
+                fg = dvPtr->delFgGC;
+                bg = Tk_GCForColor(dvPtr->delBgColorPtr, pixmap);
+                break;
+            }
+            case 'c': {
+                fg = dvPtr->chgFgGC;
+                bg = Tk_GCForColor(dvPtr->chgBgColorPtr, pixmap);
+                break;
+            }
+        }
+
+        if (bg != None) {
+            XFillRectangle(Tk_Display(tkwin), pixmap, bg,
+                x, y - dvPtr->lineAscent,
+                (unsigned int)width, (unsigned int)dvPtr->lineHeight);
+        }
+
+        bnum = dvPtr->worldview.lines[i].bnum;
+        bline = dvPtr->worldview.lines[i].bline;
+
+        /* a negative number means "leave the line blank" */
+        if (bline >= 0) {
+            textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
+            textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
+
+            /* Draw the actual text of this item */
+            Tk_DrawChars(dvPtr->display, pixmap, fg, dvPtr->tkfont,
+                textPtr, textLen, x, y);
+
+            /* Draw an overstrike on deleted text */
+            if (dvPtr->worldview.lines[i].style == 'd' && dvPtr->overStrDel) {
+                xw = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen) + 5;
+                ymid = y - dvPtr->lineAscent/2;
+                XDrawLine(Tk_Display(tkwin), pixmap, fg, 0, ymid, xw, ymid);
+            }
+        }
+
+        y += dvPtr->lineHeight;
+    }
+
+    /*
+     * Draw the border on top so that it covers any characters that
+     * have scrolled off screen.
+     */
+    Tk_Draw3DRectangle(tkwin, pixmap, dvPtr->normalBorder,
+            dvPtr->highlightWidth, dvPtr->highlightWidth,
+            Tk_Width(tkwin) - 2*dvPtr->highlightWidth,
+            Tk_Height(tkwin) - 2*dvPtr->highlightWidth,
+            dvPtr->borderWidth, dvPtr->relief);
+
+    if (dvPtr->highlightWidth > 0) {
+        XColor *color; GC gc;
+
+        color = (dvPtr->flags & GOT_FOCUS)
+            ? dvPtr->highlightColor : dvPtr->highlightBgColor;
+        gc = Tk_GCForColor(color, pixmap);
+        Tk_DrawFocusHighlight(dvPtr->tkwin, gc, dvPtr->highlightWidth, pixmap);
+    }
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+    XCopyArea(dvPtr->display, pixmap, Tk_WindowId(tkwin),
+            dvPtr->normGC, 0, 0, (unsigned) Tk_Width(tkwin),
+            (unsigned) Tk_Height(tkwin), 0, 0);
+    Tk_FreePixmap(dvPtr->display, pixmap);
+#endif
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewComputeGeometry()
+ *
+ * Recomputes the overall dimensions of the widget when the overall
+ * size or the borderwidth is changed.  Registers a new size for the
+ * widget.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewComputeGeometry(dvPtr)
+    Diffview *dvPtr;          /* widget data */
+{
+    int pixelWidth, pixelHeight;
+
+    dvPtr->xScrollUnit = Tk_TextWidth(dvPtr->tkfont, "0", 1);
+    if (dvPtr->xScrollUnit == 0) {
+        dvPtr->xScrollUnit = 1;
+    }
+
+    pixelWidth = dvPtr->width + 2*dvPtr->inset;
+    pixelHeight = dvPtr->height + 2*dvPtr->inset;
+    Tk_GeometryRequest(dvPtr->tkwin, pixelWidth, pixelHeight);
+    Tk_SetInternalBorder(dvPtr->tkwin, dvPtr->inset);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewEventProc()
+ *
+ * Catches important events on the widget and causes it to redraw
+ * itself and react to focus changes.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewEventProc(cdata, eventPtr)
+    ClientData cdata;        /* widget data */
+    XEvent *eventPtr;        /* event information coming from X11 */
+{
+    Diffview *dvPtr = (Diffview*)cdata;
+    
+    if (eventPtr->type == Expose) {
+        EventuallyRedraw(dvPtr);
+    }
+    else if (eventPtr->type == DestroyNotify) {
+        if (!(dvPtr->flags & WIDGET_DELETED)) {
+            dvPtr->flags |= WIDGET_DELETED;
+            Tcl_DeleteCommandFromToken(dvPtr->interp, dvPtr->widgetCmd);
+            if (dvPtr->flags & REDRAW_PENDING) {
+                Tcl_CancelIdleCall(DisplayDiffview, cdata);
+            }
+            Tcl_EventuallyFree(cdata, DestroyDiffview);
+        }
+    }
+    else if (eventPtr->type == ConfigureNotify) {
+        dvPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
+        ChangeDiffviewView(dvPtr, dvPtr->topLine);
+        EventuallyRedraw(dvPtr);
+    }
+    else if (eventPtr->type == FocusIn) {
+        if (eventPtr->xfocus.detail != NotifyInferior) {
+            dvPtr->flags |= GOT_FOCUS;
+            EventuallyRedraw(dvPtr);
+        }
+    }
+    else if (eventPtr->type == FocusOut) {
+        if (eventPtr->xfocus.detail != NotifyInferior) {
+            dvPtr->flags &= ~GOT_FOCUS;
+            EventuallyRedraw(dvPtr);
+        }
+    }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewCmdDeletedProc --
+ *
+ * Invoked whenever the command associated with the widget is deleted.
+ * If the widget is not already being destroyed, this starts the process.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewCmdDeletedProc(cdata)
+    ClientData cdata;        /* widget data */
+{
+    Diffview *dvPtr = (Diffview*)cdata;
+
+    /*
+     * This gets invoked whenever the command is deleted.  If we haven't
+     * started destroying the widget yet, then do it now to clean up.
+     */
+    if (!(dvPtr->flags & WIDGET_DELETED)) {
+        Tk_DestroyWindow(dvPtr->tkwin);
+    }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * ChangeDiffviewView()
+ *
+ * Changes the y-view of the widget for scrolling operations.  Sets the
+ * given line to the top of the view.
+ * ----------------------------------------------------------------------
+ */
+static void
+ChangeDiffviewView(dvPtr, lnum)
+    Diffview *dvPtr;          /* widget data */
+    int lnum;                 /* this line should appear at the top */
+{
+    int maxLines, ySize, yLines;
+
+    DiffviewUpdateLayout(dvPtr);
+    ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
+    yLines = ySize/dvPtr->lineHeight;
+    maxLines = dvPtr->maxHeight/dvPtr->lineHeight;
+
+    if (lnum >= (maxLines - yLines)) {
+        lnum = maxLines - yLines;
+    }
+    if (lnum < 0) {
+        lnum = 0;
+    }
+    if (dvPtr->topLine != lnum) {
+        dvPtr->topLine = lnum;
+        dvPtr->yOffset = lnum * dvPtr->lineHeight;
+        EventuallyRedraw(dvPtr);
+        dvPtr->flags |= UPDATE_V_SCROLLBAR;
+    }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewScanTo()
+ *
+ * Implements the "scan dragto" operation on the widget.  Given a
+ * starting point from the "scan mark" operation, this adjusts the
+ * view to the given location.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewScanTo(dvPtr, x, y)
+    Diffview *dvPtr;          /* widget data */
+    int x;                    /* x-coord for drag-to location */
+    int y;                    /* y-coord for drag-to location */
+{
+    int newTopLine, newOffset, maxLine, maxOffset;
+
+    /*
+     * Compute the top line for the display by amplifying the difference
+     * between the current "drag" point and the original "start" point.
+     */
+    maxLine = dvPtr->maxHeight/dvPtr->lineHeight - dvPtr->fullLines;
+
+    newTopLine = dvPtr->scanMarkYStart
+            - (10*(y - dvPtr->scanMarkY))/dvPtr->lineHeight;
+
+    if (newTopLine > maxLine) {
+        newTopLine = dvPtr->scanMarkYStart = maxLine;
+        dvPtr->scanMarkY = y;
+    }
+    else if (newTopLine < 0) {
+        newTopLine = dvPtr->scanMarkYStart = 0;
+        dvPtr->scanMarkY = y;
+    }
+    ChangeDiffviewView(dvPtr, newTopLine);
+
+    /*
+     * Compute the left edge for the display by amplifying the difference
+     * between the current "drag" point and the original "start" point.
+     */
+    maxOffset = dvPtr->maxWidth - (Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset);
+    newOffset = dvPtr->scanMarkXStart - (10*(x - dvPtr->scanMarkX));
+    if (newOffset > maxOffset) {
+        newOffset = dvPtr->scanMarkXStart = maxOffset;
+        dvPtr->scanMarkX = x;
+    } else if (newOffset < 0) {
+        newOffset = dvPtr->scanMarkXStart = 0;
+        dvPtr->scanMarkX = x;
+    }
+    newOffset -= newOffset % dvPtr->xScrollUnit;
+
+    if (newOffset != dvPtr->xOffset) {
+        dvPtr->xOffset = newOffset;
+        dvPtr->flags |= UPDATE_H_SCROLLBAR;
+        EventuallyRedraw(dvPtr);
+    }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewUpdateLayout()
+ *
+ * Called whenever the widget is about to access layout information
+ * to make sure that the latest info is in place and up-to-date.
+ * Computes the line start/end for each line in each buffer, then
+ * computes the diffs and figures out how many lines to show.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewUpdateLayout(dvPtr)
+    Diffview *dvPtr;          /* widget data */
+{
+    int changes = 0;   /* no layout changes yet */
+    DiffviewBuffer* bufferPtr;
+    DiffviewDiffOp *currOp;
+    DiffviewLayoutLine line;
+    int i, bnum, bline, pixelWidth, maxWidth, ySize;
+    int numLines1, numLines2, lnum1, lnum2, dnum, lastdnum, pastDiff;
+    char *textPtr, *textPtr2; int textLen;
+    Tk_FontMetrics fm;
+
+    /* if the font changed, then re-measure everything */
+    changes = ((dvPtr->flags & FONT_CHANGED) != 0);
+
+    /* fill in line limits for any new data */
+    for (bnum=0; bnum < 2; bnum++) {
+        bufferPtr = &dvPtr->buffer[bnum];
+
+        if (bufferPtr->textObj != NULL && bufferPtr->lineLimits == NULL) {
+            textPtr = Tcl_GetStringFromObj(bufferPtr->textObj, &textLen);
+            bufferPtr->lineLimits = DiffviewLinesCreate(textPtr, textLen);
+            changes = 1;
+        }
+    }
+
+    if (changes) {
+        /* recompute the diffs between the buffers */
+        if (dvPtr->diffsPtr) {
+            DiffviewDiffsFree(dvPtr->diffsPtr);
+            dvPtr->diffsPtr = NULL;
+        }
+
+        if (dvPtr->buffer[0].textObj && dvPtr->buffer[1].textObj) {
+            textPtr = Tcl_GetStringFromObj(dvPtr->buffer[0].textObj, &textLen);
+            textPtr2 = Tcl_GetStringFromObj(dvPtr->buffer[1].textObj, &textLen);
+
+            dvPtr->diffsPtr = DiffviewDiffsCreate(
+                textPtr, dvPtr->buffer[0].lineLimits,
+                textPtr2, dvPtr->buffer[1].lineLimits);
+        }
+
+        /*
+         * Compute the layout of all lines according to the current
+         * view mode.  Each line in the world view is stored in the
+         * array dvPtr->worldview.lines.  Each line has a style (color
+         * for normal, add, delete, etc.) and an indication of which
+         * buffer it comes from.
+         */
+        DiffviewLayoutClear(&dvPtr->worldview);
+
+        /*
+         * March through the lines and figure out the source and style
+         * of each line based on the diffs:
+         *   'n' = normal (common) line
+         *   'a' = draw with the "added" style
+         *   'd' = draw with the "deleted" style
+         *   'c' = draw with the "changed" style
+         */
+        numLines1 = (dvPtr->buffer[0].lineLimits)
+                       ? dvPtr->buffer[0].lineLimits->numLines : 0;
+        numLines2 = (dvPtr->buffer[1].lineLimits)
+                       ? dvPtr->buffer[1].lineLimits->numLines : 0;
+
+        dnum = 0;  /* current difference in diffsPtr */
+        lnum1 = lnum2 = 0;
+
+        while (lnum1 < numLines1 || lnum2 < numLines2) {
+            /* assume it's a normal-looking line (buffers are the same) */
+            line.style = 'n';
+            line.bnum = 0;
+            line.bline = lnum1;
+            line.diffNum = -1;
+
+            /* is there a diff that contains this line? */
+            if (dvPtr->diffsPtr && dnum < dvPtr->diffsPtr->numDiffs) {
+              currOp = &dvPtr->diffsPtr->ops[dnum];
+
+              if ( (lnum1 >= currOp->fromIndex1
+                     && lnum1 <= currOp->toIndex1)
+                || (lnum2 >= currOp->fromIndex2
+                     && lnum2 <= currOp->toIndex2) ) {
+
+                line.diffNum = dnum;  /* line is part of this diff */
+
+                switch (currOp->op) {
+                    case 'c': {
+                        if (dvPtr->layout == LAYOUT_INLINE) {
+                            if (dvPtr->diffdir == DIFF_1TO2) {
+                                /* show the buffer #1 lines first, then #2 */
+                                if (lnum1 <= currOp->toIndex1) {
+                                    line.style = 'd';
+                                    line.bnum = 0;
+                                    line.bline = lnum1++;
+                                } else {
+                                    line.style = 'a';
+                                    line.bnum = 1;
+                                    line.bline = lnum2++;
+                                }
+                            } else {
+                                /* reverse -- show #2 lines first, then #1 */
+                                if (lnum2 <= currOp->toIndex2) {
+                                    line.style = 'd';
+                                    line.bnum = 1;
+                                    line.bline = lnum2++;
+                                } else {
+                                    line.style = 'a';
+                                    line.bnum = 0;
+                                    line.bline = lnum1++;
+                                }
+                            }
+                        } else {
+                            if (dvPtr->diffdir == DIFF_1TO2) {
+                                /* show final lines in buf #2 as "changed" */
+                                line.style = 'c';
+                                line.bnum = 1;
+                                line.bline = lnum2++;
+                                lnum1 = currOp->toIndex1+1;
+                            } else {
+                                /* show final lines in buf #1 as "changed" */
+                                line.style = 'c';
+                                line.bnum = 0;
+                                line.bline = lnum1++;
+                                lnum2 = currOp->toIndex2+1;
+                            }
+                        }
+                        break;
+                    }
+                    case 'a': {
+                        if (dvPtr->diffdir == DIFF_1TO2) {
+                            /* show lines in buffer #2 as 'added' */
+                            line.style = 'a';
+                            line.bnum = 1;
+                            line.bline = lnum2++;
+                            lnum1 = currOp->toIndex1;
+                        } else {
+                            /* reverse diff -- like we're deleting lines */
+                            if (dvPtr->layout == LAYOUT_INLINE) {
+                                line.style = 'd';
+                                line.bnum = 1;
+                                line.bline = lnum2++;
+                                lnum1 = currOp->toIndex1;
+                            } else {
+                                /* show lines from buffer #2 as empty */
+                                line.style = 'd';
+                                line.bnum = 0;
+                                line.bline = -1;
+                                lnum2++;
+                                lnum1 = currOp->toIndex1;
+                            }
+                        }
+                        break;
+                    }
+                    case 'd': {
+                        if (dvPtr->diffdir == DIFF_1TO2) {
+                            if (dvPtr->layout == LAYOUT_INLINE) {
+                                line.style = 'd';
+                                line.bnum = 0;
+                                line.bline = lnum1++;
+                                lnum2 = currOp->toIndex2;
+                            } else {
+                                /* show lines from buffer #2 as empty */
+                                line.style = 'd';
+                                line.bnum = 0;
+                                line.bline = -1;
+                                lnum1++;
+                                lnum2 = currOp->toIndex2;
+                            }
+                        } else {
+                            /* reverse diff -- like we're adding lines */
+                            line.style = 'a';
+                            line.bnum = 0;
+                            line.bline = lnum1++;
+                            lnum2 = currOp->toIndex2;
+                        }
+                        break;
+                    }
+                    default: {
+                        Tcl_Panic("bad diff type '%c' found in layout",
+                            currOp->op);
+                        break;
+                    }
+                }
+              } else {
+                /* normal line -- keep moving forward */
+                lnum1++; lnum2++;
+              }
+
+              /* have we reached the end of the diff? then move on */
+              pastDiff = 0;
+              switch (currOp->op) {
+                  case 'c':
+                      pastDiff = (lnum1 > currOp->toIndex1
+                               && lnum2 > currOp->toIndex2);
+                      break;
+                  case 'a':
+                      pastDiff = (lnum2 > currOp->toIndex2);
+                      break;
+                  case 'd':
+                      pastDiff = (lnum1 > currOp->toIndex1);
+                      break;
+              }
+              if (pastDiff) {
+                  dnum++;
+              }
+            } else {
+                /* no more diffs -- keep moving forward */
+                lnum1++; lnum2++;
+            }
+
+            /* add this new line to the layout */
+            DiffviewLayoutAdd(&dvPtr->worldview, &line);
+        }
+
+        /*
+         * Figure out where the diffs are located, and put that info
+         * back into the diffs.  This makes it easy later to refer to
+         * diff "#3" and understand what lines we're talking about.
+         */
+        lastdnum = -1;
+        for (i=0; i < dvPtr->worldview.numLines; i++) {
+            dnum = dvPtr->worldview.lines[i].diffNum;
+            if (dnum != lastdnum) {
+                if (lastdnum < 0) {
+                    /* leading edge -- catch the "from" line */
+                    lnum1 = i;
+                } else {
+                    /* trailing edge -- save diff info */
+                    dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
+                    dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
+                }
+            }
+            lastdnum = dnum;
+        }
+        if (lastdnum >= 0) {
+            dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
+            dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
+        }
+
+        /* compute overall text width for all lines */
+        maxWidth = 0;
+        for (i=0; i < dvPtr->worldview.numLines; i++) {
+            bline = dvPtr->worldview.lines[i].bline;
+            if (bline >= 0) {
+                bnum = dvPtr->worldview.lines[i].bnum;
+                bufferPtr = &dvPtr->buffer[bnum];
+
+                if (bufferPtr->lineLimits) {
+                    textPtr = bufferPtr->lineLimits->startPtr[bline];
+                    textLen = bufferPtr->lineLimits->lenPtr[bline];
+                    pixelWidth = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
+
+                    if (pixelWidth > maxWidth) {
+                        maxWidth = pixelWidth;
+                    }
+                }
+            }
+        }
+        dvPtr->maxWidth = maxWidth;
+
+        /* compute overall height of the widget */
+        Tk_GetFontMetrics(dvPtr->tkfont, &fm);
+        dvPtr->lineHeight = fm.linespace;
+        dvPtr->lineAscent = fm.ascent;
+        dvPtr->maxHeight = dvPtr->worldview.numLines * fm.linespace;
+
+        /* figure out the limits of the current view */
+        ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
+        dvPtr->topLine = dvPtr->yOffset/fm.linespace;
+        dvPtr->btmLine = (dvPtr->yOffset + ySize)/fm.linespace + 1;
+        dvPtr->fullLines = ySize/fm.linespace;
+
+        dvPtr->flags &= ~FONT_CHANGED;
+    }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewUpdateVScrollbar()
+ *
+ * Used to invoke the -yscrollcommand whenever the y-view has changed.
+ * This updates the scrollbar so that it shows the proper bubble.
+ * If there is no -yscrollcommand option, this does nothing.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewUpdateVScrollbar(dvPtr)
+    Diffview *dvPtr;          /* widget data */
+{
+    char string[TCL_DOUBLE_SPACE*2 + 2];
+    double first, last;
+    int result, ySize;
+
+    /* make sure the layout info is up to date */
+    DiffviewUpdateLayout(dvPtr);
+
+    /* update the limits of the current view */
+    ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
+    if (dvPtr->lineHeight > 0) {
+        dvPtr->topLine = dvPtr->yOffset/dvPtr->lineHeight;
+        dvPtr->btmLine = (dvPtr->yOffset + ySize)/dvPtr->lineHeight + 1;
+        dvPtr->fullLines = ySize/dvPtr->lineHeight;
+    }
+
+    /* if there's no scroll command, then there's nothing left to do */
+    if (dvPtr->yScrollCmd == NULL) {
+        return;
+    }
+
+    if (dvPtr->maxHeight == 0) {
+        first = 0.0;
+        last = 1.0;
+    } else {
+        first = dvPtr->yOffset/((double)dvPtr->maxHeight);
+        last = (dvPtr->yOffset + ySize)/((double)dvPtr->maxHeight);
+        if (last > 1.0) {
+            last = 1.0;
+        }
+    }
+    sprintf(string, " %g %g", first, last);
+
+    /* preserve/release the interp, in case it gets destroyed during the call */
+    Tcl_Preserve((ClientData)dvPtr->interp);
+
+    result = Tcl_VarEval(dvPtr->interp, dvPtr->yScrollCmd, string,
+            (char*)NULL);
+    if (result != TCL_OK) {
+        Tcl_AddErrorInfo(dvPtr->interp,
+                "\n    (vertical scrolling command executed by diffview)");
+        Tcl_BackgroundError(dvPtr->interp);
+    }
+
+    Tcl_Release((ClientData)dvPtr->interp);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewUpdateHScrollbar()
+ *
+ * Used to invoke the -xscrollcommand whenever the x-view has changed.
+ * This updates the scrollbar so that it shows the proper bubble.
+ * If there is no -xscrollcommand option, this does nothing.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewUpdateHScrollbar(dvPtr)
+    Diffview *dvPtr;          /* widget data */
+{
+    char string[TCL_DOUBLE_SPACE*2 + 3];
+    int result, viewWidth;
+    double first, last;
+
+    /* make sure the layout info is up to date */
+    DiffviewUpdateLayout(dvPtr);
+
+    /* if there's no scroll command, then there's nothing left to do */
+    if (dvPtr->xScrollCmd == NULL) {
+        return;
+    }
+
+    viewWidth = Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset;
+    if (dvPtr->maxWidth == 0) {
+        first = 0;
+        last = 1.0;
+    } else {
+        first = dvPtr->xOffset/((double)dvPtr->maxWidth);
+        last = (dvPtr->xOffset + viewWidth)/((double) dvPtr->maxWidth);
+        if (last > 1.0) {
+            last = 1.0;
+        }
+    }
+    sprintf(string, " %g %g", first, last);
+
+    /* preserve/release the interp, in case it gets destroyed during the call */
+    Tcl_Preserve((ClientData)dvPtr->interp);
+
+    result = Tcl_VarEval(dvPtr->interp, dvPtr->xScrollCmd, string,
+            (char*)NULL);
+    if (result != TCL_OK) {
+        Tcl_AddErrorInfo(dvPtr->interp,
+                "\n    (horizontal scrolling command executed by diffview)");
+        Tcl_BackgroundError(dvPtr->interp);
+    }
+
+    Tcl_Release((ClientData)dvPtr->interp);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLinesCreate()
+ *
+ * Used to breakup a Tcl string object into the indices for the start
+ * and end of each line.  This is the first step in the computation
+ * of diffs and the world view of the widget.
+ *
+ * Returns a pointer to a description of the line geometry, which should
+ * be freed by calling DiffviewLinesFree() when it is no longer needed.
+ * ----------------------------------------------------------------------
+ */
+static DiffviewLines*
+DiffviewLinesCreate(textPtr, textLen)
+    char *textPtr;                  /* text string being broken up */
+    int textLen;                    /* length of the string */
+{
+    int numLines = 0;
+    DiffviewLines *lineLimitsPtr;
+    char **newStarts;
+    int *newLens;
+    unsigned int len;
+
+    lineLimitsPtr = (DiffviewLines*)ckalloc(sizeof(DiffviewLines));
+    lineLimitsPtr->maxLines = 0;
+    lineLimitsPtr->startPtr = NULL;
+    lineLimitsPtr->lenPtr = NULL;
+
+    while (textLen > 0) {
+        /*
+         * If we're out of space, double the size and copy existing
+         * info over.
+         */
+        if (numLines >= lineLimitsPtr->maxLines) {
+            if (lineLimitsPtr->maxLines == 0) {
+                lineLimitsPtr->maxLines = 100;
+            } else {
+                lineLimitsPtr->maxLines *= 2;
+            }
+
+            /* resize the start point array */
+            newStarts = (char**)ckalloc(lineLimitsPtr->maxLines*sizeof(char*));
+            if (lineLimitsPtr->startPtr) {
+                memcpy((VOID*)newStarts, (VOID*)lineLimitsPtr->startPtr,
+                    numLines*sizeof(char*));
+                ckfree((char*)lineLimitsPtr->startPtr);
+            }
+            lineLimitsPtr->startPtr = newStarts;
+
+            /* resize the end point array */
+            newLens = (int*)ckalloc(lineLimitsPtr->maxLines*sizeof(int));
+            if (lineLimitsPtr->lenPtr) {
+                memcpy((VOID*)newLens, (VOID*)lineLimitsPtr->lenPtr,
+                    numLines*sizeof(int));
+                ckfree((char*)lineLimitsPtr->lenPtr);
+            }
+            lineLimitsPtr->lenPtr = newLens;
+        }
+
+        /* mark the start of this line, then search for the end */
+        lineLimitsPtr->startPtr[numLines] = textPtr;
+        lineLimitsPtr->lenPtr[numLines] = 0;
+
+        while (*textPtr != '\n' && textLen > 0) {
+            textPtr++;  textLen--;
+        }
+        if (textPtr > lineLimitsPtr->startPtr[numLines]) {
+            len = textPtr - lineLimitsPtr->startPtr[numLines];
+            lineLimitsPtr->lenPtr[numLines] = len;
+        }
+        numLines++;
+
+        /* skip over the newline and start with next line */
+        if (*textPtr == '\n') {
+            textPtr++;  textLen--;
+        }
+    }
+    lineLimitsPtr->numLines = numLines;
+
+    return lineLimitsPtr;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLinesFree()
+ *
+ * Used to free up the data structure created by DiffviewLinesCreate().
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLinesFree(lineLimitsPtr)
+    DiffviewLines *lineLimitsPtr;   /* data structure being freed */
+{
+    if (lineLimitsPtr) {
+        if (lineLimitsPtr->startPtr) {
+            ckfree((char*)lineLimitsPtr->startPtr);
+        }
+        if (lineLimitsPtr->lenPtr) {
+            ckfree((char*)lineLimitsPtr->lenPtr);
+        }
+        ckfree((char*)lineLimitsPtr);
+    }
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewDiffsCreate()
+ *
+ * Considers two strings textPtr1 and textPtr2 (divided into segments
+ * according to limsPtr1 and limsPtr2), and computes the longest common
+ * subsequences between the two strings.  This is the first step in
+ * computing the differences between the two strings.
+ *
+ * Returns a data structure that contains a series of "diff" operations.
+ * This should be freed when it is no longer needed by calling
+ * DiffviewDiffsFree().
+ *
+ *   REFERENCE:
+ *   J. W. Hunt and M. D. McIlroy, "An algorithm for differential
+ *   file comparison," Comp. Sci. Tech. Rep. #41, Bell Telephone
+ *   Laboratories (1976). Available on the Web at the second
+ *   author's personal site: http://www.cs.dartmouth.edu/~doug/
+ *
+ * ----------------------------------------------------------------------
+ */
+static DiffviewDiffs*
+DiffviewDiffsCreate(textPtr1, limsPtr1, textPtr2, limsPtr2)
+    char *textPtr1;           /* text from buffer #1 */
+    DiffviewLines *limsPtr1;  /* limits of individual strings in textPtr1 */
+    char *textPtr2;           /* text from buffer #2 */
+    DiffviewLines *limsPtr2;  /* limits of individual strings in textPtr2 */
+{
+    DiffviewDiffs *diffPtr = NULL;
+
+    Tcl_HashTable eqv;
+    Tcl_HashSearch iter;
+    Tcl_HashEntry *entryPtr;
+    Tcl_DString buffer;
+    DiffviewSubseq *K, *newK, newCandidate; 
+    int Kmax; int Klen;
+    Tcl_Obj *listPtr, **objv;
+    int i, j, candidateIdx, subseqLen, longestMatch;
+    int max, min, mid, midval, sLen, del;
+    int len, created, o, objc, index1, *lcsIndex1, index2, *lcsIndex2;
+    char *key;
+
+    newCandidate.index1 = -1;		/* Suppress compiler warning. */
+    newCandidate.index2 = -1;
+    newCandidate.next = -1;
+
+    /*
+     * Build a set of equivalence classes.  Scan through all of
+     * buffer #2 and map each string to a list of indices for the
+     * lines that have that string.
+     */
+    Tcl_DStringInit(&buffer);
+    Tcl_InitHashTable(&eqv, TCL_STRING_KEYS);
+    for (i=0; i < limsPtr2->numLines; i++) {
+        len = limsPtr2->lenPtr[i];
+        Tcl_DStringSetLength(&buffer, len);
+        key = Tcl_DStringValue(&buffer);
+        memcpy((VOID*)key, (VOID*)limsPtr2->startPtr[i], len);
+
+        entryPtr = Tcl_CreateHashEntry(&eqv, key, &created);
+        if (created) {
+            listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL);
+            Tcl_IncrRefCount(listPtr);
+            Tcl_SetHashValue(entryPtr, (ClientData)listPtr);
+        }
+
+        listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
+        Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, Tcl_NewIntObj(i));
+    }
+
+    /*
+     * Build a list K that holds the descriptions of the common
+     * subsequences.  At first, there is one common subsequence of
+     * length 0 with a fence that includes line -1 of both files.
+     */
+    Kmax = 10;
+    K = (DiffviewSubseq*)ckalloc(Kmax*sizeof(DiffviewSubseq));
+    K[0].index1 = -1;
+    K[0].index2 = -1;
+    K[0].next = -1;
+    K[1].index1 = limsPtr1->numLines;
+    K[1].index2 = limsPtr2->numLines;
+    K[1].next = -1;
+    Klen = 2;
+    longestMatch = 0;
+
+    /*
+     * Step through the first buffer line by line.
+     */
+    for (i=0; i < limsPtr1->numLines; i++) {
+        len = limsPtr1->lenPtr[i];
+        Tcl_DStringSetLength(&buffer, len);
+        key = Tcl_DStringValue(&buffer);
+        memcpy((VOID*)key, (VOID*)limsPtr1->startPtr[i], len);
+
+        /* look at each possible line j in second buffer */
+        entryPtr = Tcl_FindHashEntry(&eqv, key);
+        if (entryPtr) {
+            subseqLen = 0;
+            candidateIdx = 0;
+
+            listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
+            Tcl_ListObjGetElements((Tcl_Interp*)NULL, listPtr, &objc, &objv);
+            for (o=0; o < objc; o++) {
+                Tcl_GetIntFromObj((Tcl_Interp*)NULL, objv[o], &j);
+
+                /*
+                 * Binary search to find a candidate common subsequence.
+                 * This match may get appended to that.
+                 */
+                max  = longestMatch;
+                min  = subseqLen;
+                mid  = subseqLen;
+                sLen = longestMatch + 1;
+                while (max >= min) {
+                    mid = (max+min)/2;
+                    midval = K[mid].index2;
+                    if (j == midval) {
+                        break;
+                    } else if (j < midval) {
+                        max = mid-1;
+                    } else {
+                        sLen = mid;
+                        min = mid+1;
+                    }
+                }
+
+                /* no good candidate? then go to the next match point */
+                if (j == K[mid].index2 || sLen > longestMatch) {
+                    continue;
+                }
+
+                /*
+                 * sLen = sequence length of the longest sequence that
+                 *        this match point can be appended to
+                 *
+                 * Make a new candidate match and store the old on in K.
+                 * Set subseqLen to the length of the new candidate match.
+                 */
+                if (subseqLen >= 0) {
+                    if (candidateIdx >= 0) {
+                        K[subseqLen].index1 = K[candidateIdx].index1;
+                        K[subseqLen].index2 = K[candidateIdx].index2;
+                        K[subseqLen].next   = K[candidateIdx].next;
+                    } else {
+                        K[subseqLen].index1 = newCandidate.index1;
+                        K[subseqLen].index2 = newCandidate.index2;
+                        K[subseqLen].next   = newCandidate.next;
+                    }
+                }
+                newCandidate.index1 = i;
+                newCandidate.index2 = j;
+                newCandidate.next = sLen;
+                candidateIdx = -1;  /* use newCandidate info */
+                subseqLen = sLen + 1;
+
+                /*
+                 * If we've extended the length of the longest match,
+                 * we don't need to keep checking candidate lines.
+                 * We're done.  Move the fence.
+                 */
+                if (sLen >= longestMatch) {
+                    if (Klen >= Kmax) {
+                        Kmax *= 2;
+                        newK = (DiffviewSubseq*)ckalloc(Kmax*sizeof(DiffviewSubseq));
+                        memcpy((VOID*)newK, (VOID*)K, Klen*sizeof(DiffviewSubseq));
+                        ckfree((char*)K);
+                        K = newK;
+                    }
+                    K[Klen].index1 = K[Klen-1].index1;
+                    K[Klen].index2 = K[Klen-1].index2;
+                    K[Klen].next   = K[Klen-1].next;
+                    Klen++;
+
+                    longestMatch++;
+                    break;
+                }
+            }
+
+            /* put the last candidate into the array */
+            if (candidateIdx >= 0) {
+                K[subseqLen].index1 = K[candidateIdx].index1;
+                K[subseqLen].index2 = K[candidateIdx].index2;
+                K[subseqLen].next   = K[candidateIdx].next;
+            } else {
+                K[subseqLen].index1 = newCandidate.index1;
+                K[subseqLen].index2 = newCandidate.index2;
+                K[subseqLen].next   = newCandidate.next;
+            }
+        }
+    }
+
+    /*
+     * Translate the resulting info into a list of common subseq indices.
+     */
+    lcsIndex1 = (int*)ckalloc(longestMatch*sizeof(int));
+    memset((void*)lcsIndex1, 0, (longestMatch*sizeof(int)));
+    lcsIndex2 = (int*)ckalloc(longestMatch*sizeof(int));
+    memset((void*)lcsIndex2, 0, (longestMatch*sizeof(int)));
+    len = longestMatch;
+
+    candidateIdx = longestMatch;
+    while (K[candidateIdx].index1 >= 0) {
+        if (longestMatch-- < 0) {
+            Tcl_Panic("internal mismatch in diff algorithm");
+        }
+        lcsIndex1[longestMatch] = K[candidateIdx].index1;
+        lcsIndex2[longestMatch] = K[candidateIdx].index2;
+        candidateIdx = K[candidateIdx].next;
+    }
+
+    /*
+     * Now, march through all lines in both buffers and convert the
+     * lcs indices into a sequence of diff-style operations.
+     */
+    diffPtr = (DiffviewDiffs*)ckalloc(sizeof(DiffviewDiffs));
+    diffPtr->maxDiffs = 0;
+    diffPtr->numDiffs = 0;
+    diffPtr->ops = NULL;
+
+    i = j = 0;
+    for (o=0; o < len; o++) {
+        index1 = lcsIndex1[o];
+        index2 = lcsIndex2[o];
+
+        if (index1-i == index2-j && index1-i > 0) {
+            del = index1-i;
+            DiffviewDiffsAppend(diffPtr, 'c', i, i+del-1, j, j+del-1);
+            i += del; j += del;
+        } else {
+            del = index1-i;
+            if (del > 0) {
+                DiffviewDiffsAppend(diffPtr, 'd', i, i+del-1, j, j);
+                i += del;
+            }
+
+            del = index2-j;
+            if (del > 0) {
+                DiffviewDiffsAppend(diffPtr, 'a', i, i, j, j+del-1);
+                j += del;
+            }
+        }
+        i++; j++;
+    }
+
+    /* lines left over? mark as many "changed" as possible */
+    if (limsPtr1->numLines-i > 0 && limsPtr2->numLines-j > 0) {
+        del = (limsPtr1->numLines-i < limsPtr2->numLines-j)
+                ? limsPtr1->numLines-i : limsPtr2->numLines-j;
+        DiffviewDiffsAppend(diffPtr, 'c', i, i+del-1, j, j+del-1);
+        i += del;
+        j += del;
+    }
+
+    /* still have lines left over? then mark them added or deleted */
+    if (i < limsPtr1->numLines) {
+        del = limsPtr1->numLines - i;
+        DiffviewDiffsAppend(diffPtr, 'd', i, i+del-1, j, j);
+        i += del;
+    }
+    if (j < limsPtr2->numLines) {
+        del = limsPtr2->numLines - j;
+        DiffviewDiffsAppend(diffPtr, 'a', i, i, j, j+del-1);
+        j += del;
+    }
+
+    /*
+     * Clean up all memory used during the diff.
+     */
+    ckfree((char*)K);
+    ckfree((char*)lcsIndex1);
+    ckfree((char*)lcsIndex2);
+
+    entryPtr = Tcl_FirstHashEntry(&eqv, &iter);
+    while (entryPtr) {
+        listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
+        Tcl_DecrRefCount(listPtr);
+        entryPtr = Tcl_NextHashEntry(&iter);
+    }
+    Tcl_DeleteHashTable(&eqv);
+
+    return diffPtr;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewDiffsAppend()
+ *
+ * Appends a "diff" operation onto a list of diffs.  The list is extended
+ * automatically, if need be, to store the new element.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewDiffsAppend(diffsPtr, op, fromIndex1, toIndex1, fromIndex2, toIndex2)
+    DiffviewDiffs *diffsPtr;  /* diffs being updated */
+    int op;                   /* diff operation: a/d/c */
+    int fromIndex1;           /* starts at this line in buffer 1 */
+    int toIndex1;             /* ends at this line in buffer 1 */
+    int fromIndex2;           /* starts at this line in buffer 2 */
+    int toIndex2;             /* ends at this line in buffer 2 */
+{
+    DiffviewDiffOp *newOpArray;
+    int last;
+
+    if (diffsPtr->numDiffs >= diffsPtr->maxDiffs) {
+        if (diffsPtr->maxDiffs == 0) {
+            diffsPtr->maxDiffs = 10;
+        } else {
+            diffsPtr->maxDiffs *= 2;
+        }
+        newOpArray = (DiffviewDiffOp*)ckalloc(
+            diffsPtr->maxDiffs*sizeof(DiffviewDiffOp));
+
+        if (diffsPtr->ops) {
+            memcpy((VOID*)newOpArray, (VOID*)diffsPtr->ops,
+                diffsPtr->numDiffs*sizeof(DiffviewDiffOp));
+            ckfree((char*)diffsPtr->ops);
+        }
+        diffsPtr->ops = newOpArray;
+    }
+
+    last = diffsPtr->numDiffs;
+    diffsPtr->ops[last].op         = op;
+    diffsPtr->ops[last].fromIndex1 = fromIndex1;
+    diffsPtr->ops[last].toIndex1   = toIndex1;
+    diffsPtr->ops[last].fromIndex2 = fromIndex2;
+    diffsPtr->ops[last].toIndex2   = toIndex2;
+    diffsPtr->numDiffs++;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewDiffsFree()
+ *
+ * Frees up the storage previously created by calling
+ * DiffviewDiffsAppend().
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewDiffsFree(diffsPtr)
+    DiffviewDiffs *diffsPtr;  /* diffs being freed */
+{
+    if (diffsPtr->ops) {
+        ckfree((char*)diffsPtr->ops);
+    }
+    ckfree((char*)diffsPtr);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLayoutAdd()
+ *
+ * Clears out the layout storage in preparation for building another
+ * layout.  If the previous number of lines was much less than the
+ * maximum allocated, then this routine reallocates a smaller storage
+ * space.  This provides a way to give back a large chunk of memory
+ * if the contents of the widget is nulled out, for example.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLayoutAdd(layoutPtr, linePtr)
+    DiffviewLayout *layoutPtr;    /* line layout being updated */
+    DiffviewLayoutLine *linePtr;  /* line being added to layout */
+{
+    DiffviewLayoutLine *newLineArray;
+
+    /* expand the array as needed */
+    if (layoutPtr->numLines >= layoutPtr->maxLines) {
+        if (layoutPtr->maxLines == 0) {
+            layoutPtr->maxLines = 100;
+        } else {
+            layoutPtr->maxLines *= 2;
+        }
+        newLineArray = (DiffviewLayoutLine*)ckalloc(
+            (unsigned)(layoutPtr->maxLines*sizeof(DiffviewLayoutLine)));
+
+        if (layoutPtr->lines) {
+            memcpy((VOID*)newLineArray, (VOID*)layoutPtr->lines,
+                layoutPtr->numLines*sizeof(DiffviewLayoutLine));
+            ckfree((char*)layoutPtr->lines);
+        }
+        layoutPtr->lines = newLineArray;
+    }
+
+    /* copy the latest line in and bump the count */
+    memcpy((VOID*)&layoutPtr->lines[layoutPtr->numLines],
+        (VOID*)linePtr, sizeof(DiffviewLayoutLine));
+
+    layoutPtr->numLines++;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLayoutClear()
+ *
+ * Clears out the layout storage in preparation for building another
+ * layout.  If the previous number of lines was much less than the
+ * maximum allocated, then this routine reallocates a smaller storage
+ * space.  This provides a way to give back a large chunk of memory
+ * if the contents of the widget is nulled out, for example.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLayoutClear(layoutPtr)
+    DiffviewLayout *layoutPtr;  /* line layout being freed */
+{
+    if (layoutPtr->numLines > 0
+          && layoutPtr->numLines < layoutPtr->maxLines/3
+          && layoutPtr->maxLines > 100) {
+        ckfree((char*)layoutPtr->lines);
+        layoutPtr->lines = (DiffviewLayoutLine*)ckalloc(
+            (unsigned)(layoutPtr->numLines * sizeof(DiffviewLayoutLine)));
+        layoutPtr->maxLines = layoutPtr->numLines;
+    }
+    layoutPtr->numLines = 0;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * DiffviewLayoutFree()
+ *
+ * Frees the storage associated with the layout of all lines within
+ * the widget.
+ * ----------------------------------------------------------------------
+ */
+static void
+DiffviewLayoutFree(layoutPtr)
+    DiffviewLayout *layoutPtr;  /* line layout being freed */
+{
+    if (layoutPtr->lines) {
+        ckfree((char*)layoutPtr->lines);
+        layoutPtr->lines = NULL;
+    }
+}
Index: /branches/r9/packages/DxToVtk/RpDxToVtk.c
===================================================================
--- /branches/r9/packages/DxToVtk/RpDxToVtk.c	(revision 4841)
+++ /branches/r9/packages/DxToVtk/RpDxToVtk.c	(revision 4841)
@@ -0,0 +1,814 @@
+
+/* 
+ * ----------------------------------------------------------------------
+ *  RpDxToVtk - 
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  See the file "license.terms" for information on usage and
+ *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ * ======================================================================
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+#include <float.h>
+#include "tcl.h"
+
+#define DO_WEDGES
+//#define CHECK_WINDINGS
+
+static INLINE char *
+SkipSpaces(char *string) 
+{
+    while (isspace(*string)) {
+        string++;
+    }
+    return string;
+}
+
+static INLINE char *
+GetLine(char **stringPtr, const char *endPtr) 
+{
+    char *line, *p;
+
+    line = SkipSpaces(*stringPtr);
+    for (p = line; p < endPtr; p++) {
+        if (*p == '\n') {
+            *stringPtr = p + 1;
+            return line;
+        }
+    }
+    *stringPtr = p;
+    return line;
+}
+
+static int
+GetUniformFieldValues(Tcl_Interp *interp, int nPoints, int *counts, char **stringPtr, 
+                      const char *endPtr, Tcl_Obj *objPtr) 
+{
+    int i;
+    const char *p;
+    char mesg[2000];
+    double *array;
+    int iX, iY, iZ;
+
+    p = *stringPtr;
+    array = malloc(sizeof(double) * nPoints);
+    if (array == NULL) {
+        return TCL_ERROR;
+    }
+    iX = iY = iZ = 0;
+    for (i = 0; i < nPoints; i++) {
+        double value;
+        char *nextPtr;
+        int loc;
+
+        if (p >= endPtr) {
+            Tcl_AppendResult(interp, "unexpected EOF in reading field values",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        value = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading field values",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        loc = iZ*counts[0]*counts[1] + iY*counts[0] + iX;
+        if (++iZ >= counts[2]) {
+            iZ = 0;
+            if (++iY >= counts[1]) {
+                iY = 0;
+                ++iX;
+            }
+        }
+        array[loc] = value;
+    }
+    for (i = 0; i < nPoints; i++) {
+        sprintf(mesg, "%g\n", array[i]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+    }
+    free(array);
+    *stringPtr = (char *)p;
+    return TCL_OK;
+}
+
+static int
+GetStructuredGridFieldValues(Tcl_Interp *interp, int nPoints, char **stringPtr, 
+                             const char *endPtr, Tcl_Obj *objPtr) 
+{
+    int i;
+    const char *p;
+    char mesg[2000];
+    double *array;
+
+    p = *stringPtr;
+    array = malloc(sizeof(double) * nPoints);
+    if (array == NULL) {
+        return TCL_ERROR;
+    }
+    for (i = 0; i < nPoints; i++) {
+        double value;
+        char *nextPtr;
+
+        if (p >= endPtr) {
+            Tcl_AppendResult(interp, "unexpected EOF in reading field values",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        value = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading field values",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        array[i] = value;
+    }
+    for (i = 0; i < nPoints; i++) {
+        sprintf(mesg, "%g\n", array[i]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+    }
+    free(array);
+    *stringPtr = (char *)p;
+    return TCL_OK;
+}
+
+static int
+GetCloudFieldValues(Tcl_Interp *interp, int nXYPoints, int nZPoints, char **stringPtr, 
+                    const char *endPtr, Tcl_Obj *objPtr) 
+{
+    int i;
+    const char *p;
+    char mesg[2000];
+    double *array;
+    int iXY, iZ;
+    int nPoints;
+
+    nPoints = nXYPoints * nZPoints;
+
+    p = *stringPtr;
+    array = malloc(sizeof(double) * nPoints);
+    if (array == NULL) {
+        return TCL_ERROR;
+    }
+    iXY = iZ = 0;
+    for (i = 0; i < nPoints; i++) {
+        double value;
+        char *nextPtr;
+        int loc;
+
+        if (p >= endPtr) {
+            Tcl_AppendResult(interp, "unexpected EOF in reading field values",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        value = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading field values",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        loc = nXYPoints * iZ + iXY;
+        if (++iZ >= nZPoints) {
+            iZ = 0;
+            ++iXY;
+        }
+        array[loc] = value;
+    }
+    for (i = 0; i < nPoints; i++) {
+        sprintf(mesg, "%g\n", array[i]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+    }
+    free(array);
+    *stringPtr = (char *)p;
+    return TCL_OK;
+}
+
+static int
+GetPoints(Tcl_Interp *interp, double *array, int nXYPoints,
+          char **stringPtr, const char *endPtr) 
+{
+    int i;
+    const char *p;
+
+    p = *stringPtr;
+    if (array == NULL) {
+        return TCL_ERROR;
+    }
+    for (i = 0; i < nXYPoints; i++) {
+        double x, y;
+        char *nextPtr;
+
+        if (p >= endPtr) {
+            Tcl_AppendResult(interp, "unexpected EOF in reading points",
+                             (char *)NULL);
+            return TCL_ERROR;
+        }
+        x = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading points",
+                             (char *)NULL);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        y = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading points",
+                             (char *)NULL);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        /* z is unused */
+        strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading points",
+                             (char *)NULL);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+
+        array[i*2  ] = x;
+        array[i*2+1] = y;
+    }
+
+    *stringPtr = (char *)p;
+    return TCL_OK;
+}
+
+static int
+GetUniformFieldVectors(Tcl_Interp *interp, int nPoints, int *counts,
+                       char **stringPtr, const char *endPtr, Tcl_Obj *objPtr) 
+{
+    int i;
+    const char *p;
+    char mesg[2000];
+    double *array;
+    int iX, iY, iZ;
+
+    p = *stringPtr;
+    array = malloc(sizeof(double) * nPoints * 3);
+    if (array == NULL) {
+        return TCL_ERROR;
+    }
+    iX = iY = iZ = 0;
+    for (i = 0; i < nPoints; i++) {
+        double x, y, z;
+        char *nextPtr;
+        int loc;
+
+        if (p >= endPtr) {
+            Tcl_AppendResult(interp, "unexpected EOF in reading vectors",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        x = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading vectors",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        y = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading vectors",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        z = strtod(p, &nextPtr);
+        if (nextPtr == p) {
+            Tcl_AppendResult(interp, "bad value found in reading vectors",
+                             (char *)NULL);
+            free(array);
+            return TCL_ERROR;
+        }
+        p = nextPtr;
+        loc = iZ*counts[0]*counts[1] + iY*counts[0] + iX;
+        if (++iZ >= counts[2]) {
+            iZ = 0;
+            if (++iY >= counts[1]) {
+                iY = 0;
+                ++iX;
+            }
+        }
+        array[loc*3  ] = x;
+        array[loc*3+1] = y;
+        array[loc*3+2] = z;
+    }
+    for (i = 0; i < nPoints; i++) {
+        sprintf(mesg, "%g %g %g\n", array[i*3], array[i*3+1], array[i*3+2]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+    }
+    free(array);
+    *stringPtr = (char *)p;
+    return TCL_OK;
+}
+
+#if defined(DO_WEDGES) && defined(CHECK_WINDINGS)
+static void
+Normalize(double v[3])
+{
+    double len = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
+    v[0] /= len;
+    v[1] /= len;
+    v[2] /= len;
+}
+
+static double
+Dot(double v1[3], double v2[3])
+{
+    return (v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]);
+}
+
+static void
+Cross(double v1[3], double v2[3], double vout[3])
+{
+    vout[0] = v1[1]*v2[2] - v1[2]*v2[1];
+    vout[1] = v1[2]*v2[0] - v1[0]*v2[2];
+    vout[2] = v1[0]*v2[1] - v1[1]*v2[0];
+}
+#endif
+/* 
+ *  DxToVtk string
+ *
+ * In DX format:
+ *  rank 0 means scalars,
+ *  rank 1 means vectors,
+ *  rank 2 means matrices,
+ *  rank 3 means tensors
+ * 
+ *  For rank 1, shape is a single number equal to the number of dimensions.
+ *  e.g. rank 1 shape 3 means a 3-component vector field
+ *
+ */
+
+static int
+DxToVtkCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv) 
+{
+    double *points;
+    Tcl_Obj *objPtr, *pointsObjPtr, *fieldObjPtr, *cellsObjPtr;
+    char *p, *pend;
+    char *string;
+    char mesg[2000];
+    double dv0[3], dv1[3], dv2[3];
+    double dx, dy, dz;
+    double origin[3];
+    int count[3];
+    int length, nAxes, nPoints, nXYPoints, nCells;
+    char *name;
+    int isUniform;
+    int isStructuredGrid;
+    int hasVectors;
+    int i, ix, iy, iz;
+
+    name = "component";
+    points = NULL;
+    nAxes = nPoints = nXYPoints = nCells = 0;
+    dx = dy = dz = 0.0; /* Suppress compiler warning. */
+    origin[0] = origin[1] = origin[2] = 0.0; /* May not have an origin line. */
+    dv0[0] = dv0[1] = dv0[2] = 0.0;
+    dv1[0] = dv1[1] = dv1[2] = 0.0;
+    dv2[0] = dv2[1] = dv2[2] = 0.0;
+    count[0] = count[1] = count[2] = 0; /* Suppress compiler warning. */
+    isUniform = 0;
+    isStructuredGrid = 0;
+    hasVectors = 0;
+
+    if (objc != 2) {
+        Tcl_AppendResult(interp, "wrong # arguments: should be \"",
+                         Tcl_GetString(objv[0]), " string\"", (char *)NULL);
+        return TCL_ERROR;
+    }
+    string = Tcl_GetStringFromObj(objv[1], &length);
+    if (strncmp("<ODX>", string, 5) == 0) {
+        string += 5;
+        length -= 5;
+    }
+    pointsObjPtr = Tcl_NewStringObj("", -1);
+    cellsObjPtr = Tcl_NewStringObj("", -1);
+    fieldObjPtr = Tcl_NewStringObj("", -1);
+    for (p = string, pend = p + length; p < pend; /*empty*/) {
+        char *line;
+        double ddx, ddy, ddz;
+
+        line = GetLine(&p, pend);
+        if (line >= pend) {
+            break;                        /* EOF */
+        }
+        if ((line[0] == '#') || (line[0] == '\n')) {
+            continue;                        /* Skip blank or comment lines. */
+        }
+        if (sscanf(line, "object %*d class gridpositions counts %d %d %d", 
+                   count, count + 1, count + 2) == 3) {
+            isUniform = 1;
+            if ((count[0] < 0) || (count[1] < 0) || (count[2] < 0)) {
+                sprintf(mesg, "invalid grid size: x=%d, y=%d, z=%d", 
+                        count[0], count[1], count[2]);
+                Tcl_AppendResult(interp, mesg, (char *)NULL);
+                return TCL_ERROR;
+            }
+#ifdef notdef
+            fprintf(stderr, "found gridpositions counts %d %d %d\n",
+                    count[0], count[1], count[2]);
+#endif
+        } else if (sscanf(line, "origin %lg %lg %lg", origin, origin + 1, 
+                origin + 2) == 3) {
+            /* Found origin. */
+#ifdef notdef
+            fprintf(stderr, "found origin %g %g %g\n",
+                    origin[0], origin[1], origin[2]);
+#endif
+        } else if (sscanf(line, "delta %lg %lg %lg", &ddx, &ddy, &ddz) == 3) {
+            if (nAxes == 3) {
+                Tcl_AppendResult(interp, "too many delta statements", 
+                        (char *)NULL);
+                return TCL_ERROR;
+            }
+            switch (nAxes) {
+            case 0:
+                dv0[0] = ddx;
+                dv0[1] = ddy;
+                dv0[2] = ddz;
+                break;
+            case 1:
+                dv1[0] = ddx;
+                dv1[1] = ddy;
+                dv1[2] = ddz;
+                break;
+            case 2:
+                dv2[0] = ddx;
+                dv2[1] = ddy;
+                dv2[2] = ddz;
+                break;
+            default:
+                break;
+            }
+
+            if (ddx != 0.0) {
+                if (ddy != 0.0 || ddz != 0.0) {
+                    /* Not axis aligned or skewed grid */
+                    isUniform = 0;
+                    isStructuredGrid = 1;
+                }
+                dx = ddx;
+            } else if (ddy != 0.0) {
+                if (ddx != 0.0 || ddz != 0.0) {
+                    /* Not axis aligned or skewed grid */
+                    isUniform = 0;
+                    isStructuredGrid = 1;
+                }
+                dy = ddy;
+            } else if (ddz != 0.0) {
+                if (ddx != 0.0 || ddy != 0.0) {
+                    /* Not axis aligned or skewed grid */
+                    isUniform = 0;
+                    isStructuredGrid = 1;
+                }
+                dz = ddz;
+            }
+            nAxes++;
+#ifdef notdef
+            fprintf(stderr, "found delta %g %g %g\n", ddx, ddy, ddx);
+#endif
+        } else if (sscanf(line, "object %*d class regulararray count %d", 
+                          &count[2]) == 1) {
+            // Z grid
+            isUniform = 0;
+        } else if (sscanf(line, "object %*d class array type %*s shape 3 rank 1"
+                          " items %d data follows", &nPoints) == 1) {
+#ifdef notdef
+                fprintf(stderr, "found class array type shape 3 nPoints=%d\n",
+                        nPoints);
+#endif
+            if (isUniform) {
+                hasVectors = 1;
+                if (GetUniformFieldVectors(interp, nPoints, count, &p, pend, fieldObjPtr)
+                    != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            } else {
+                // This is a 2D point cloud in xy with a uniform zgrid
+                isUniform = 0;
+                nXYPoints = nPoints;
+                if (nXYPoints < 0) {
+                    sprintf(mesg, "bad # points %d", nXYPoints);
+                    Tcl_AppendResult(interp, mesg, (char *)NULL);
+                    return TCL_ERROR;
+                }
+                points = malloc(sizeof(double) * nXYPoints * 2);
+                if (GetPoints(interp, points, nXYPoints, &p, pend) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            }
+        } else if (sscanf(line, "object %*d class array type %*s rank 1 shape 3"
+                          " items %d data follows", &nPoints) == 1) {
+#ifdef notdef
+                fprintf(stderr, "found class array type shape 3 nPoints=%d\n",
+                        nPoints);
+#endif
+            if (isUniform) {
+                hasVectors = 1;
+                if (GetUniformFieldVectors(interp, nPoints, count, &p, pend, fieldObjPtr)
+                    != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            } else {
+                // This is a 2D point cloud in xy with a uniform zgrid
+                isUniform = 0;
+                nXYPoints = nPoints;
+                if (nXYPoints < 0) {
+                    sprintf(mesg, "bad # points %d", nXYPoints);
+                    Tcl_AppendResult(interp, mesg, (char *)NULL);
+                    return TCL_ERROR;
+                }
+                points = malloc(sizeof(double) * nXYPoints * 2);
+                if (GetPoints(interp, points, nXYPoints, &p, pend) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            }
+        } else if (sscanf(line, "object %*d class array type %*s rank 0"
+                          " %*s %d data follows", &nPoints) == 1) {
+#ifdef notdef
+            fprintf(stderr, "found class array type rank 0 nPoints=%d\n", 
+                nPoints);
+#endif
+            if ((isUniform || isStructuredGrid) &&
+                nPoints != count[0]*count[1]*count[2]) {
+                sprintf(mesg, "inconsistent data: expected %d points"
+                        " but found %d points", count[0]*count[1]*count[2], 
+                        nPoints);
+                Tcl_AppendResult(interp, mesg, (char *)NULL);
+                return TCL_ERROR;
+            } else if (!(isUniform || isStructuredGrid) &&
+                       nPoints != nXYPoints * count[2]) {
+                sprintf(mesg, "inconsistent data: expected %d points"
+                        " but found %d points", nXYPoints * count[2], 
+                        nPoints);
+                Tcl_AppendResult(interp, mesg, (char *)NULL);
+                return TCL_ERROR;
+            }
+            if (isUniform) {
+                if (GetUniformFieldValues(interp, nPoints, count, &p, pend, fieldObjPtr) 
+                    != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            } else if (isStructuredGrid) {
+                if (GetStructuredGridFieldValues(interp, nPoints, &p, pend, fieldObjPtr) 
+                    != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            } else {
+                if (GetCloudFieldValues(interp, nXYPoints, count[2], &p, pend, fieldObjPtr) 
+                    != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            }
+#ifdef notdef
+        } else {
+            fprintf(stderr, "unknown line (%.80s)\n", line);
+#endif
+        }
+    }
+
+    if (isUniform) {
+        objPtr = Tcl_NewStringObj("# vtk DataFile Version 2.0\n", -1);
+        Tcl_AppendToObj(objPtr, "Converted from DX file\n", -1);
+        Tcl_AppendToObj(objPtr, "ASCII\n", -1);
+        Tcl_AppendToObj(objPtr, "DATASET STRUCTURED_POINTS\n", -1);
+        sprintf(mesg, "DIMENSIONS %d %d %d\n", count[0], count[1], count[2]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "ORIGIN %g %g %g\n", origin[0], origin[1], origin[2]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "SPACING %g %g %g\n", dx, dy, dz);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "POINT_DATA %d\n", nPoints);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        if (hasVectors) {
+            sprintf(mesg, "VECTORS %s double\n", name);
+            Tcl_AppendToObj(objPtr, mesg, -1);
+        } else {
+            sprintf(mesg, "SCALARS %s double 1\n", name);
+            Tcl_AppendToObj(objPtr, mesg, -1);
+            sprintf(mesg, "LOOKUP_TABLE default\n");
+            Tcl_AppendToObj(objPtr, mesg, -1);
+        }
+        Tcl_AppendObjToObj(objPtr, fieldObjPtr);
+    } else if (isStructuredGrid) {
+#ifdef notdef
+        fprintf(stderr, "dv0 %g %g %g\n", dv0[0], dv0[1], dv0[2]);
+        fprintf(stderr, "dv1 %g %g %g\n", dv1[0], dv1[1], dv1[2]);
+        fprintf(stderr, "dv2 %g %g %g\n", dv2[0], dv2[1], dv2[2]);
+#endif
+        objPtr = Tcl_NewStringObj("# vtk DataFile Version 2.0\n", -1);
+        Tcl_AppendToObj(objPtr, "Converted from DX file\n", -1);
+        Tcl_AppendToObj(objPtr, "ASCII\n", -1);
+        Tcl_AppendToObj(objPtr, "DATASET STRUCTURED_GRID\n", -1);
+        sprintf(mesg, "DIMENSIONS %d %d %d\n", count[0], count[1], count[2]);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        for (iz = 0; iz < count[2]; iz++) {
+            for (iy = 0; iy < count[1]; iy++) {
+                for (ix = 0; ix < count[0]; ix++) {
+                    double x, y, z;
+                    x = origin[0] + dv2[0] * iz + dv1[0] * iy + dv0[0] * ix;
+                    y = origin[1] + dv2[1] * iz + dv1[1] * iy + dv0[1] * ix;
+                    z = origin[2] + dv2[2] * iz + dv1[2] * iy + dv0[2] * ix;
+                    sprintf(mesg, "%g %g %g\n", x, y, z);
+                    Tcl_AppendToObj(pointsObjPtr, mesg, -1);
+                }
+            }
+        }
+        sprintf(mesg, "POINTS %d double\n", nPoints);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        Tcl_AppendObjToObj(objPtr, pointsObjPtr);
+        sprintf(mesg, "POINT_DATA %d\n", nPoints);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "SCALARS %s double 1\n", name);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "LOOKUP_TABLE default\n");
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        Tcl_AppendObjToObj(objPtr, fieldObjPtr);
+    } else {
+        /* Fill points.  Have to wait to do this since origin, delta can come after
+         * the point list in the file.
+         */
+        for (iz = 0; iz < count[2]; iz++) {
+            for (i = 0; i < nXYPoints; i++) {
+                sprintf(mesg, "%g %g %g\n", points[i*2], points[i*2+1], (origin[2] + dz * iz));
+                Tcl_AppendToObj(pointsObjPtr, mesg, -1);
+            }
+        }
+
+        objPtr = Tcl_NewStringObj("# vtk DataFile Version 2.0\n", -1);
+        Tcl_AppendToObj(objPtr, "Converted from DX file\n", -1);
+        Tcl_AppendToObj(objPtr, "ASCII\n", -1);
+        Tcl_AppendToObj(objPtr, "DATASET UNSTRUCTURED_GRID\n", -1);
+        sprintf(mesg, "POINTS %d double\n", nPoints);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        Tcl_AppendObjToObj(objPtr, pointsObjPtr);
+#ifdef DO_WEDGES
+        {
+            double xmin, xmax, ymin, ymax;
+            xmin = xmax = points[0];
+            ymin = ymax = points[1];
+            for (i = 1; i < nXYPoints; i++) {
+                double x, y;
+                x = points[i*2];
+                y = points[i*2+1];
+                if (x < xmin) xmin = x;
+                if (x > xmax) xmax = x;
+                if (y < ymin) ymin = y;
+                if (y > ymax) ymax = y;
+            }
+
+            char fpts[128];
+            sprintf(fpts, "/tmp/tmppts%d", getpid());
+            char fcells[128];
+            sprintf(fcells, "/tmp/tmpcells%d", getpid());
+
+            FILE *ftmp = fopen(fpts, "w");
+            // save corners of bounding box first, to work around meshing
+            // problems in voronoi utility
+            int numBoundaryPoints = 4;
+
+            // Bump out the bounds by an epsilon to avoid problem when
+            // corner points are already nodes
+            double XEPS = (xmax - xmin) / 10.0f;
+            double YEPS = (ymax - ymin) / 10.0f;
+
+            fprintf(ftmp, "%g %g\n", xmin - XEPS, ymin - YEPS);
+            fprintf(ftmp, "%g %g\n", xmax + XEPS, ymin - YEPS);
+            fprintf(ftmp, "%g %g\n", xmax + XEPS, ymax + YEPS);
+            fprintf(ftmp, "%g %g\n", xmin - XEPS, ymax + YEPS);
+
+            for (i = 0; i < nXYPoints; i++) {
+                fprintf(ftmp, "%g %g\n", points[i*2], points[i*2+1]);
+            }
+            fclose(ftmp);
+#ifdef CHECK_WINDINGS
+            double normal[3];
+            normal[0] = normal[1] = 0.0;
+            if (dx >= 0) {
+                normal[2] = 1.0;
+            } else {
+                normal[2] = -1.0;
+            }
+#endif
+            char cmdstr[512];
+            sprintf(cmdstr, "voronoi -t < %s > %s", fpts, fcells);
+            if (system(cmdstr) == 0) {
+                int c0, c1, c2;
+                ftmp = fopen(fcells, "r");
+                while (!feof(ftmp)) {
+                    if (fscanf(ftmp, "%d %d %d", &c0, &c1, &c2) == 3) {
+                        c0 -= numBoundaryPoints;
+                        c1 -= numBoundaryPoints;
+                        c2 -= numBoundaryPoints;
+                        // skip boundary points we added
+                        if (c0 >= 0 && c1 >= 0 && c2 >= 0) {
+#ifdef CHECK_WINDINGS
+                            /* Winding of base triangle should point to 
+                               outside of cell using right hand rule */
+                            double v1[3], v2[3], c[3];
+                            v1[0] = points[c1*2] - points[c0*2];
+                            v1[1] = points[c1*2+1] - points[c0*2+1];
+                            v1[2] = 0;
+                            v2[0] = points[c2*2] - points[c0*2];
+                            v2[1] = points[c2*2+1] - points[c0*2+1];
+                            v2[2] = 0;
+                            Cross(v1, v2, c);
+                            if (Dot(normal, c) > 0) {
+                                int tmp = c0;
+                                c0 = c2;
+                                c2 = tmp;
+                            }
+#endif
+                            for (iz = 0; iz < count[2] - 1; iz++) {
+                                int base = nXYPoints * iz;
+                                int top = base + nXYPoints;
+                                nCells++;
+                                sprintf(mesg, "%d %d %d %d %d %d %d\n", 6,
+                                        base + c0, base + c1, base + c2,
+                                        top + c0, top + c1, top + c2);
+                                Tcl_AppendToObj(cellsObjPtr, mesg, -1);
+                            }
+                        }
+                    } else {
+                        break;
+                    }
+                }
+                fclose(ftmp);
+             } else {
+                fprintf(stderr, "voronoi mesher failed");
+            }
+            unlink(fpts);
+            unlink(fcells);
+        }
+        sprintf(mesg, "CELLS %d %d\n", nCells, 7*nCells);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        Tcl_AppendObjToObj(objPtr, cellsObjPtr);
+        sprintf(mesg, "CELL_TYPES %d\n", nCells);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "%d\n", 13);
+        for (i = 0; i < nCells; i++) {
+            Tcl_AppendToObj(objPtr, mesg, -1);
+        }
+#endif
+        if (points != NULL) {
+            free(points);
+        }
+        sprintf(mesg, "POINT_DATA %d\n", nPoints);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "SCALARS %s double 1\n", name);
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        sprintf(mesg, "LOOKUP_TABLE default\n");
+        Tcl_AppendToObj(objPtr, mesg, -1);
+        Tcl_AppendObjToObj(objPtr, fieldObjPtr);
+    }
+
+    Tcl_DecrRefCount(pointsObjPtr);
+    Tcl_DecrRefCount(cellsObjPtr);
+    Tcl_DecrRefCount(fieldObjPtr);
+    Tcl_SetObjResult(interp, objPtr);
+    return TCL_OK;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpDxToVtk_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "DxToVtk" command into the interpreter.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpDxToVtk_Init(Tcl_Interp *interp)
+{
+    /* install the widget command */
+    Tcl_CreateObjCommand(interp, "Rappture::DxToVtk", DxToVtkCmd,
+        NULL, NULL);
+    return TCL_OK;
+}
Index: /branches/r9/packages/Listbox/RpListbox.c
===================================================================
--- /branches/r9/packages/Listbox/RpListbox.c	(revision 4841)
+++ /branches/r9/packages/Listbox/RpListbox.c	(revision 4841)
@@ -0,0 +1,3811 @@
+/* 
+ * ----------------------------------------------------------------------
+ *  RpListbox - listbox with a few extra features
+ *
+ *  Upgrade of the usual Tk listbox, to handle special icons and indents
+ *  for each item.  This makes it more useful as a file selection box.
+ *
+ *  Rappture::listbox .lb
+ *  .lb insert end "text" -image icon -indent num -data xyz
+ *
+ * ======================================================================
+ *  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.
+ * ======================================================================
+ *  Based on the original Tk listbox widget, which is licensed as
+ *  follows...
+ *
+ * tkListbox.c --
+ *
+ *	This module implements listbox widgets for the Tk
+ *	toolkit.  A listbox displays a collection of strings,
+ *	one per line, and provides scrolling and selection.
+ *
+ * Copyright (c) 1990-1994 The Regents of the University of California.
+ * Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * RCS: @(#) $Id: tkListbox.c,v 1.29.2.5 2007/04/29 02:24:02 das Exp $
+ */
+#include "tkPort.h"
+#include "default.h"
+#include "tkInt.h"
+
+#ifdef WIN32
+#include "tkWinInt.h"
+#endif
+
+typedef struct {
+    Tk_OptionTable listboxOptionTable;	/* Table defining configuration options
+					 * available for the listbox */
+    Tk_OptionTable itemAttrOptionTable;	/* Table definining configuration
+					 * options available for listbox
+					 * items */
+} ListboxOptionTables;
+
+/*
+ * A data structure of the following type is kept for each listbox
+ * widget managed by this file:
+ */
+
+typedef struct {
+    Tk_Window tkwin;		/* Window that embodies the listbox.  NULL
+				 * means that the window has been destroyed
+				 * but the data structures haven't yet been
+				 * cleaned up.*/
+    Display *display;		/* Display containing widget.  Used, among
+				 * other things, so that resources can be
+				 * freed even after tkwin has gone away. */
+    Tcl_Interp *interp;		/* Interpreter associated with listbox. */
+    Tcl_Command widgetCmd;	/* Token for listbox's widget command. */
+    Tk_OptionTable optionTable;	/* Table that defines configuration options
+				 * available for this widget. */
+    Tk_OptionTable itemAttrOptionTable;	/* Table that defines configuration
+					 * options available for listbox
+					 * items */
+    Tcl_Obj *listObj;           /* Pointer to the list object being used */
+    int nElements;              /* Holds the current count of elements */
+    Tcl_HashTable *selection;   /* Tracks selection */
+    Tcl_HashTable *itemAttrTable;	/* Tracks item attributes */
+
+    /*
+     * Information used when displaying widget:
+     */
+
+    Tk_3DBorder normalBorder;	/* Used for drawing border around whole
+				 * window, plus used for background. */
+    int borderWidth;		/* Width of 3-D border around window. */
+    int relief;			/* 3-D effect: TK_RELIEF_RAISED, etc. */
+    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. */
+    Tk_Font tkfont;		/* Information about text font, or NULL. */
+    XColor *fgColorPtr;		/* Text color in normal mode. */
+    XColor *dfgColorPtr;	/* Text color in disabled mode. */
+    GC textGC;			/* For drawing normal text. */
+    Tk_3DBorder selBorder;	/* Borders and backgrounds for selected
+				 * elements. */
+    int selBorderWidth;		/* Width of border around selection. */
+    XColor *selFgColorPtr;	/* Foreground color for selected elements. */
+    GC selTextGC;		/* For drawing selected text. */
+    int width;			/* Desired width of window, in characters. */
+    int height;			/* Desired height of window, in lines. */
+    int lineHeight;		/* Number of pixels allocated for each line
+				 * in display. */
+    int topIndex;		/* Index of top-most element visible in
+				 * window. */
+    int setGrid;		/* Non-zero means pass gridding information
+				 * to window manager. */
+
+    int orient;			/* Orientation: horizontal (multi columns)
+				 * or vertical (single vertical column) */
+    int elemsPerColumn;         /* number of elements in each column
+                                 * (in horizontal orientation) */
+    int numColumns;             /* number of horizontal columns */
+    int *xColumnMax;            /* x-coord for right edge of each column */
+    int xColumnSpace[10];       /* built-in space for xColumnMax */
+
+    /*
+     * Information to support horizontal scrolling:
+     */
+
+    int maxWidth;		/* Width (in pixels) of widest string in
+				 * listbox. */
+    int imageWidth;		/* Width (in pixels) of widest icon in
+				 * listbox. */
+    int imageHeight;		/* Height (in pixels) of tallest icon in
+				 * listbox. */
+    int xScrollUnit;		/* Number of pixels in one "unit" for
+				 * horizontal scrolling (window scrolls
+				 * horizontally in increments of this size).
+				 * This is an average character size. */
+    int xOffset;		/* The left edge of each string in the
+				 * listbox is offset to the left by this
+				 * many pixels (0 means no offset, positive
+				 * means there is an offset). */
+    int yOffset;		/* The top edge of all elements in the
+				 * listbox is offset to the bottom by this
+				 * many pixels (0 means no offset, positive
+				 * means there is an offset). */
+
+    /*
+     * Information about what's selected or active, if any.
+     */
+
+    Tk_Uid selectMode;		/* Selection style: single, browse, multiple,
+				 * or extended.  This value isn't used in C
+				 * code, but the Tcl bindings use it. */
+    int numSelected;		/* Number of elements currently selected. */
+    int selectAnchor;		/* Fixed end of selection (i.e. element
+				 * at which selection was started.) */
+    int exportSelection;	/* Non-zero means tie internal listbox
+				 * to X selection. */
+    int active;			/* Index of "active" element (the one that
+				 * has been selected by keyboard traversal).
+				 * -1 means none. */
+    int activeStyle;		/* style in which to draw the active element.
+				 * One of: underline, none, dotbox */
+
+    /*
+     * Information for scanning:
+     */
+
+    int scanMarkX;		/* X-position at which scan started (e.g.
+				 * button was pressed here). */
+    int scanMarkY;		/* Y-position at which scan started (e.g.
+				 * button was pressed here). */
+    int scanMarkXOffset;	/* Value of "xOffset" field when scan
+				 * started. */
+    int scanMarkYOffset;	/* Value of "yOffset" field when scan
+				 * started. */
+
+    /*
+     * 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. */
+    char *yScrollCmd;		/* Command prefix for communicating with
+				 * vertical scrollbar.  NULL means no command
+				 * to issue.  Malloc'ed. */
+    char *xScrollCmd;		/* Command prefix for communicating with
+				 * horizontal scrollbar.  NULL means no command
+				 * to issue.  Malloc'ed. */
+    int state;			/* Listbox state. */
+    Pixmap gray;		/* Pixmap for displaying disabled text. */
+    int flags;			/* Various flag bits:  see below for
+				 * definitions. */
+} Listbox;
+
+/*
+ * ItemAttr structures are used to store item configuration information for
+ * the items in a listbox
+ */
+typedef struct {
+    Tk_3DBorder border;		/* Used for drawing background around text */
+    Tk_3DBorder selBorder;	/* Used for selected text */
+    XColor *fgColor;		/* Text color in normal mode. */
+    XColor *selFgColor;		/* Text color in selected mode. */
+    char *data;      		/* Extra data string for this item */
+    char *imagePtr;		/* Value of -image option (icon for item) */
+    Tk_Image image;    		/* Derived from imagePtr for actual image */
+    int indent;      		/* Indent item by this many pixels */
+} ItemAttr;    
+
+/*
+ * Flag bits for listboxes:
+ *
+ * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
+ *				has already been queued to redraw
+ *				this window.
+ * UPDATE_V_SCROLLBAR:		Non-zero means vertical scrollbar needs
+ *				to be updated.
+ * UPDATE_H_SCROLLBAR:		Non-zero means horizontal scrollbar needs
+ *				to be updated.
+ * GOT_FOCUS:			Non-zero means this widget currently
+ *				has the input focus.
+ * GEOMETRY_IS_STALE:           Stored maxWidth/imageWidth/imageHeight
+ * 				and lineHeight may be out-of-date
+ * LISTBOX_DELETED:		This listbox has been effectively destroyed.
+ */
+
+#define REDRAW_PENDING		1
+#define UPDATE_V_SCROLLBAR	2
+#define UPDATE_H_SCROLLBAR	4
+#define GOT_FOCUS		8
+#define GEOMETRY_IS_STALE	16
+#define LISTBOX_DELETED		32
+
+/*
+ * The following enum is used to define a type for the -state option
+ * of the Entry widget.  These values are used as indices into the 
+ * string table below.
+ */
+
+enum state {
+    STATE_DISABLED, STATE_NORMAL
+};
+
+static char *stateStrings[] = {
+    "disabled", "normal", (char *) NULL
+};
+
+enum activeStyle {
+    ACTIVE_STYLE_DOTBOX, ACTIVE_STYLE_NONE, ACTIVE_STYLE_UNDERLINE
+};
+
+static char *activeStyleStrings[] = {
+    "dotbox", "none", "underline", (char *) NULL
+};
+
+enum orientation {
+    ORIENT_VERTICAL, ORIENT_HORIZONTAL
+};
+
+static char *orientStrings[] = {
+    "vertical", "horizontal", (char*)NULL
+};
+
+
+/*
+ * The optionSpecs table defines the valid configuration options for the
+ * listbox widget
+ */
+static Tk_OptionSpec optionSpecs[] = {
+    {TK_OPTION_STRING_TABLE, "-activestyle", "activeStyle", "ActiveStyle",
+	DEF_LISTBOX_ACTIVE_STYLE, -1, Tk_Offset(Listbox, activeStyle),
+        0, (ClientData) activeStyleStrings, 0},
+    {TK_OPTION_BORDER, "-background", "background", "Background",
+	 DEF_LISTBOX_BG_COLOR, -1, Tk_Offset(Listbox, normalBorder),
+	 0, (ClientData) DEF_LISTBOX_BG_MONO, 0},
+    {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL,
+	 (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
+    {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
+	 (char *) NULL, 0, -1, 0, (ClientData) "-background", 0},
+    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
+	 DEF_LISTBOX_BORDER_WIDTH, -1, Tk_Offset(Listbox, borderWidth),
+	 0, 0, 0},
+    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
+	 DEF_LISTBOX_CURSOR, -1, Tk_Offset(Listbox, cursor),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
+	 "DisabledForeground", DEF_LISTBOX_DISABLED_FG, -1,
+	 Tk_Offset(Listbox, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
+	 "ExportSelection", DEF_LISTBOX_EXPORT_SELECTION, -1,
+	 Tk_Offset(Listbox, exportSelection), 0, 0, 0},
+    {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL,
+	 (char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0},
+    {TK_OPTION_FONT, "-font", "font", "Font",
+	 DEF_LISTBOX_FONT, -1, Tk_Offset(Listbox, tkfont), 0, 0, 0},
+    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
+	 DEF_LISTBOX_FG, -1, Tk_Offset(Listbox, fgColorPtr), 0, 0, 0},
+    {TK_OPTION_INT, "-height", "height", "Height",
+	 DEF_LISTBOX_HEIGHT, -1, Tk_Offset(Listbox, height), 0, 0, 0},
+    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
+	 "HighlightBackground", DEF_LISTBOX_HIGHLIGHT_BG, -1, 
+	 Tk_Offset(Listbox, highlightBgColorPtr), 0, 0, 0},
+    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
+	 DEF_LISTBOX_HIGHLIGHT, -1, Tk_Offset(Listbox, highlightColorPtr),
+	 0, 0, 0},
+    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
+	 "HighlightThickness", DEF_LISTBOX_HIGHLIGHT_WIDTH, -1,
+	 Tk_Offset(Listbox, highlightWidth), 0, 0, 0},
+    {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical", -1,
+	 Tk_Offset(Listbox, orient), 0, (ClientData)orientStrings, 0},
+    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
+	 DEF_LISTBOX_RELIEF, -1, Tk_Offset(Listbox, relief), 0, 0, 0},
+    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
+	 DEF_LISTBOX_SELECT_COLOR, -1, Tk_Offset(Listbox, selBorder),
+	 0, (ClientData) DEF_LISTBOX_SELECT_MONO, 0},
+    {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth",
+	 "BorderWidth", DEF_LISTBOX_SELECT_BD, -1,
+	 Tk_Offset(Listbox, selBorderWidth), 0, 0, 0},
+    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
+	 DEF_LISTBOX_SELECT_FG_COLOR, -1, Tk_Offset(Listbox, selFgColorPtr),
+	 TK_CONFIG_NULL_OK, (ClientData) DEF_LISTBOX_SELECT_FG_MONO, 0},
+    {TK_OPTION_STRING, "-selectmode", "selectMode", "SelectMode",
+	 DEF_LISTBOX_SELECT_MODE, -1, Tk_Offset(Listbox, selectMode),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
+	 DEF_LISTBOX_SET_GRID, -1, Tk_Offset(Listbox, setGrid), 0, 0, 0},
+    {TK_OPTION_STRING_TABLE, "-state", "state", "State",
+	DEF_LISTBOX_STATE, -1, Tk_Offset(Listbox, state), 
+        0, (ClientData) stateStrings, 0},
+    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
+	 DEF_LISTBOX_TAKE_FOCUS, -1, Tk_Offset(Listbox, takeFocus),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_INT, "-width", "width", "Width",
+	 DEF_LISTBOX_WIDTH, -1, Tk_Offset(Listbox, width), 0, 0, 0},
+    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
+	 DEF_LISTBOX_SCROLL_COMMAND, -1, Tk_Offset(Listbox, xScrollCmd),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
+	 DEF_LISTBOX_SCROLL_COMMAND, -1, Tk_Offset(Listbox, yScrollCmd),
+	 TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
+	 (char *) NULL, 0, -1, 0, 0, 0}
+};
+
+/*
+ * The itemAttrOptionSpecs table defines the valid configuration options for
+ * listbox items
+ */
+static Tk_OptionSpec itemAttrOptionSpecs[] = {
+    {TK_OPTION_BORDER, "-background", "background", "Background",
+     (char *)NULL, -1, Tk_Offset(ItemAttr, border),
+     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT,
+     (ClientData) DEF_LISTBOX_BG_MONO, 0},
+    {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
+     (char *) NULL, 0, -1, 0, (ClientData) "-background", 0},
+    {TK_OPTION_STRING, "-data", "data", "Data",
+     (char *) NULL, -1, Tk_Offset(ItemAttr, data),
+     TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL,
+     (char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0},
+    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
+     (char *) NULL, -1, Tk_Offset(ItemAttr, fgColor),
+     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+    {TK_OPTION_STRING, "-image", "image", "Image",
+     (char *) NULL, -1, Tk_Offset(ItemAttr, imagePtr),
+     TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_INT, "-indent", "indent", "Indent",
+      "0", -1, Tk_Offset(ItemAttr, indent), 0, 0, 0},
+    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
+     (char *) NULL, -1, Tk_Offset(ItemAttr, selBorder),
+     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT,
+     (ClientData) DEF_LISTBOX_SELECT_MONO, 0},
+    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
+     (char *) NULL, -1, Tk_Offset(ItemAttr, selFgColor),
+     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT,
+     (ClientData) DEF_LISTBOX_SELECT_FG_MONO, 0},
+    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
+     (char *) NULL, 0, -1, 0, 0, 0}
+};
+
+/*
+ * The following tables define the listbox widget commands (and sub-
+ * commands) and map the indexes into the string tables into 
+ * enumerated types used to dispatch the listbox widget command.
+ */
+static CONST char *commandNames[] = {
+    "activate", "bbox", "cget", "configure", "curselection", "delete", "get",
+    "index", "insert", "itemcget", "itemconfigure", "nearest", "scan",
+    "see", "selection", "size", "xview", "yview",
+    (char *) NULL
+};
+
+enum command {
+    COMMAND_ACTIVATE, COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE,
+    COMMAND_CURSELECTION, COMMAND_DELETE, COMMAND_GET, COMMAND_INDEX,
+    COMMAND_INSERT, COMMAND_ITEMCGET, COMMAND_ITEMCONFIGURE,
+    COMMAND_NEAREST, COMMAND_SCAN, COMMAND_SEE, COMMAND_SELECTION,
+    COMMAND_SIZE, COMMAND_XVIEW, COMMAND_YVIEW
+};
+
+static CONST char *selCommandNames[] = {
+    "anchor", "clear", "includes", "set", (char *) NULL
+};
+
+enum selcommand {
+    SELECTION_ANCHOR, SELECTION_CLEAR, SELECTION_INCLUDES, SELECTION_SET
+};
+
+static CONST char *scanCommandNames[] = {
+    "mark", "dragto", (char *) NULL
+};
+
+enum scancommand {
+    SCAN_MARK, SCAN_DRAGTO
+};
+
+static CONST char *indexNames[] = {
+    "active", "anchor", "end", (char *)NULL
+};
+
+enum indices {
+    INDEX_ACTIVE, INDEX_ANCHOR, INDEX_END
+};
+
+static CONST char *bboxOptionNames[] = {
+    "-all", "-icon", "-text", (char *) NULL
+};
+
+enum bboxoption {
+    BBOX_ALL, BBOX_ICON, BBOX_TEXT
+};
+
+
+/* Declarations for procedures defined later in this file */
+static void		RpChangeListboxXOffset _ANSI_ARGS_((Listbox *listPtr,
+			    int offset));
+static void		RpChangeListboxYOffset _ANSI_ARGS_((Listbox *listPtr,
+			    int offset));
+static void		RpChangeListboxView _ANSI_ARGS_((Listbox *listPtr,
+			    int index));
+static int		RpConfigureListbox _ANSI_ARGS_((Tcl_Interp *interp,
+			    Listbox *listPtr, int objc, Tcl_Obj *CONST objv[],
+			    int flags));
+static int		RpConfigureListboxItem _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Listbox *listPtr, ItemAttr *attrs, int objc,
+			    Tcl_Obj *CONST objv[], int index));
+static int		RpListboxDeleteSubCmd _ANSI_ARGS_((Listbox *listPtr,
+			    int first, int last));
+static void		RpDestroyListbox _ANSI_ARGS_((char *memPtr));
+static void		RpDestroyListboxOptionTables _ANSI_ARGS_ (
+			    (ClientData clientData, Tcl_Interp *interp));
+static void		RpDisplayListbox _ANSI_ARGS_((ClientData clientData));
+static void		RpDisplayImage _ANSI_ARGS_((Pixmap pixmap,
+			    int w, int h, Tk_Image image, int x, int y));
+static void		RpListboxImageProc _ANSI_ARGS_((ClientData clientData,
+			    int x, int y, int width, int height,
+			    int imgWidth, int imgHeight));
+static int		RpGetListboxIndex _ANSI_ARGS_((Tcl_Interp *interp,
+			    Listbox *listPtr, Tcl_Obj *index, int endIsSize,
+			    int *indexPtr));
+static void		RpGetListboxPos _ANSI_ARGS_((Listbox *listPtr,
+			    int index, int *rowPtr, int *colPtr));
+static int		RpListboxInsertSubCmd _ANSI_ARGS_((Listbox *listPtr,
+			    int index, int objc, Tcl_Obj *CONST objv[]));
+static void		RpListboxCmdDeletedProc _ANSI_ARGS_((
+			    ClientData clientData));
+static void		RpListboxComputeGeometry _ANSI_ARGS_((Listbox *listPtr,
+			    int fontChanged, int maxIsStale, int updateGrid));
+static void		RpListboxEventProc _ANSI_ARGS_((ClientData clientData,
+			    XEvent *eventPtr));
+static int		RpListboxFetchSelection _ANSI_ARGS_((
+			    ClientData clientData, int offset, char *buffer,
+			    int maxBytes));
+static void		RpListboxLostSelection _ANSI_ARGS_((
+			    ClientData clientData));
+static void		RpEventuallyRedrawRange _ANSI_ARGS_((Listbox *listPtr,
+			    int first, int last));
+static void		RpListboxScanTo _ANSI_ARGS_((Listbox *listPtr,
+			    int x, int y));
+static int		RpListboxSelect _ANSI_ARGS_((Listbox *listPtr,
+			    int first, int last, int select));
+static void		RpListboxUpdateHScrollbar _ANSI_ARGS_(
+    			    (Listbox *listPtr));
+static void		RpListboxUpdateVScrollbar _ANSI_ARGS_(
+			    (Listbox *listPtr));
+static int		RpListboxWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
+	                    Tcl_Interp *interp, int objc,
+	                    Tcl_Obj *CONST objv[]));
+static int		RpListboxBboxSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+	                    Listbox *listPtr, int index, int what));
+static int		RpListboxSelectionSubCmd _ANSI_ARGS_ (
+			    (Tcl_Interp *interp, Listbox *listPtr, int objc,
+			    Tcl_Obj *CONST objv[]));
+static int		RpListboxXviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Listbox *listPtr, int objc,
+			    Tcl_Obj *CONST objv[]));
+static int		RpListboxYviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
+			    Listbox *listPtr, int objc,
+			    Tcl_Obj *CONST objv[]));
+static ItemAttr *	RpListboxGetItemAttributes _ANSI_ARGS_ (
+    			    (Tcl_Interp *interp, Listbox *listPtr, int index));
+static void		RpListboxWorldChanged _ANSI_ARGS_((
+			    ClientData instanceData));
+static int		RpNearestListboxElement _ANSI_ARGS_((Listbox *listPtr,
+			    int x, int y));
+static void		RpMigrateHashEntries _ANSI_ARGS_ ((Tcl_HashTable *table,
+			    int first, int last, int offset));
+/*
+ * The structure below defines button class behavior by means of procedures
+ * that can be invoked from generic window code.
+ */
+
+static Tk_ClassProcs listboxClass = {
+    sizeof(Tk_ClassProcs),	/* size */
+    RpListboxWorldChanged,	/* worldChangedProc */
+};
+
+extern Tcl_ObjCmdProc RpListboxObjCmd;
+
+
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpListbox_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the Rappture "listbox" widget.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpListbox_Init(interp)
+    Tcl_Interp *interp;         /* interpreter being initialized */
+{
+    Tcl_CreateObjCommand(interp, "Rappture::listbox", RpListboxObjCmd,
+        (ClientData)NULL, NULL);
+
+    return TCL_OK;
+}
+
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RpListboxObjCmd --
+ *
+ *	This procedure is invoked to process the "listbox" Tcl
+ *	command.  See the user documentation for details on what
+ *	it does.
+ *
+ * Results:
+ *	A standard Tcl result.
+ *
+ * Side effects:
+ *	See the user documentation.
+ *
+ *--------------------------------------------------------------
+ */
+
+int
+RpListboxObjCmd(clientData, interp, objc, objv)
+    ClientData clientData;	/* NULL. */
+    Tcl_Interp *interp;		/* Current interpreter. */
+    int objc;			/* Number of arguments. */
+    Tcl_Obj *CONST objv[];	/* Argument objects. */
+{
+    register Listbox *listPtr;
+    Tk_Window tkwin;
+    ListboxOptionTables *optionTables;
+
+    if (objc < 2) {
+	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
+	return TCL_ERROR;
+    }
+
+    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
+	    Tcl_GetString(objv[1]), (char *) NULL);
+    if (tkwin == NULL) {
+	return TCL_ERROR;
+    }
+
+    optionTables = (ListboxOptionTables *)
+	Tcl_GetAssocData(interp, "RpListboxOptionTables", NULL);
+    if (optionTables == NULL) {
+	/*
+	 * We haven't created the option tables for this widget class yet.
+	 * Do it now and save the a pointer to them as the ClientData for
+	 * the command, so future invocations will have access to it.
+	 */
+
+	optionTables = (ListboxOptionTables *)
+	    ckalloc(sizeof(ListboxOptionTables));
+	/* Set up an exit handler to free the optionTables struct */
+	Tcl_SetAssocData(interp, "RpListboxOptionTables",
+		RpDestroyListboxOptionTables, (ClientData) optionTables);
+
+	/* Create the listbox option table and the listbox item option table */
+	optionTables->listboxOptionTable =
+	    Tk_CreateOptionTable(interp, optionSpecs);
+	optionTables->itemAttrOptionTable =
+	    Tk_CreateOptionTable(interp, itemAttrOptionSpecs);
+    }
+
+    /*
+     * Initialize the fields of the structure that won't be initialized
+     * by RpConfigureListbox, or that RpConfigureListbox requires to be
+     * initialized already (e.g. resource pointers).
+     */
+    listPtr 				= (Listbox *) ckalloc(sizeof(Listbox));
+    memset((void *) listPtr, 0, (sizeof(Listbox)));
+
+    listPtr->tkwin 			= tkwin;
+    listPtr->display 			= Tk_Display(tkwin);
+    listPtr->interp 			= interp;
+    listPtr->widgetCmd 			= Tcl_CreateObjCommand(interp,
+	    Tk_PathName(listPtr->tkwin), RpListboxWidgetObjCmd,
+	    (ClientData) listPtr, RpListboxCmdDeletedProc);
+    listPtr->optionTable 		= optionTables->listboxOptionTable;
+    listPtr->itemAttrOptionTable	= optionTables->itemAttrOptionTable;
+    listPtr->selection 			=
+	(Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
+    Tcl_InitHashTable(listPtr->selection, TCL_ONE_WORD_KEYS);
+    listPtr->itemAttrTable 		=
+	(Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable));
+    Tcl_InitHashTable(listPtr->itemAttrTable, TCL_ONE_WORD_KEYS);
+    listPtr->relief 			= TK_RELIEF_RAISED;
+    listPtr->textGC 			= None;
+    listPtr->selFgColorPtr 		= None;
+    listPtr->selTextGC 			= None;
+    listPtr->xScrollUnit 		= 1;
+    listPtr->exportSelection 		= 1;
+    listPtr->cursor 			= None;
+    listPtr->state			= STATE_NORMAL;
+    listPtr->gray			= None;
+    listPtr->orient			= ORIENT_VERTICAL;
+
+    /*
+     * Keep a hold of the associated tkwin until we destroy the listbox,
+     * otherwise Tk might free it while we still need it.
+     */
+
+    Tcl_Preserve((ClientData) listPtr->tkwin);
+
+    Tk_SetClass(listPtr->tkwin, "Listbox");
+    Tk_SetClassProcs(listPtr->tkwin, &listboxClass, (ClientData) listPtr);
+    Tk_CreateEventHandler(listPtr->tkwin,
+	    ExposureMask|StructureNotifyMask|FocusChangeMask,
+	    RpListboxEventProc, (ClientData) listPtr);
+    Tk_CreateSelHandler(listPtr->tkwin, XA_PRIMARY, XA_STRING,
+	    RpListboxFetchSelection, (ClientData) listPtr, XA_STRING);
+    if (Tk_InitOptions(interp, (char *)listPtr,
+	    optionTables->listboxOptionTable, tkwin) != TCL_OK) {
+	Tk_DestroyWindow(listPtr->tkwin);
+	return TCL_ERROR;
+    }
+
+    if (RpConfigureListbox(interp, listPtr, objc-2, objv+2, 0) != TCL_OK) {
+	Tk_DestroyWindow(listPtr->tkwin);
+	return TCL_ERROR;
+    }
+
+    Tcl_SetResult(interp, Tk_PathName(listPtr->tkwin), TCL_STATIC);
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxWidgetObjCmd --
+ *
+ *	This Tcl_Obj based procedure is invoked to process the Tcl command
+ *      that corresponds to a widget managed by this module.  See the user
+ *      documentation for details on what it does.
+ *
+ * Results:
+ *	A standard Tcl result.
+ *
+ * Side effects:
+ *	See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxWidgetObjCmd(clientData, interp, objc, objv)
+    ClientData clientData;		/* Information about listbox widget. */
+    Tcl_Interp *interp;			/* Current interpreter. */
+    int objc;				/* Number of arguments. */
+    Tcl_Obj *CONST objv[];		/* Arguments as Tcl_Obj's. */
+{
+    register Listbox *listPtr = (Listbox *) clientData;
+    int cmdIndex, index;
+    int result = TCL_OK;
+    
+    if (objc < 2) {
+	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
+	return TCL_ERROR;
+    }
+
+    /*
+     * Parse the command by looking up the second argument in the list
+     * of valid subcommand names
+     */
+    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
+	    "option", 0, &cmdIndex);
+    if (result != TCL_OK) {
+	return result;
+    }
+
+    Tcl_Preserve((ClientData)listPtr);
+    /* The subcommand was valid, so continue processing */
+    switch (cmdIndex) {
+	case COMMAND_ACTIVATE: {
+	    if (objc != 3) {
+		Tcl_WrongNumArgs(interp, 2, objv, "index");
+		result = TCL_ERROR;
+		break;
+	    }
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+
+	    if (!(listPtr->state & STATE_NORMAL)) {
+		break;
+	    }
+
+	    if (index >= listPtr->nElements) {
+		index = listPtr->nElements-1;
+	    }
+	    if (index < 0) {
+		index = 0;
+	    }
+	    listPtr->active = index;
+	    RpEventuallyRedrawRange(listPtr, listPtr->active, listPtr->active);
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_BBOX: {
+            int bboxOpt = BBOX_ALL;
+	    if (objc < 3 || objc > 4) {
+		Tcl_WrongNumArgs(interp, 2, objv, "index ?what?");
+		result = TCL_ERROR;
+		break;
+	    }
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+
+            if (objc > 3) {
+	        result = Tcl_GetIndexFromObj(interp, objv[3], bboxOptionNames,
+		        "what", 0, &bboxOpt);
+	        if (result != TCL_OK) {
+		    break;
+	        }
+            }
+	    result = RpListboxBboxSubCmd(interp, listPtr, index, bboxOpt);
+	    break;
+	}
+
+	case COMMAND_CGET: {
+	    Tcl_Obj *objPtr;
+	    if (objc != 3) {
+		Tcl_WrongNumArgs(interp, 2, objv, "option");
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    objPtr = Tk_GetOptionValue(interp, (char *)listPtr,
+		    listPtr->optionTable, objv[2], listPtr->tkwin);
+	    if (objPtr == NULL) {
+		result = TCL_ERROR;
+		break;
+	    }
+	    Tcl_SetObjResult(interp, objPtr);
+	    result = TCL_OK;
+	    break;
+	}
+	
+	case COMMAND_CONFIGURE: {
+	    Tcl_Obj *objPtr;
+	    if (objc <= 3) {
+		objPtr = Tk_GetOptionInfo(interp, (char *) listPtr,
+			listPtr->optionTable,
+			(objc == 3) ? objv[2] : (Tcl_Obj *) NULL,
+			listPtr->tkwin);
+		if (objPtr == NULL) {
+		    result = TCL_ERROR;
+		    break;
+		} else {
+		    Tcl_SetObjResult(interp, objPtr);
+		    result = TCL_OK;
+		}
+	    } else {
+		result = RpConfigureListbox(interp, listPtr, objc-2, objv+2, 0);
+	    }
+	    break;
+	}
+
+	case COMMAND_CURSELECTION: {
+	    char indexStringRep[TCL_INTEGER_SPACE];
+	    int i;
+	    if (objc != 2) {
+		Tcl_WrongNumArgs(interp, 2, objv, NULL);
+		result = TCL_ERROR;
+		break;
+	    }
+	    /*
+	     * Of course, it would be more efficient to use the Tcl_HashTable
+	     * search functions (Tcl_FirstHashEntry, Tcl_NextHashEntry), but
+	     * then the result wouldn't be in sorted order.  So instead we
+	     * loop through the indices in order, adding them to the result
+	     * if they are selected
+	     */
+	    for (i = 0; i < listPtr->nElements; i++) {
+		if (Tcl_FindHashEntry(listPtr->selection, (char *)i) != NULL) {
+		    sprintf(indexStringRep, "%d", i);
+		    Tcl_AppendElement(interp, indexStringRep);
+		}
+	    }
+	    result = TCL_OK;
+	    break;
+	}
+	
+	case COMMAND_DELETE: {
+	    int first, last;
+	    if ((objc < 3) || (objc > 4)) {
+		Tcl_WrongNumArgs(interp, 2, objv,
+			"firstIndex ?lastIndex?");
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &first);
+	    if (result != TCL_OK) {
+		break;
+	    }
+
+	    if (!(listPtr->state & STATE_NORMAL)) {
+		break;
+	    }
+
+	    if (first < listPtr->nElements) {
+		/*
+		 * if a "last index" was given, get it now; otherwise, use the
+		 * first index as the last index
+		 */
+		if (objc == 4) {
+		    result = RpGetListboxIndex(interp, listPtr,
+			    objv[3], 0, &last);
+		    if (result != TCL_OK) {
+			break;
+		    }
+		} else {
+		    last = first;
+		}
+		if (last >= listPtr->nElements) {
+		    last = listPtr->nElements - 1;
+		}
+		result = RpListboxDeleteSubCmd(listPtr, first, last);
+	    } else {
+		result = TCL_OK;
+	    }
+	    break;
+	}
+
+	case COMMAND_GET: {
+	    int first, last;
+	    Tcl_Obj **elemPtrs;
+	    int listLen;
+	    if (objc != 3 && objc != 4) {
+		Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
+		result = TCL_ERROR;
+		break;
+	    }
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &first);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    last = first;
+	    if (objc == 4) {
+		result = RpGetListboxIndex(interp, listPtr, objv[3], 0, &last);
+		if (result != TCL_OK) {
+		    break;
+		}
+	    }
+	    if (first >= listPtr->nElements) {
+		result = TCL_OK;
+		break;
+	    }
+	    if (last >= listPtr->nElements) {
+		last = listPtr->nElements - 1;
+	    }
+	    if (first < 0) {
+		first = 0;
+	    }
+	    if (first > last) {
+		result = TCL_OK;
+		break;
+	    }
+	    result = Tcl_ListObjGetElements(interp, listPtr->listObj, &listLen,
+		    &elemPtrs);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    if (objc == 3) {
+		/*
+		 * One element request - we return a string
+		 */
+		Tcl_SetObjResult(interp, elemPtrs[first]);
+	    } else {
+		Tcl_SetListObj(Tcl_GetObjResult(interp), (last - first + 1),
+			&(elemPtrs[first]));
+	    }
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_INDEX:{
+	    char buf[TCL_INTEGER_SPACE];
+	    if (objc != 3) {
+		Tcl_WrongNumArgs(interp, 2, objv, "index");
+		result = TCL_ERROR;
+		break;
+	    }
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 1, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    sprintf(buf, "%d", index);
+	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_INSERT: {
+	    if (objc < 3) {
+		Tcl_WrongNumArgs(interp, 2, objv,
+			"index ?element element ...?");
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 1, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+
+	    if (!(listPtr->state & STATE_NORMAL)) {
+		break;
+	    }
+
+	    result = RpListboxInsertSubCmd(listPtr, index, objc-3, objv+3);
+	    break;
+	}
+
+	case COMMAND_ITEMCGET: {
+	    Tcl_Obj *objPtr;
+	    ItemAttr *attrPtr;
+	    if (objc != 4) {
+		Tcl_WrongNumArgs(interp, 2, objv, "index option");
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+
+	    if (index < 0 || index >= listPtr->nElements) {
+		Tcl_AppendResult(interp, "item number \"",
+			Tcl_GetString(objv[2]), "\" out of range",
+			(char *)NULL);
+		result = TCL_ERROR;
+		break;
+	    }
+	    
+	    attrPtr = RpListboxGetItemAttributes(interp, listPtr, index);
+
+	    objPtr = Tk_GetOptionValue(interp, (char *)attrPtr,
+		    listPtr->itemAttrOptionTable, objv[3], listPtr->tkwin);
+	    if (objPtr == NULL) {
+		result = TCL_ERROR;
+		break;
+	    }
+	    Tcl_SetObjResult(interp, objPtr);
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_ITEMCONFIGURE: {
+	    Tcl_Obj *objPtr;
+	    ItemAttr *attrPtr;
+	    if (objc < 3) {
+		Tcl_WrongNumArgs(interp, 2, objv,
+			"index ?option? ?value? ?option value ...?");
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    
+	    if (index < 0 || index >= listPtr->nElements) {
+		Tcl_AppendResult(interp, "item number \"",
+			Tcl_GetString(objv[2]), "\" out of range",
+			(char *)NULL);
+		result = TCL_ERROR;
+		break;
+	    }
+	    
+	    attrPtr = RpListboxGetItemAttributes(interp, listPtr, index);
+	    if (objc <= 4) {
+		objPtr = Tk_GetOptionInfo(interp, (char *)attrPtr,
+			listPtr->itemAttrOptionTable,
+			(objc == 4) ? objv[3] : (Tcl_Obj *) NULL,
+			listPtr->tkwin);
+		if (objPtr == NULL) {
+		    result = TCL_ERROR;
+		    break;
+		} else {
+		    Tcl_SetObjResult(interp, objPtr);
+		    result = TCL_OK;
+		}
+	    } else {
+		result = RpConfigureListboxItem(interp, listPtr, attrPtr,
+			objc-3, objv+3, index);
+	    }
+	    break;
+	}
+	
+	case COMMAND_NEAREST: {
+	    char buf[TCL_INTEGER_SPACE];
+	    int x, y;
+	    if (objc != 4) {
+		Tcl_WrongNumArgs(interp, 2, objv, "x y");
+		result = TCL_ERROR;
+		break;
+	    }
+	    
+	    result = Tcl_GetIntFromObj(interp, objv[2], &x);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    result = Tcl_GetIntFromObj(interp, objv[3], &y);
+	    if (result != TCL_OK) {
+		break;
+	    }
+
+	    index = RpNearestListboxElement(listPtr, x, y);
+	    sprintf(buf, "%d", index);
+	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
+	    result = TCL_OK;
+	    break;
+	}
+	
+	case COMMAND_SCAN: {
+	    int x, y, scanCmdIndex;
+
+	    if (objc != 5) {
+		Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y");
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK
+		    || Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+		result = TCL_ERROR;
+		break;
+	    }
+
+	    result = Tcl_GetIndexFromObj(interp, objv[2], scanCommandNames,
+		    "option", 0, &scanCmdIndex);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    switch (scanCmdIndex) {
+		case SCAN_MARK: {
+		    listPtr->scanMarkX = x;
+		    listPtr->scanMarkY = y;
+		    listPtr->scanMarkXOffset = listPtr->xOffset;
+		    listPtr->scanMarkYOffset = listPtr->yOffset;
+		    break;
+		}
+		case SCAN_DRAGTO: {
+		    RpListboxScanTo(listPtr, x, y);
+		    break;
+		}
+	    }
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_SEE: {
+	    int first, last, nrow, ncol, x0, y0, x1, y1;
+	    if (objc != 3) {
+		Tcl_WrongNumArgs(interp, 2, objv, "index");
+		result = TCL_ERROR;
+		break;
+	    }
+	    result = RpGetListboxIndex(interp, listPtr, objv[2], 0, &index);
+	    if (result != TCL_OK) {
+		break;
+	    }
+	    if (index >= listPtr->nElements) {
+		index = listPtr->nElements - 1;
+	    }
+	    if (index < 0) {
+		index = 0;
+	    }
+
+            /* find the first element that is completely on screen */
+            first = RpNearestListboxElement(listPtr,
+                listPtr->inset, listPtr->inset);
+
+            RpGetListboxPos(listPtr, first, &nrow, &ncol);
+            x0 = (ncol > 0) ? listPtr->xColumnMax[ncol-1] : 0;
+            y0 = nrow*listPtr->lineHeight;
+            if (x0 < listPtr->xOffset) {
+                first += listPtr->elemsPerColumn;
+            }
+            if (y0 < listPtr->yOffset) {
+                first++;
+            }
+
+            last = RpNearestListboxElement(listPtr,
+                Tk_Width(listPtr->tkwin)-listPtr->inset,
+                Tk_Height(listPtr->tkwin)-listPtr->inset);
+
+            RpGetListboxPos(listPtr, last, &nrow, &ncol);
+            x1 = listPtr->xColumnMax[ncol];
+            y1 = (nrow+1)*listPtr->lineHeight;
+            if (x1 > listPtr->xOffset + Tk_Width(listPtr->tkwin)) {
+                last -= listPtr->elemsPerColumn;
+            }
+            if (y1 > listPtr->yOffset + Tk_Height(listPtr->tkwin)) {
+                last--;
+            }
+
+            if (index < first || index > last) {
+                if (listPtr->orient == ORIENT_VERTICAL) {
+                    /* center requested element in view */
+                    if (listPtr->lineHeight > 0) {
+                        index -= Tk_Height(listPtr->tkwin)
+                                  / listPtr->lineHeight / 2;
+                    }
+                }
+		RpChangeListboxView(listPtr, index);
+	    }
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_SELECTION: {
+	    result = RpListboxSelectionSubCmd(interp, listPtr, objc, objv);
+	    break;
+	}
+
+	case COMMAND_SIZE: {
+	    char buf[TCL_INTEGER_SPACE];
+	    if (objc != 2) {
+		Tcl_WrongNumArgs(interp, 2, objv, NULL);
+		result = TCL_ERROR;
+		break;
+	    }
+	    sprintf(buf, "%d", listPtr->nElements);
+	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
+	    result = TCL_OK;
+	    break;
+	}
+
+	case COMMAND_XVIEW: {
+	    result = RpListboxXviewSubCmd(interp, listPtr, objc, objv);
+	    break;
+	}
+	
+	case COMMAND_YVIEW: {
+	    result = RpListboxYviewSubCmd(interp, listPtr, objc, objv);
+	    break;
+	}
+    }
+    Tcl_Release((ClientData)listPtr);
+    return result;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxBboxSubCmd --
+ *
+ *	This procedure is invoked to process a listbox bbox request.
+ *      See the user documentation for more information.
+ *
+ * Results:
+ *	A standard Tcl result.
+ *
+ * Side effects:
+ *	For valid indices, places the bbox of the requested element in
+ *      the interpreter's result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxBboxSubCmd(interp, listPtr, index, what)
+    Tcl_Interp *interp;          /* Pointer to the calling Tcl interpreter */
+    Listbox *listPtr;            /* Information about the listbox */
+    int index;                   /* Index of the element to get bbox info on */
+    int what;                    /* Return bbox around icon, text, or all */
+{
+    int nrow, ncol, x0, y0, width, height;
+    ItemAttr *attrPtr;
+    char buf[TCL_INTEGER_SPACE * 4];
+    Tcl_Obj *el;
+    char *stringRep;
+    int stringLen, result;
+    Tk_FontMetrics fm;
+
+    result = Tcl_ListObjIndex(interp, listPtr->listObj, index, &el);
+    if (result != TCL_OK) {
+        return result;
+    }
+    stringRep = Tcl_GetStringFromObj(el, &stringLen);
+
+    RpGetListboxPos(listPtr, index, &nrow, &ncol);
+    x0 = listPtr->inset + ((ncol > 0) ? listPtr->xColumnMax[ncol-1] : 0);
+    y0 = listPtr->inset + nrow*listPtr->lineHeight;
+    width = 0;
+    height = listPtr->lineHeight;
+
+    Tk_GetFontMetrics(listPtr->tkfont, &fm);
+
+    attrPtr = RpListboxGetItemAttributes((Tcl_Interp*)NULL, listPtr, index);
+    if (attrPtr) {
+        x0 += attrPtr->indent;
+    }
+
+    switch (what) {
+        case BBOX_ICON:
+            if (attrPtr->image) {
+                width = listPtr->imageWidth;
+            } else {
+                width = height = 0;
+            }
+            break;
+
+        case BBOX_TEXT:
+            /* Compute the pixel width of the requested element */
+            x0 += listPtr->imageWidth+1;
+            width += Tk_TextWidth(listPtr->tkfont, stringRep, stringLen)+2;
+            height = fm.linespace;
+            y0 += (listPtr->lineHeight - fm.linespace)/2;
+            break;
+
+        case BBOX_ALL:
+            if (attrPtr->image) {
+                width += listPtr->imageWidth + 4;
+            } else if (listPtr->imageWidth > 0) {
+                x0 += listPtr->imageWidth + 2;
+            }
+
+            /* Compute the pixel width of the requested element */
+            width += Tk_TextWidth(listPtr->tkfont, stringRep, stringLen)+2;
+            break;
+    }
+
+    sprintf(buf, "%d %d %d %d", x0-listPtr->xOffset, y0-listPtr->yOffset,
+        width, height);
+    Tcl_SetResult(interp, buf, TCL_VOLATILE);
+
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxSelectionSubCmd --
+ *
+ *	This procedure is invoked to process the selection sub command
+ *      for listbox widgets.
+ *
+ * Results:
+ *	Standard Tcl result.
+ *
+ * Side effects:
+ *	May set the interpreter's result field.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxSelectionSubCmd(interp, listPtr, objc, objv)
+    Tcl_Interp *interp;          /* Pointer to the calling Tcl interpreter */
+    Listbox *listPtr;            /* Information about the listbox */
+    int objc;                    /* Number of arguments in the objv array */
+    Tcl_Obj *CONST objv[];       /* Array of arguments to the procedure */
+{
+    int selCmdIndex, first, last;
+    int result = TCL_OK;
+    if (objc != 4 && objc != 5) {
+	Tcl_WrongNumArgs(interp, 2, objv, "option index ?index?");
+	return TCL_ERROR;
+    }
+    result = RpGetListboxIndex(interp, listPtr, objv[3], 0, &first);
+    if (result != TCL_OK) {
+	return result;
+    }
+    last = first;
+    if (objc == 5) {
+	result = RpGetListboxIndex(interp, listPtr, objv[4], 0, &last);
+	if (result != TCL_OK) {
+	    return result;
+	}
+    }
+    result = Tcl_GetIndexFromObj(interp, objv[2], selCommandNames,
+	    "option", 0, &selCmdIndex);
+    if (result != TCL_OK) {
+	return result;
+    }
+
+    /*
+     * Only allow 'selection includes' to respond if disabled. [Bug #632514]
+     */
+
+    if ((listPtr->state == STATE_DISABLED)
+	    && (selCmdIndex != SELECTION_INCLUDES)) {
+	return TCL_OK;
+    }
+
+    switch (selCmdIndex) {
+	case SELECTION_ANCHOR: {
+	    if (objc != 4) {
+		Tcl_WrongNumArgs(interp, 3, objv, "index");
+		return TCL_ERROR;
+	    }
+	    if (first >= listPtr->nElements) {
+		first = listPtr->nElements - 1;
+	    }
+	    if (first < 0) {
+		first = 0;
+	    }
+	    listPtr->selectAnchor = first;
+	    result = TCL_OK;
+	    break;
+	}
+	case SELECTION_CLEAR: {
+	    result = RpListboxSelect(listPtr, first, last, 0);
+	    break;
+	}
+	case SELECTION_INCLUDES: {
+	    if (objc != 4) {
+		Tcl_WrongNumArgs(interp, 3, objv, "index");
+		return TCL_ERROR;
+	    }
+	    Tcl_SetObjResult(interp,
+		    Tcl_NewBooleanObj((Tcl_FindHashEntry(listPtr->selection,
+			    (char *)first) != NULL)));
+	    result = TCL_OK;
+	    break;
+	}
+	case SELECTION_SET: {
+	    result = RpListboxSelect(listPtr, first, last, 1);
+	    break;
+	}
+    }
+    return result;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxXviewSubCmd --
+ *
+ *	Process the listbox "xview" subcommand.
+ *
+ * Results:
+ *	Standard Tcl result.
+ *
+ * Side effects:
+ *	May change the listbox viewing area; may set the interpreter's result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxXviewSubCmd(interp, listPtr, objc, objv)
+    Tcl_Interp *interp;          /* Pointer to the calling Tcl interpreter */
+    Listbox *listPtr;            /* Information about the listbox */
+    int objc;                    /* Number of arguments in the objv array */
+    Tcl_Obj *CONST objv[];       /* Array of arguments to the procedure */
+{
+
+    int index, count, type, windowWidth, windowUnits;
+    int offset = 0;		/* Initialized to stop gcc warnings. */
+    double fraction, fraction2;
+    
+    windowWidth = Tk_Width(listPtr->tkwin) - 2*listPtr->inset;
+
+    if (objc == 2) {
+	if (listPtr->maxWidth < 5) {
+	    Tcl_SetResult(interp, "0 1", TCL_STATIC);
+	} else {
+	    char buf[TCL_DOUBLE_SPACE * 2];
+	    
+	    fraction = listPtr->xOffset/((double)listPtr->maxWidth);
+	    fraction2 = (listPtr->xOffset + windowWidth)
+		/((double)listPtr->maxWidth);
+	    if (fraction2 > 1.0) {
+		fraction2 = 1.0;
+	    }
+	    sprintf(buf, "%g %g", fraction, fraction2);
+	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
+	}
+    } else if (objc == 3) {
+	if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+	RpChangeListboxXOffset(listPtr, index*listPtr->xScrollUnit);
+    } else {
+	type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
+	switch (type) {
+	    case TK_SCROLL_ERROR:
+		return TCL_ERROR;
+	    case TK_SCROLL_MOVETO:
+		offset = (int) (fraction*listPtr->maxWidth + 0.5);
+		break;
+	    case TK_SCROLL_PAGES:
+		windowUnits = windowWidth/listPtr->xScrollUnit;
+		if (windowUnits > 2) {
+		    offset = listPtr->xOffset
+			+ count*listPtr->xScrollUnit*(windowUnits-2);
+		} else {
+		    offset = listPtr->xOffset + count*listPtr->xScrollUnit;
+		}
+		break;
+	    case TK_SCROLL_UNITS:
+		offset = listPtr->xOffset + count*listPtr->xScrollUnit;
+		break;
+	}
+	RpChangeListboxXOffset(listPtr, offset);
+    }
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxYviewSubCmd --
+ *
+ *	Process the listbox "yview" subcommand.
+ *
+ * Results:
+ *	Standard Tcl result.
+ *
+ * Side effects:
+ *	May change the listbox viewing area; may set the interpreter's result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxYviewSubCmd(interp, listPtr, objc, objv)
+    Tcl_Interp *interp;          /* Pointer to the calling Tcl interpreter */
+    Listbox *listPtr;            /* Information about the listbox */
+    int objc;                    /* Number of arguments in the objv array */
+    Tcl_Obj *CONST objv[];       /* Array of arguments to the procedure */
+{
+    int index, count, type, worldHeight, yOffset;
+    double fraction, fraction2;
+
+    /* make sure that layout is fresh -- we'll need it below */
+    if (listPtr->flags & GEOMETRY_IS_STALE) {
+        RpListboxComputeGeometry(listPtr, 0, 1, 0);
+        listPtr->flags &= ~GEOMETRY_IS_STALE;
+    }
+    worldHeight = listPtr->elemsPerColumn*listPtr->lineHeight;
+
+    if (objc == 2) {
+	if (listPtr->nElements == 0) {
+	    Tcl_SetResult(interp, "0 1", TCL_STATIC);
+	} else {
+	    char buf[TCL_DOUBLE_SPACE * 2];
+
+	    fraction = listPtr->yOffset/((double)worldHeight);
+	    fraction2 = (listPtr->yOffset+Tk_Height(listPtr->tkwin))
+                          / ((double)worldHeight);
+	    if (fraction2 > 1.0) {
+		fraction2 = 1.0;
+	    }
+	    sprintf(buf, "%g %g", fraction, fraction2);
+	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
+	}
+    } else if (objc == 3) {
+	if (RpGetListboxIndex(interp, listPtr, objv[2], 0, &index) != TCL_OK) {
+	    return TCL_ERROR;
+	}
+	RpChangeListboxView(listPtr, index);
+    } else {
+        yOffset = 0;
+	type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
+	switch (type) {
+	    case TK_SCROLL_ERROR:
+		return TCL_ERROR;
+	    case TK_SCROLL_MOVETO:
+                yOffset = fraction*worldHeight;
+		break;
+	    case TK_SCROLL_PAGES:
+                yOffset = listPtr->yOffset + count*Tk_Height(listPtr->tkwin);
+		break;
+	    case TK_SCROLL_UNITS:
+		yOffset = listPtr->yOffset + count*listPtr->lineHeight;
+		break;
+	}
+        RpChangeListboxYOffset(listPtr, yOffset);
+    }
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxGetItemAttributes --
+ *
+ *	Returns a pointer to the ItemAttr record for a given index,
+ *	creating one if it does not already exist.
+ *
+ * Results:
+ *	Pointer to an ItemAttr record.
+ *
+ * Side effects:
+ *	Memory may be allocated for the ItemAttr record.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static ItemAttr *
+RpListboxGetItemAttributes(interp, listPtr, index)
+    Tcl_Interp *interp;          /* Pointer to the calling Tcl interpreter */
+    Listbox *listPtr;            /* Information about the listbox */
+    int index;                   /* Index of the item to retrieve attributes
+				  * for */
+{
+    int new;
+    Tcl_HashEntry *entry;
+    ItemAttr *attrs;
+
+    entry = Tcl_CreateHashEntry(listPtr->itemAttrTable, (char *)index, &new);
+    if (new) {
+	attrs = (ItemAttr *) ckalloc(sizeof(ItemAttr));
+	attrs->border = NULL;
+	attrs->selBorder = NULL;
+	attrs->fgColor = NULL;
+	attrs->selFgColor = NULL;
+	attrs->data = NULL;
+	attrs->indent = 0;
+	attrs->imagePtr = NULL;
+	attrs->image = NULL;
+	Tk_InitOptions(interp, (char *)attrs, listPtr->itemAttrOptionTable,
+		listPtr->tkwin);
+	Tcl_SetHashValue(entry, (ClientData) attrs);
+    }
+    attrs = (ItemAttr *)Tcl_GetHashValue(entry);
+    return attrs;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpDestroyListbox --
+ *
+ *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
+ *	to clean up the internal structure of a listbox at a safe time
+ *	(when no-one is using it anymore).
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Everything associated with the listbox is freed up.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpDestroyListbox(memPtr)
+    char *memPtr;	/* Info about listbox widget. */
+{
+    register Listbox *listPtr = (Listbox *) memPtr;
+    Tcl_HashEntry *entry;
+    Tcl_HashSearch search;
+    ItemAttr *attrPtr;
+
+    /* If we have an internal list object, free it */
+    if (listPtr->listObj != NULL) {
+	Tcl_DecrRefCount(listPtr->listObj);
+	listPtr->listObj = NULL;
+    }
+
+    /* Free the selection hash table */
+    Tcl_DeleteHashTable(listPtr->selection);
+    ckfree((char *)listPtr->selection);
+
+    /* Free the item attribute hash table */
+    for (entry = Tcl_FirstHashEntry(listPtr->itemAttrTable, &search);
+	 entry != NULL; entry = Tcl_NextHashEntry(&search)) {
+
+        attrPtr = (ItemAttr *)Tcl_GetHashValue(entry);
+
+        Tk_FreeConfigOptions((char*)attrPtr, listPtr->itemAttrOptionTable,
+            listPtr->tkwin);
+
+        if (attrPtr->image != NULL) {
+            Tk_FreeImage(attrPtr->image);
+        }
+	ckfree((char *)attrPtr);
+    }
+    Tcl_DeleteHashTable(listPtr->itemAttrTable);
+    ckfree((char *)listPtr->itemAttrTable);
+
+    /*
+     * Free up all the stuff that requires special handling, then
+     * let Tk_FreeOptions handle all the standard option-related
+     * stuff.
+     */
+
+    if (listPtr->textGC != None) {
+	Tk_FreeGC(listPtr->display, listPtr->textGC);
+    }
+    if (listPtr->selTextGC != None) {
+	Tk_FreeGC(listPtr->display, listPtr->selTextGC);
+    }
+    if (listPtr->gray != None) {
+	Tk_FreeBitmap(Tk_Display(listPtr->tkwin), listPtr->gray);
+    }
+
+    Tk_FreeConfigOptions((char *)listPtr, listPtr->optionTable,
+	    listPtr->tkwin);
+    Tcl_Release((ClientData) listPtr->tkwin);
+    listPtr->tkwin = NULL;
+    ckfree((char *) listPtr);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpDestroyListboxOptionTables --
+ *
+ *	This procedure is registered as an exit callback when the listbox
+ *	command is first called.  It cleans up the OptionTables structure
+ *	allocated by that command.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Frees memory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpDestroyListboxOptionTables(clientData, interp)
+    ClientData clientData;	/* Pointer to the OptionTables struct */
+    Tcl_Interp *interp;		/* Pointer to the calling interp */
+{
+    ckfree((char *)clientData);
+    return;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpConfigureListbox --
+ *
+ *	This procedure is called to process an objv/objc list, plus
+ *	the Tk option database, in order to configure (or reconfigure)
+ *	a listbox widget.
+ *
+ * Results:
+ *	The return value is a standard Tcl result.  If TCL_ERROR is
+ *	returned, then the interp's result contains an error message.
+ *
+ * Side effects:
+ *	Configuration information, such as colors, border width,
+ *	etc. get set for listPtr;  old resources get freed,
+ *	if there were any.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpConfigureListbox(interp, listPtr, objc, objv, flags)
+    Tcl_Interp *interp;		/* Used for error reporting. */
+    register Listbox *listPtr;	/* Information about widget;  may or may
+				 * not already have values for some fields. */
+    int objc;			/* Number of valid entries in argv. */
+    Tcl_Obj *CONST objv[];	/* Arguments. */
+    int flags;			/* Flags to pass to Tk_ConfigureWidget. */
+{
+    Tk_SavedOptions savedOptions;
+    Tcl_Obj *errorResult = NULL;
+    int oldExport, error;
+
+    oldExport = listPtr->exportSelection;
+
+    for (error = 0; error <= 1; error++) {
+	if (!error) {
+	    /*
+	     * First pass: set options to new values.
+	     */
+
+	    if (Tk_SetOptions(interp, (char *) listPtr,
+		    listPtr->optionTable, objc, objv,
+		    listPtr->tkwin, &savedOptions, (int *) NULL) != TCL_OK) {
+		continue;
+	    }
+	} else {
+	    /*
+	     * Second pass: restore options to old values.
+	     */
+
+	    errorResult = Tcl_GetObjResult(interp);
+	    Tcl_IncrRefCount(errorResult);
+	    Tk_RestoreSavedOptions(&savedOptions);
+	}
+
+	/*
+	 * A few options need special processing, such as setting the
+	 * background from a 3-D border.
+	 */
+
+	Tk_SetBackgroundFromBorder(listPtr->tkwin, listPtr->normalBorder);
+
+	if (listPtr->highlightWidth < 0) {
+	    listPtr->highlightWidth = 0;
+	}
+	listPtr->inset = listPtr->highlightWidth + listPtr->borderWidth;
+
+	/*
+	 * Claim the selection if we've suddenly started exporting it and
+	 * there is a selection to export.
+	 */
+
+	if (listPtr->exportSelection && !oldExport
+		&& (listPtr->numSelected != 0)) {
+	    Tk_OwnSelection(listPtr->tkwin, XA_PRIMARY, RpListboxLostSelection,
+		    (ClientData) listPtr);
+	}
+
+	if (listPtr->listObj == NULL) {
+	    listPtr->listObj = Tcl_NewObj();
+	}
+	Tcl_IncrRefCount(listPtr->listObj);
+	break;
+    }
+    if (!error) {
+	Tk_FreeSavedOptions(&savedOptions);
+    }
+
+    /* Make sure that the list length is correct */
+    Tcl_ListObjLength(listPtr->interp, listPtr->listObj, &listPtr->nElements);
+    
+    if (error) {
+        Tcl_SetObjResult(interp, errorResult);
+	Tcl_DecrRefCount(errorResult);
+	return TCL_ERROR;
+    } else {
+	RpListboxWorldChanged((ClientData) listPtr);
+	return TCL_OK;
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpConfigureListboxItem --
+ *
+ *	This procedure is called to process an objv/objc list, plus
+ *	the Tk option database, in order to configure (or reconfigure)
+ *	a listbox item.
+ *
+ * Results:
+ *	The return value is a standard Tcl result.  If TCL_ERROR is
+ *	returned, then the interp's result contains an error message.
+ *
+ * Side effects:
+ *	Configuration information, such as colors, border width,
+ *	etc. get set for a listbox item;  old resources get freed,
+ *	if there were any.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpConfigureListboxItem(interp, listPtr, attrs, objc, objv, index)
+    Tcl_Interp *interp;		/* Used for error reporting. */
+    register Listbox *listPtr;	/* Information about widget;  may or may
+				 * not already have values for some fields. */
+    ItemAttr *attrs;		/* Information about the item to configure */
+    int objc;			/* Number of valid entries in argv. */
+    Tcl_Obj *CONST objv[];	/* Arguments. */
+    int index;			/* Index of the listbox item being configure */
+{
+    Tk_SavedOptions savedOptions;
+    Tk_Image image;
+
+    if (Tk_SetOptions(interp, (char *)attrs,
+	    listPtr->itemAttrOptionTable, objc, objv, listPtr->tkwin,
+	    &savedOptions, (int *)NULL) != TCL_OK) {
+	Tk_RestoreSavedOptions(&savedOptions);
+	return TCL_ERROR;
+    }
+    Tk_FreeSavedOptions(&savedOptions);
+
+    /*
+     *  If this item has an image name, then translate it to an
+     *  image that we can draw.
+     */
+    if (attrs->imagePtr != NULL) {
+        image = Tk_GetImage(interp, listPtr->tkwin,
+                attrs->imagePtr, RpListboxImageProc,
+                (ClientData) attrs);
+    } else {
+        image = NULL;
+    }
+    if (attrs->image != NULL) {
+        Tk_FreeImage(attrs->image);
+    }
+    attrs->image = image;
+
+    /*
+     * If this item has an image or an indent, then we should recompute
+     * the geometry and all bets are off.
+     */
+    if (attrs->image || attrs->indent > 0) {
+        listPtr->flags |= GEOMETRY_IS_STALE;
+        RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+    } else {
+        /* okay to redraw just this index */
+        RpEventuallyRedrawRange(listPtr, index, index);
+    }
+    return TCL_OK;
+}
+
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * RpListboxWorldChanged --
+ *
+ *      This procedure is called when the world has changed in some
+ *      way and the widget needs to recompute all its graphics contexts
+ *	and determine its new geometry.
+ *
+ * Results:
+ *      None.
+ *
+ * Side effects:
+ *      Listbox will be relayed out and redisplayed.
+ *
+ *---------------------------------------------------------------------------
+ */
+ 
+static void
+RpListboxWorldChanged(instanceData)
+    ClientData instanceData;	/* Information about widget. */
+{
+    XGCValues gcValues;
+    GC gc;
+    unsigned long mask;
+    Listbox *listPtr;
+    
+    listPtr = (Listbox *) instanceData;
+
+    if (listPtr->state & STATE_NORMAL) {
+	gcValues.foreground = listPtr->fgColorPtr->pixel;
+	gcValues.graphics_exposures = False;
+	mask = GCForeground | GCFont | GCGraphicsExposures;
+    } else {
+	if (listPtr->dfgColorPtr != NULL) {
+	    gcValues.foreground = listPtr->dfgColorPtr->pixel;
+	    gcValues.graphics_exposures = False;
+	    mask = GCForeground | GCFont | GCGraphicsExposures;
+	} else {
+	    gcValues.foreground = listPtr->fgColorPtr->pixel;
+	    mask = GCForeground | GCFont;
+	    if (listPtr->gray == None) {
+		listPtr->gray = Tk_GetBitmap(NULL, listPtr->tkwin, "gray50");
+	    }
+	    if (listPtr->gray != None) {
+		gcValues.fill_style = FillStippled;
+		gcValues.stipple = listPtr->gray;
+		mask |= GCFillStyle | GCStipple;
+	    }
+	}
+    }
+
+    gcValues.font = Tk_FontId(listPtr->tkfont);
+    gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
+    if (listPtr->textGC != None) {
+	Tk_FreeGC(listPtr->display, listPtr->textGC);
+    }
+    listPtr->textGC = gc;
+
+    if (listPtr->selFgColorPtr != NULL) {
+	gcValues.foreground = listPtr->selFgColorPtr->pixel;
+    }
+    gcValues.font = Tk_FontId(listPtr->tkfont);
+    mask = GCForeground | GCFont;
+    gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
+    if (listPtr->selTextGC != None) {
+	Tk_FreeGC(listPtr->display, listPtr->selTextGC);
+    }
+    listPtr->selTextGC = gc;
+
+    /*
+     * Register the desired geometry for the window and arrange for
+     * the window to be redisplayed.
+     */
+
+    RpListboxComputeGeometry(listPtr, 1, 1, 1);
+    listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
+    RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RpDisplayListbox --
+ *
+ *	This procedure redraws the contents of a listbox window.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Information appears on the screen.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+RpDisplayListbox(clientData)
+    ClientData clientData;	/* Information about window. */
+{
+    register Listbox *listPtr = (Listbox *) clientData;
+    register Tk_Window tkwin = listPtr->tkwin;
+    GC gc;
+    int i, first, last, ncol, nrow, x0, y0, x, y, width, freeGC;
+    int indent, imageWidth, imageHeight;
+    Tk_FontMetrics fm;
+    Tcl_Obj *curElement;
+    char *stringRep;
+    int stringLen;
+    ItemAttr *attrs;
+    Tk_3DBorder selectedBg;
+    XGCValues gcValues;
+    unsigned long mask;
+    Pixmap pixmap;
+
+    listPtr->flags &= ~REDRAW_PENDING;
+    if (listPtr->flags & LISTBOX_DELETED) {
+	return;
+    }
+
+    if (listPtr->flags & GEOMETRY_IS_STALE) {
+	RpListboxComputeGeometry(listPtr, 0, 1, 0);
+	listPtr->flags &= ~GEOMETRY_IS_STALE;
+	listPtr->flags |= UPDATE_H_SCROLLBAR;
+    }
+
+    Tcl_Preserve((ClientData) listPtr);
+    if (listPtr->flags & UPDATE_V_SCROLLBAR) {
+	RpListboxUpdateVScrollbar(listPtr);
+	if ((listPtr->flags & LISTBOX_DELETED) || !Tk_IsMapped(tkwin)) {
+	    Tcl_Release((ClientData) listPtr);
+	    return;
+	}
+    }
+    if (listPtr->flags & UPDATE_H_SCROLLBAR) {
+	RpListboxUpdateHScrollbar(listPtr);
+	if ((listPtr->flags & LISTBOX_DELETED) || !Tk_IsMapped(tkwin)) {
+	    Tcl_Release((ClientData) listPtr);
+	    return;
+	}
+    }
+    listPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);
+    Tcl_Release((ClientData) listPtr);
+
+#ifndef TK_NO_DOUBLE_BUFFERING
+    /*
+     * Redrawing is done in a temporary pixmap that is allocated
+     * here and freed at the end of the procedure.  All drawing is
+     * done to the pixmap, and the pixmap is copied to the screen
+     * at the end of the procedure.  This provides the smoothest
+     * possible visual effects (no flashing on the screen).
+     */
+
+    pixmap = Tk_GetPixmap(listPtr->display, Tk_WindowId(tkwin),
+	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
+#else
+    pixmap = Tk_WindowId(tkwin);
+#endif /* TK_NO_DOUBLE_BUFFERING */
+    Tk_Fill3DRectangle(tkwin, pixmap, listPtr->normalBorder, 0, 0,
+	    Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
+
+    /* Display each item in the current view */
+    first = RpNearestListboxElement(listPtr, listPtr->inset, listPtr->inset);
+    last = RpNearestListboxElement(listPtr,
+        Tk_Width(tkwin)-listPtr->inset, Tk_Height(tkwin)-listPtr->inset);
+
+    for (i=first; i <= last; i++) {
+        if (listPtr->elemsPerColumn == 0) {
+            ncol = nrow = 0;
+        } else {
+            ncol = i / listPtr->elemsPerColumn;
+            nrow = i % listPtr->elemsPerColumn;
+        }
+	x0 = listPtr->inset + ((ncol > 0) ? listPtr->xColumnMax[ncol-1] : 0)
+                 - listPtr->xOffset;
+	y0 = listPtr->inset + nrow*listPtr->lineHeight
+                 - listPtr->yOffset;
+
+        if (ncol == listPtr->numColumns-1) {
+            width = Tk_Width(listPtr->tkwin) - listPtr->inset - x0;
+        } else if (ncol > 0) {
+            width = listPtr->xColumnMax[ncol] - listPtr->xColumnMax[ncol-1];
+        } else {
+            width = listPtr->xColumnMax[0];
+        }
+
+	gc = listPtr->textGC;
+	freeGC = 0;
+
+	/*
+	 * Lookup this item in the item attributes table, to see if it has
+	 * special foreground/background colors
+	 */
+        attrs = RpListboxGetItemAttributes((Tcl_Interp*)NULL, listPtr, i);
+
+	/*
+	 * If the listbox is enabled, items may be drawn differently;
+	 * they may be drawn selected, or they may have special foreground
+	 * or background colors.
+	 */
+	if (listPtr->state & STATE_NORMAL) {
+            if (Tcl_FindHashEntry(listPtr->selection, (char *)i) != NULL) {
+		/* Selected items are drawn differently. */
+		gc = listPtr->selTextGC;
+		selectedBg = listPtr->selBorder;
+		
+		/* If there is attribute information for this item,
+		 * adjust the drawing accordingly */
+		if (attrs) {
+		    /* Default GC has the values from the widget at large */
+		    if (listPtr->selFgColorPtr) {
+			gcValues.foreground = listPtr->selFgColorPtr->pixel;
+		    } else {
+			gcValues.foreground = listPtr->fgColorPtr->pixel;
+		    }
+		    gcValues.font = Tk_FontId(listPtr->tkfont);
+		    gcValues.graphics_exposures = False;
+		    mask = GCForeground | GCFont | GCGraphicsExposures;
+		    
+		    if (attrs->selBorder != NULL) {
+			selectedBg = attrs->selBorder;
+		    }
+		    
+		    if (attrs->selFgColor != NULL) {
+			gcValues.foreground = attrs->selFgColor->pixel;
+			gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
+			freeGC = 1;
+		    }
+		}
+
+                /*
+                 * Don't bother joining the selection rectangles around
+                 * multiple selected elements.  Just put a beveled
+                 * rectangle around each one.
+                 */
+		Tk_Fill3DRectangle(tkwin, pixmap, selectedBg, x0, y0,
+		    width, listPtr->lineHeight,
+                    listPtr->selBorderWidth, TK_RELIEF_RAISED);
+	    } else {
+		/*
+		 * If there is an item attributes record for this item, draw
+		 * the background box and set the foreground color accordingly
+		 */
+		if (attrs) {
+		    gcValues.foreground = listPtr->fgColorPtr->pixel;
+		    gcValues.font = Tk_FontId(listPtr->tkfont);
+		    gcValues.graphics_exposures = False;
+		    mask = GCForeground | GCFont | GCGraphicsExposures;
+		    
+		    /*
+		     * If the item has its own background color, draw it now.
+		     */
+		    
+		    if (attrs->border != NULL) {
+			Tk_Fill3DRectangle(tkwin, pixmap, attrs->border,
+                            x0, y0, width, listPtr->lineHeight,
+                            0, TK_RELIEF_FLAT);
+		    }
+		    
+		    /*
+		     * If the item has its own foreground, use it to override
+		     * the value in the gcValues structure.
+		     */
+		    
+		    if ((listPtr->state & STATE_NORMAL)
+			    && attrs->fgColor != NULL) {
+			gcValues.foreground = attrs->fgColor->pixel;
+			gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
+			freeGC = 1;
+		    }
+		}
+	    }
+	}
+
+	/* Find any extra indent */
+        indent = 0;
+        if (attrs && attrs->indent > 0) {
+            indent = attrs->indent;
+        }
+
+	/* Draw any icon for the item */
+	x = x0 + indent + listPtr->imageWidth + listPtr->selBorderWidth;
+        if (attrs && attrs->image) {
+            Tk_SizeOfImage(attrs->image, &imageWidth, &imageHeight);
+
+            RpDisplayImage(pixmap, Tk_Width(tkwin), Tk_Height(tkwin),
+                attrs->image,
+                x-imageWidth-2, y0+(listPtr->lineHeight-imageHeight)/2);
+        }
+
+	/* Draw the actual text of this item */
+	Tk_GetFontMetrics(listPtr->tkfont, &fm);
+	y = y0 + (listPtr->lineHeight-fm.linespace)/2
+               + fm.ascent + listPtr->selBorderWidth;
+
+        stringRep = NULL;
+	Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i, &curElement);
+        if (curElement) {
+	    stringRep = Tcl_GetStringFromObj(curElement, &stringLen);
+	    Tk_DrawChars(listPtr->display, pixmap, gc, listPtr->tkfont,
+		stringRep, stringLen, x+2, y);
+        }
+
+	/* If this is the active element, apply the activestyle to it. */
+	if ((i == listPtr->active) && (listPtr->flags & GOT_FOCUS) && stringRep) {
+	    if (listPtr->activeStyle == ACTIVE_STYLE_UNDERLINE) {
+		/* Underline the text. */
+		Tk_UnderlineChars(listPtr->display, pixmap, gc,
+			listPtr->tkfont, stringRep, x+2, y, 0, stringLen);
+	    } else if (listPtr->activeStyle == ACTIVE_STYLE_DOTBOX) {
+#ifdef WIN32
+		/*
+		 * This provides for exact default look and feel on Windows.
+		 */
+		TkWinDCState state;
+		HDC dc;
+		RECT rect;
+
+		dc = TkWinGetDrawableDC(listPtr->display, pixmap, &state);
+		rect.left   = listPtr->inset;
+		rect.top    = y0;
+		rect.right  = rect.left + width;
+		rect.bottom = rect.top + listPtr->lineHeight;
+		DrawFocusRect(dc, &rect);
+		TkWinReleaseDrawableDC(pixmap, dc, &state);
+#else
+		/*
+		 * Draw a dotted box around the text.
+		 */
+		x = x0 + indent + listPtr->imageWidth;
+		y = y0 + listPtr->lineHeight;
+
+		gcValues.line_style  = LineOnOffDash;
+		gcValues.line_width  = listPtr->selBorderWidth;
+		if (gcValues.line_width <= 0) {
+		    gcValues.line_width  = 1;
+		}
+		gcValues.dash_offset = 0;
+		gcValues.dashes      = 1;
+		/*
+		 * You would think the XSetDashes was necessary, but it
+		 * appears that the default dotting for just saying we
+		 * want dashes appears to work correctly.
+		 static char dashList[] = { 1 };
+		 static int  dashLen    = sizeof(dashList);
+		 XSetDashes(listPtr->display, gc, 0, dashList, dashLen);
+		 */
+		mask = GCLineWidth | GCLineStyle | GCDashList | GCDashOffset;
+		XChangeGC(listPtr->display, gc, mask, &gcValues);
+		XDrawRectangle(listPtr->display, pixmap, gc, x, y,
+			(unsigned) width, (unsigned) listPtr->lineHeight - 1);
+		if (!freeGC) {
+		    /* Don't bother changing if it is about to be freed. */
+		    gcValues.line_style = LineSolid;
+		    XChangeGC(listPtr->display, gc, GCLineStyle, &gcValues);
+		}
+#endif
+	    }
+	}
+
+	if (freeGC) {
+	    Tk_FreeGC(listPtr->display, gc);
+	}
+    }
+
+    /*
+     * Redraw the border for the listbox to make sure that it's on top
+     * of any of the text of the listbox entries.
+     */
+
+    Tk_Draw3DRectangle(tkwin, pixmap, listPtr->normalBorder,
+	    listPtr->highlightWidth, listPtr->highlightWidth,
+	    Tk_Width(tkwin) - 2*listPtr->highlightWidth,
+	    Tk_Height(tkwin) - 2*listPtr->highlightWidth,
+	    listPtr->borderWidth, listPtr->relief);
+    if (listPtr->highlightWidth > 0) {
+	GC fgGC, bgGC;
+
+	bgGC = Tk_GCForColor(listPtr->highlightBgColorPtr, pixmap);
+	if (listPtr->flags & GOT_FOCUS) {
+	    fgGC = Tk_GCForColor(listPtr->highlightColorPtr, pixmap);
+	    TkpDrawHighlightBorder(tkwin, fgGC, bgGC, 
+	            listPtr->highlightWidth, pixmap);
+	} else {
+	    TkpDrawHighlightBorder(tkwin, bgGC, bgGC, 
+	            listPtr->highlightWidth, pixmap);
+	}
+    }
+#ifndef TK_NO_DOUBLE_BUFFERING
+    XCopyArea(listPtr->display, pixmap, Tk_WindowId(tkwin),
+	    listPtr->textGC, 0, 0, (unsigned) Tk_Width(tkwin),
+	    (unsigned) Tk_Height(tkwin), 0, 0);
+    Tk_FreePixmap(listPtr->display, pixmap);
+#endif /* TK_NO_DOUBLE_BUFFERING */
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RpDisplayImage --
+ *
+ *	This procedure draws a Tk image at the given (x,y)
+ *	coordinate, being careful to clip the image if it
+ *	falls off an edge.  If we're not careful, then Tk
+ *	simply ignores the drawing command and the whole
+ *	image disappears.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Draws the image into the pixmap.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+RpDisplayImage(pixmap, w, h, image, x, y)
+    Pixmap pixmap;	/* Draw into this drawable */
+    int w, h;           /* overall size of pixmap */
+    Tk_Image image;	/* Draw this image into drawable */
+    int x, y;		/* put upper-left corner at this (x,y) */
+{
+    int xoffs, yoffs, imageWidth, imageHeight;
+
+    xoffs = yoffs = 0;
+    Tk_SizeOfImage(image, &imageWidth, &imageHeight);
+
+    if (x+imageWidth < 0 || x > w || y+imageHeight < 0 || y > h) {
+        return;  /* completely off screen */
+    }
+
+    if (x < 0) {                  /* falling off on the left side */
+        xoffs = -x;
+        imageWidth -= xoffs;
+        x = 0;
+    }
+    else if (x+imageWidth > w) {  /* falling off on the right side */
+        imageWidth = w-x;
+    }
+
+    if (y < 0) {                  /* falling off on the top side */
+        yoffs = -y;
+        imageHeight -= yoffs;
+        y = 0;
+    }
+    else if (y+imageHeight > h) { /* falling off on the bottom side */
+        imageHeight = h-y;
+    }
+
+    Tk_RedrawImage(image, xoffs, yoffs, imageWidth, imageHeight, pixmap, x, y);
+}
+
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxImageProc --
+ *
+ *	This procedure is invoked by the image code whenever the manager
+ *	for an image does something that affects the size or contents
+ *	of an image displayed in a listbox entry.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Arranges for the listbox to get redisplayed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
+    ClientData clientData;		/* Pointer to widget record. */
+    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. */
+{
+    register Listbox *listPtr = (Listbox *) clientData;
+
+    if (listPtr->tkwin != NULL) {
+        RpListboxComputeGeometry(listPtr, 1, 1, 0);
+	if (Tk_IsMapped(listPtr->tkwin) && !(listPtr->flags & REDRAW_PENDING)) {
+            Tcl_DoWhenIdle(RpDisplayListbox, (ClientData) listPtr);
+	    listPtr->flags |= REDRAW_PENDING;
+	}
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxComputeGeometry --
+ *
+ *	This procedure is invoked to recompute geometry information
+ *	such as the sizes of the elements and the overall dimensions
+ *	desired for the listbox.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Geometry information is updated and a new requested size is
+ *	registered for the widget.  Internal border and gridding
+ *	information is also set.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxComputeGeometry(listPtr, fontChanged, maxIsStale, updateGrid)
+    Listbox *listPtr;		/* Listbox whose geometry is to be
+				 * recomputed. */
+    int fontChanged;		/* Non-zero means the font may have changed
+				 * so per-element width information also
+				 * has to be computed. */
+    int maxIsStale;		/* Non-zero means the "maxWidth" field may
+				 * no longer be up-to-date and must
+				 * be recomputed.  If fontChanged is 1 then
+				 * this must be 1. */
+    int updateGrid;		/* Non-zero means call Tk_SetGrid or
+				 * Tk_UnsetGrid to update gridding for
+				 * the window. */
+{
+    int width, height, pixelWidth, pixelHeight;
+    int imageWidth, imageHeight, maxWidth, reqHeight, nrow, ncol;
+    Tk_FontMetrics fm;
+    Tcl_Obj *element;
+    int textLength;
+    char *text;
+    int i, result;
+    ItemAttr *attrPtr;
+
+    if (listPtr->flags & LISTBOX_DELETED) {
+        return;
+    }
+
+    Tk_GetFontMetrics(listPtr->tkfont, &fm);
+
+    /*
+     * Figure out max icon size and the overall height of each line
+     */
+    if (fontChanged || maxIsStale) {
+	listPtr->xScrollUnit = Tk_TextWidth(listPtr->tkfont, "0", 1);
+	if (listPtr->xScrollUnit == 0) {
+	    listPtr->xScrollUnit = 1;
+	}
+
+	listPtr->imageWidth = 0;
+	listPtr->imageHeight = 0;
+	for (i = 0; i < listPtr->nElements; i++) {
+	    /* Compute the pixel width of the current element */
+	    result = Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i,
+		    &element);
+	    if (result != TCL_OK) {
+		continue;
+	    }
+	    attrPtr = RpListboxGetItemAttributes(listPtr->interp, listPtr, i);
+
+            /* compute the max image width/height */
+            if (attrPtr && attrPtr->image) {
+                Tk_SizeOfImage(attrPtr->image, &imageWidth, &imageHeight);
+                imageWidth += 4;  /* add padding on either side */
+                imageHeight += 4;
+                if (imageWidth > listPtr->imageWidth) {
+                    listPtr->imageWidth = imageWidth;
+                }
+                if (imageHeight > listPtr->imageHeight) {
+                    listPtr->imageHeight = imageHeight;
+                }
+            }
+	}
+    }
+
+    listPtr->lineHeight = fm.linespace + 1 + 2*listPtr->selBorderWidth;
+    if (listPtr->imageHeight > listPtr->lineHeight) {
+        listPtr->lineHeight = listPtr->imageHeight;
+    }
+
+    /*
+     * Compute a new layout -- either vertical or horizontal
+     */
+    if (fontChanged  || maxIsStale) {
+        if (listPtr->xColumnMax
+              && listPtr->xColumnMax != listPtr->xColumnSpace) {
+            ckfree((char*)listPtr->xColumnMax);
+            listPtr->xColumnMax = NULL;
+        }
+        if (listPtr->orient == ORIENT_HORIZONTAL) {
+            reqHeight = listPtr->height * listPtr->lineHeight;
+            if (Tk_Height(listPtr->tkwin) > reqHeight) {
+                reqHeight = Tk_Height(listPtr->tkwin);
+            }
+            listPtr->elemsPerColumn = reqHeight / listPtr->lineHeight;
+            if (listPtr->elemsPerColumn == 0) {
+                listPtr->elemsPerColumn = 1;
+            }
+
+            listPtr->numColumns = listPtr->nElements / listPtr->elemsPerColumn;
+            if (listPtr->nElements % listPtr->elemsPerColumn != 0) {
+                listPtr->numColumns++;
+            }
+
+            if (listPtr->numColumns <= 10) {
+                listPtr->xColumnMax = listPtr->xColumnSpace;
+            } else {
+                listPtr->xColumnMax = (int*)ckalloc(listPtr->numColumns*sizeof(int));
+            }
+        } else {
+            listPtr->elemsPerColumn = listPtr->nElements;
+            listPtr->numColumns = 1;
+            listPtr->xColumnMax = listPtr->xColumnSpace;
+        }
+
+	maxWidth = 0;
+        nrow = ncol = 0;
+	for (i = 0; i < listPtr->nElements; i++) {
+	    /* Compute the pixel width of the current element */
+	    result = Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i,
+		    &element);
+	    if (result != TCL_OK) {
+		continue;
+	    }
+	    text = Tcl_GetStringFromObj(element, &textLength);
+	    attrPtr = RpListboxGetItemAttributes(listPtr->interp, listPtr, i);
+
+	    pixelWidth = Tk_TextWidth(listPtr->tkfont, text, textLength);
+            if (attrPtr && attrPtr->indent > 0) {
+                pixelWidth += attrPtr->indent;
+            }
+	    if (pixelWidth > maxWidth) {
+		maxWidth = pixelWidth;
+	    }
+
+            if (++nrow >= listPtr->elemsPerColumn) {
+                listPtr->xColumnMax[ncol] =
+                    ((ncol > 0) ? listPtr->xColumnMax[ncol-1] : 0)
+                    + maxWidth + listPtr->imageWidth + 10;
+
+                maxWidth = 0;
+                nrow = 0; ncol++;
+            }
+	}
+
+        if (nrow > 0) {
+            /* finalize last column if it was in progress */
+            listPtr->xColumnMax[ncol] =
+                ((ncol > 0) ? listPtr->xColumnMax[ncol-1] : 0)
+                + maxWidth + listPtr->imageWidth + 4;
+        }
+
+        listPtr->maxWidth = listPtr->xColumnMax[listPtr->numColumns-1];
+
+        /*
+         * If the scroll offsets are outside the view, reset them.
+         */
+        if (listPtr->xOffset > 0.8*listPtr->xOffset) {
+            listPtr->xOffset = 0;
+            RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+        }
+        if (listPtr->orient == ORIENT_HORIZONTAL) {
+            listPtr->yOffset = 0;
+        } else {
+            height = listPtr->nElements*listPtr->lineHeight
+                       - Tk_Height(listPtr->tkwin) + 2*listPtr->inset;
+            if (height < 0) {
+                height = 0;
+            }
+            if (listPtr->yOffset > height) {
+                listPtr->yOffset = height;
+            }
+        }
+    }
+
+    /*
+     * Make sure that topIndex is still at top of column.
+     */
+    if (listPtr->elemsPerColumn == 0) {
+        ncol = 0;
+    } else {
+        ncol = listPtr->topIndex / listPtr->elemsPerColumn;
+    }
+    listPtr->topIndex = ncol*listPtr->elemsPerColumn;
+
+    /*
+     * Snap to nearest scroll increment and request geometry size.
+     */
+    width = listPtr->width;
+    if (width <= 0) {
+	width = (listPtr->maxWidth + listPtr->xScrollUnit - 1)
+		/listPtr->xScrollUnit;
+	if (width < 1) {
+	    width = 1;
+	}
+    }
+    pixelWidth = width*listPtr->xScrollUnit + 2*listPtr->inset
+	    + 2*listPtr->selBorderWidth;
+
+    height = listPtr->height;
+    if (listPtr->height <= 0) {
+	height = listPtr->elemsPerColumn;
+	if (height < 1) {
+	    height = 1;
+	}
+    }
+    pixelHeight = height*listPtr->lineHeight + 2*listPtr->inset;
+
+    Tk_GeometryRequest(listPtr->tkwin, pixelWidth, pixelHeight);
+    Tk_SetInternalBorder(listPtr->tkwin, listPtr->inset);
+    if (updateGrid) {
+	if (listPtr->setGrid) {
+	    Tk_SetGrid(listPtr->tkwin, width, height, listPtr->xScrollUnit,
+		    listPtr->lineHeight);
+	} else {
+	    Tk_UnsetGrid(listPtr->tkwin);
+	}
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxInsertSubCmd --
+ *
+ *	This procedure is invoked to handle the listbox "insert"
+ *      subcommand.  It's a little different from the usual Listbox
+ *	insert.  It takes a single value and all configuration options
+ *	for that one value.
+ *
+ * Results:
+ *	Standard Tcl result.
+ *
+ * Side effects:
+ *	New elements are added to the listbox pointed to by listPtr;
+ *      a refresh callback is registered for the listbox.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxInsertSubCmd(listPtr, index, objc, objv)
+    register Listbox *listPtr;	/* Listbox that is to get the new
+				 * elements. */
+    int index;			/* Add the new elements before this
+				 * element. */
+    int objc;			/* Number of new elements to add. */
+    Tcl_Obj *CONST objv[];	/* New elements (one per entry). */
+{
+    Tcl_Obj *newListObj;
+    int result;
+    ItemAttr *attrPtr;
+    
+    /* Adjust selection and attribute information beyond the current index */
+    RpMigrateHashEntries(listPtr->selection, index, listPtr->nElements-1, 1);
+    RpMigrateHashEntries(listPtr->itemAttrTable, index, listPtr->nElements-1, 1);
+    
+    /* If the object is shared, duplicate it before writing to it */
+    if (Tcl_IsShared(listPtr->listObj)) {
+        newListObj = Tcl_DuplicateObj(listPtr->listObj);
+    } else {
+        newListObj = listPtr->listObj;
+    }
+
+    /* insert the specified element (first arg to command) */
+    result = Tcl_ListObjReplace(listPtr->interp, newListObj,
+        index, 0, 1, objv);
+
+    if (result != TCL_OK) {
+	return result;
+    }
+
+    /*
+     * Replace the current object and set attached listvar, if any.
+     * This may error if listvar points to a var in a deleted namespace, but
+     * we ignore those errors.  If the namespace is recreated, it will
+     * auto-sync with the current value. [Bug 1424513]
+     */
+    Tcl_IncrRefCount(newListObj);
+    Tcl_DecrRefCount(listPtr->listObj);
+    listPtr->listObj = newListObj;
+    listPtr->nElements++;
+
+    /*
+     * Update the "special" indices (anchor, topIndex, active) to account
+     * for the renumbering that just occurred.  Then arrange for the new
+     * information to be displayed.
+     */
+    listPtr->active = listPtr->nElements-1;
+    if (index <= listPtr->selectAnchor) {
+	listPtr->selectAnchor += 1;
+    }
+    if (index < listPtr->topIndex) {
+	listPtr->topIndex += 1;
+    }
+    if (index <= listPtr->active) {
+	listPtr->active += 1;
+	if ((listPtr->active >= listPtr->nElements) &&
+		(listPtr->nElements > 0)) {
+	    listPtr->active = listPtr->nElements-1;
+	}
+    }
+    listPtr->flags |= UPDATE_V_SCROLLBAR | UPDATE_H_SCROLLBAR;
+    RpListboxComputeGeometry(listPtr, 0, 1, 0);
+    RpEventuallyRedrawRange(listPtr, index, listPtr->nElements-1);
+
+    /* apply any additional options as configuration options */
+    if (objc > 1) {
+        attrPtr = RpListboxGetItemAttributes(listPtr->interp, listPtr, index);
+        result = RpConfigureListboxItem(listPtr->interp, listPtr, attrPtr,
+            objc-1, objv+1, index);
+
+        if (result != TCL_OK) {
+	    return result;
+        }
+    }
+
+    /* return the index of the new item */
+    Tcl_SetObjResult(listPtr->interp, Tcl_NewIntObj(index));
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxDeleteSubCmd --
+ *
+ *	Process a listbox "delete" subcommand by removing one or more
+ *      elements from a listbox widget.
+ *
+ * Results:
+ *	Standard Tcl result.
+ *
+ * Side effects:
+ *	The listbox will be modified and (eventually) redisplayed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxDeleteSubCmd(listPtr, first, last)
+    register Listbox *listPtr;	/* Listbox widget to modify. */
+    int first;			/* Index of first element to delete. */
+    int last;			/* Index of last element to delete. */
+{
+    int count, i, pageLines;
+    Tcl_Obj *newListObj;
+    int result;
+    Tcl_HashEntry *entry;
+    
+    /*
+     * Adjust the range to fit within the existing elements of the
+     * listbox, and make sure there's something to delete.
+     */
+
+    if (first < 0) {
+	first = 0;
+    }
+    if (last >= listPtr->nElements) {
+	last = listPtr->nElements-1;
+    }
+    count = last + 1 - first;
+    if (count <= 0) {
+	return TCL_OK;
+    }
+
+    /*
+     * Foreach deleted index we must:
+     * a) remove selection information
+     * b) clean up attributes
+     */
+    for (i = first; i <= last; i++) {
+	/* Remove selection information */
+	entry = Tcl_FindHashEntry(listPtr->selection, (char *)i);
+	if (entry != NULL) {
+	    listPtr->numSelected--;
+	    Tcl_DeleteHashEntry(entry);
+	}
+
+	entry = Tcl_FindHashEntry(listPtr->itemAttrTable, (char *)i);
+	if (entry != NULL) {
+	    ckfree((char *)Tcl_GetHashValue(entry));
+	    Tcl_DeleteHashEntry(entry);
+	}
+    }
+
+    /* Adjust selection and attribute info for indices after lastIndex */
+    RpMigrateHashEntries(listPtr->selection, last+1,
+	    listPtr->nElements-1, count*-1);
+    RpMigrateHashEntries(listPtr->itemAttrTable, last+1,
+	    listPtr->nElements-1, count*-1);
+
+    /* Delete the requested elements */
+    if (Tcl_IsShared(listPtr->listObj)) {
+	newListObj = Tcl_DuplicateObj(listPtr->listObj);
+    } else {
+	newListObj = listPtr->listObj;
+    }
+    result = Tcl_ListObjReplace(listPtr->interp,
+	    newListObj, first, count, 0, NULL);
+    if (result != TCL_OK) {
+	return result;
+    }
+
+    /*
+     * Replace the current object and set attached listvar, if any.
+     * This may error if listvar points to a var in a deleted namespace, but
+     * we ignore those errors.  If the namespace is recreated, it will
+     * auto-sync with the current value. [Bug 1424513]
+     */
+
+    Tcl_IncrRefCount(newListObj);
+    Tcl_DecrRefCount(listPtr->listObj);
+    listPtr->listObj = newListObj;
+
+    /* Get the new list length */
+    Tcl_ListObjLength(listPtr->interp, listPtr->listObj, &listPtr->nElements);
+
+    /*
+     * Update the selection and viewing information to reflect the change
+     * in the element numbering, and redisplay to slide information up over
+     * the elements that were deleted.
+     */
+
+    if (first <= listPtr->selectAnchor) {
+	listPtr->selectAnchor -= count;
+	if (listPtr->selectAnchor < first) {
+	    listPtr->selectAnchor = first;
+	}
+    }
+    if (first <= listPtr->topIndex) {
+	listPtr->topIndex -= count;
+	if (listPtr->topIndex < first) {
+	    listPtr->topIndex = first;
+	}
+    }
+    if (listPtr->orient == ORIENT_VERTICAL) {
+        pageLines = Tk_Height(listPtr->tkwin)/listPtr->lineHeight;
+        if (listPtr->topIndex > (listPtr->nElements - pageLines)) {
+	    listPtr->topIndex = listPtr->nElements - pageLines;
+	    if (listPtr->topIndex < 0) {
+	        listPtr->topIndex = 0;
+	    }
+        }
+    }
+    if (listPtr->active > last) {
+	listPtr->active -= count;
+    } else if (listPtr->active >= first) {
+	listPtr->active = first;
+	if ((listPtr->active >= listPtr->nElements) &&
+		(listPtr->nElements > 0)) {
+	    listPtr->active = listPtr->nElements-1;
+	}
+    }
+    listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
+    RpListboxComputeGeometry(listPtr, 0, 1, 0);
+    RpEventuallyRedrawRange(listPtr, first, listPtr->nElements-1);
+    return TCL_OK;
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RpListboxEventProc --
+ *
+ *	This procedure is invoked by the Tk dispatcher for various
+ *	events on listboxes.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	When the window gets deleted, internal structures get
+ *	cleaned up.  When it gets exposed, it is redisplayed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+RpListboxEventProc(clientData, eventPtr)
+    ClientData clientData;	/* Information about window. */
+    XEvent *eventPtr;		/* Information about event. */
+{
+    Listbox *listPtr = (Listbox *) clientData;
+    int height;
+    
+    if (eventPtr->type == Expose) {
+	RpEventuallyRedrawRange(listPtr,
+		RpNearestListboxElement(listPtr,
+                    eventPtr->xexpose.x, eventPtr->xexpose.y),
+		RpNearestListboxElement(listPtr,
+                    eventPtr->xexpose.x + eventPtr->xexpose.width,
+                    eventPtr->xexpose.y + eventPtr->xexpose.height));
+    } else if (eventPtr->type == DestroyNotify) {
+	if (!(listPtr->flags & LISTBOX_DELETED)) {
+	    listPtr->flags |= LISTBOX_DELETED;
+	    Tcl_DeleteCommandFromToken(listPtr->interp, listPtr->widgetCmd);
+	    if (listPtr->setGrid) {
+		Tk_UnsetGrid(listPtr->tkwin);
+	    }
+	    if (listPtr->flags & REDRAW_PENDING) {
+		Tcl_CancelIdleCall(RpDisplayListbox, clientData);
+	    }
+	    Tcl_EventuallyFree(clientData, RpDestroyListbox);
+	}
+    } else if (eventPtr->type == ConfigureNotify) {
+        if (listPtr->orient == ORIENT_HORIZONTAL) {
+            /* size can change layout in horizontal mode */
+            RpListboxComputeGeometry(listPtr, 0, 1, 0);
+        } else {
+            height = listPtr->nElements*listPtr->lineHeight
+                       - Tk_Height(listPtr->tkwin) + 2*listPtr->inset;
+            if (height < 0) {
+                height = 0;
+            }
+            if (listPtr->yOffset > height) {
+                listPtr->yOffset = height;
+            }
+        }
+	listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
+	RpChangeListboxView(listPtr, listPtr->topIndex);
+	RpChangeListboxXOffset(listPtr, listPtr->xOffset);
+
+	/*
+	 * Redraw the whole listbox.  It's hard to tell what needs
+	 * to be redrawn (e.g. if the listbox has shrunk then we
+	 * may only need to redraw the borders), so just redraw
+	 * everything for safety.
+	 */
+
+	RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+    } else if (eventPtr->type == FocusIn) {
+	if (eventPtr->xfocus.detail != NotifyInferior) {
+	    listPtr->flags |= GOT_FOCUS;
+	    RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+	}
+    } else if (eventPtr->type == FocusOut) {
+	if (eventPtr->xfocus.detail != NotifyInferior) {
+	    listPtr->flags &= ~GOT_FOCUS;
+	    RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+	}
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxCmdDeletedProc --
+ *
+ *	This procedure is invoked when a widget command is deleted.  If
+ *	the widget isn't already in the process of being destroyed,
+ *	this command destroys it.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	The widget is destroyed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxCmdDeletedProc(clientData)
+    ClientData clientData;	/* Pointer to widget record for widget. */
+{
+    Listbox *listPtr = (Listbox *) clientData;
+
+    /*
+     * This procedure could be invoked either because the window was
+     * destroyed and the command was then deleted (in which case tkwin
+     * is NULL) or because the command was deleted, and then this procedure
+     * destroys the widget.
+     */
+
+    if (!(listPtr->flags & LISTBOX_DELETED)) {
+	Tk_DestroyWindow(listPtr->tkwin);
+    }
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RpGetListboxIndex --
+ *
+ *	Parse an index into a listbox and return either its value
+ *	or an error.
+ *
+ * Results:
+ *	A standard Tcl result.  If all went well, then *indexPtr is
+ *	filled in with the index (into listPtr) corresponding to
+ *	string.  Otherwise an error message is left in the interp's result.
+ *
+ * Side effects:
+ *	None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+RpGetListboxIndex(interp, listPtr, indexObj, endIsSize, indexPtr)
+    Tcl_Interp *interp;		/* For error messages. */
+    Listbox *listPtr;		/* Listbox for which the index is being
+				 * specified. */
+    Tcl_Obj *indexObj;		/* Specifies an element in the listbox. */
+    int endIsSize;		/* If 1, "end" refers to the number of
+				 * entries in the listbox.  If 0, "end"
+				 * refers to 1 less than the number of
+				 * entries. */
+    int *indexPtr;		/* Where to store converted index. */
+{
+    int result;
+    int index;
+    char *stringRep;
+    
+    /* First see if the index is one of the named indices */
+    result = Tcl_GetIndexFromObj(NULL, indexObj, indexNames, "", 0, &index);
+    if (result == TCL_OK) {
+	switch (index) {
+	    case INDEX_ACTIVE: {
+		/* "active" index */
+		*indexPtr = listPtr->active;
+		break;
+	    }
+
+	    case INDEX_ANCHOR: {
+		/* "anchor" index */
+		*indexPtr = listPtr->selectAnchor;
+		break;
+	    }
+
+	    case INDEX_END: {
+		/* "end" index */
+		if (endIsSize) {
+		    *indexPtr = listPtr->nElements;
+		} else {
+		    *indexPtr = listPtr->nElements - 1;
+		}
+		break;
+	    }
+	}
+	return TCL_OK;
+    }
+
+    /* The index didn't match any of the named indices; maybe it's an @x,y */
+    stringRep = Tcl_GetString(indexObj);
+    if (stringRep[0] == '@') {
+	/* @x,y index */
+	int x, y;
+	char *start, *end;
+	start = stringRep + 1;
+	x = strtol(start, &end, 0);
+	if ((start == end) || (*end != ',')) {
+	    Tcl_AppendResult(interp, "bad listbox index \"", stringRep,
+		    "\": must be active, anchor, end, @x,y, or a number",
+		    (char *)NULL);
+	    return TCL_ERROR;
+	}
+	start = end+1;
+	y = strtol(start, &end, 0);
+	if ((start == end) || (*end != '\0')) {
+	    Tcl_AppendResult(interp, "bad listbox index \"", stringRep,
+		    "\": must be active, anchor, end, @x,y, or a number",
+		    (char *)NULL);
+	    return TCL_ERROR;
+	}
+	*indexPtr = RpNearestListboxElement(listPtr, x, y);
+	return TCL_OK;
+    }
+    
+    /* Maybe the index is just an integer */
+    if (Tcl_GetIntFromObj(interp, indexObj, indexPtr) == TCL_OK) {
+	return TCL_OK;
+    }
+
+    /* Everything failed, nothing matched.  Throw up an error message */
+    Tcl_ResetResult(interp);
+    Tcl_AppendResult(interp, "bad listbox index \"",
+	    Tcl_GetString(indexObj), "\": must be active, anchor, ",
+	    "end, @x,y, or a number", (char *) NULL);
+    return TCL_ERROR;
+}
+
+
+/*
+ *--------------------------------------------------------------
+ *
+ * RpGetListboxPos --
+ *
+ *	Computes the row/col index for the given listbox element.
+ *
+ * Results:
+ *	Returns the row/col values in the pointers provided.
+ *
+ * Side effects:
+ *	Computes geometry/layout if it is stale.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+RpGetListboxPos(listPtr, index, rowPtr, colPtr)
+    Listbox *listPtr;		/* Listbox for which the index is being
+				 * specified. */
+    int index;			/* Specifies an element in the listbox. */
+    int *rowPtr;		/* Returns: row in listbox layout */
+    int *colPtr;		/* Returns: col in listbox layout */
+{
+    if (listPtr->flags & GEOMETRY_IS_STALE) {
+        RpListboxComputeGeometry(listPtr, 0, 1, 0);
+        listPtr->flags &= ~GEOMETRY_IS_STALE;
+    }
+
+    if (listPtr->elemsPerColumn == 0) {
+        *rowPtr = *colPtr = 0;
+    } else {
+        *colPtr = index / listPtr->elemsPerColumn;
+        *rowPtr = index % listPtr->elemsPerColumn;
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpChangeListboxView --
+ *
+ *	Change the view on a listbox widget so that a given element
+ *	is displayed at the top/left.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	What's displayed on the screen is changed.  If there is a
+ *	scrollbar associated with this widget, then the scrollbar
+ *	is instructed to change its display too.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpChangeListboxView(listPtr, index)
+    register Listbox *listPtr;		/* Information about widget. */
+    int index;				/* Index of element in listPtr
+					 * that should now appear at the
+					 * top of the listbox. */
+{
+    int nrow, ncol, xOffset, yOffset;
+
+    /* keep the view in bounds */
+    if (index > listPtr->nElements) {
+        index = listPtr->nElements;
+    }
+    if (index < 0) {
+        index = 0;
+    }
+
+    RpGetListboxPos(listPtr, index, &nrow, &ncol);
+
+    xOffset = (ncol > 0) ? listPtr->xColumnMax[ncol-1] : 0;
+
+    if (listPtr->orient == ORIENT_VERTICAL) {
+        yOffset = nrow*listPtr->lineHeight;
+    } else {
+        yOffset = 0;
+    }
+
+    RpChangeListboxXOffset(listPtr, xOffset);
+    RpChangeListboxYOffset(listPtr, yOffset);
+
+    /* topIndex is top of column */
+    listPtr->topIndex = ncol*listPtr->elemsPerColumn;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpChangeListboxXOffset --
+ *
+ *	Change the horizontal offset for a listbox.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	The listbox may be redrawn to reflect its new horizontal
+ *	offset.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpChangeListboxXOffset(listPtr, offset)
+    register Listbox *listPtr;		/* Information about widget. */
+    int offset;				/* Desired new "xOffset" for
+					 * listbox. */
+{
+    int windowWidth;
+    
+    windowWidth = Tk_Width(listPtr->tkwin) - 2*listPtr->inset - 10;
+    if (offset > listPtr->maxWidth-windowWidth) {
+	offset = listPtr->maxWidth-windowWidth;
+    }
+    if (offset < 0) {
+	offset = 0;
+    }
+    if (offset != listPtr->xOffset) {
+	listPtr->xOffset = offset;
+	listPtr->flags |= UPDATE_H_SCROLLBAR;
+	RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpChangeListboxYOffset --
+ *
+ *	Change the vertical offset for a listbox.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	The listbox may be redrawn to reflect its new vertical
+ *	offset.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpChangeListboxYOffset(listPtr, offset)
+    register Listbox *listPtr;		/* Information about widget. */
+    int offset;				/* Desired new "yOffset" for
+					 * listbox. */
+{
+    int windowHeight;
+    
+    windowHeight = Tk_Height(listPtr->tkwin) - 2*listPtr->inset;
+    if (offset > listPtr->elemsPerColumn*listPtr->lineHeight - windowHeight) {
+	offset = listPtr->elemsPerColumn*listPtr->lineHeight - windowHeight;
+    }
+    if (offset < 0) {
+	offset = 0;
+    }
+    if (offset != listPtr->yOffset) {
+	listPtr->yOffset = offset;
+	listPtr->flags |= UPDATE_V_SCROLLBAR;
+	RpEventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
+    }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxScanTo --
+ *
+ *	Given a point (presumably of the curent mouse location)
+ *	drag the view in the window to implement the scan operation.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	The view in the window may change.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxScanTo(listPtr, x, y)
+    register Listbox *listPtr;		/* Information about widget. */
+    int x;				/* X-coordinate to use for scan
+					 * operation. */
+    int y;				/* Y-coordinate to use for scan
+					 * operation. */
+{
+    int ncol, newx, newy, maxx, maxy;
+
+    ncol = listPtr->numColumns;
+    maxx = (ncol > 0) ? listPtr->xColumnMax[ncol] : listPtr->xColumnMax[0];
+    maxy = listPtr->elemsPerColumn*listPtr->lineHeight;
+
+    /*
+     * Compute new top line for screen by amplifying the difference
+     * between the current position and the place where the scan
+     * started (the "mark" position).  If we run off the top or bottom
+     * of the list, then reset the mark point so that the current
+     * position continues to correspond to the edge of the window.
+     * This means that the picture will start dragging as soon as the
+     * mouse reverses direction (without this reset, might have to slide
+     * mouse a long ways back before the picture starts moving again).
+     */
+    newx = listPtr->scanMarkXOffset - (10*(x - listPtr->scanMarkX));
+    if (newx > maxx) {
+	newx = listPtr->scanMarkXOffset = maxx;
+	listPtr->scanMarkX = x;
+    } else if (newx < 0) {
+	newx = listPtr->scanMarkXOffset = 0;
+	listPtr->scanMarkX = x;
+    }
+    RpChangeListboxXOffset(listPtr, newx);
+
+    newy = listPtr->scanMarkYOffset - (10*(y - listPtr->scanMarkY));
+    if (newy > maxy) {
+	newy = listPtr->scanMarkYOffset = maxy;
+	listPtr->scanMarkY = y;
+    } else if (newy < 0) {
+	newy = listPtr->scanMarkYOffset = 0;
+	listPtr->scanMarkY = y;
+    }
+    RpChangeListboxYOffset(listPtr, newy);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpNearestListboxElement --
+ *
+ *	Given an (x,y) coordinate inside a listbox, compute the index
+ *	of the element under that point or closest to it.
+ *
+ * Results:
+ *	The return value is an index of an element of listPtr.  If
+ *	listPtr has no elements, then 0 is always returned.
+ *
+ * Side effects:
+ *	None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpNearestListboxElement(listPtr, x, y)
+    register Listbox *listPtr;		/* Information about widget. */
+    int x;				/* X-coordinate in listPtr's window. */
+    int y;				/* Y-coordinate in listPtr's window. */
+{
+    int index, ncol, x0, x1, colIndex;
+
+    /* need to update geometry/layout to figure out where point sits */
+    if (listPtr->flags & GEOMETRY_IS_STALE) {
+        RpListboxComputeGeometry(listPtr, 1, 1, 1);
+        listPtr->flags &= ~GEOMETRY_IS_STALE;
+        listPtr->flags |= UPDATE_H_SCROLLBAR;
+    }
+
+    /* if we're scrolled over, adjust the x/y coordinates */
+    x += listPtr->xOffset;
+    y += listPtr->yOffset;
+
+    /* find the column containing this point */
+    if (x > listPtr->maxWidth) {
+        ncol = listPtr->numColumns-1;
+    } else {
+        for (ncol=0; ncol < listPtr->numColumns; ncol++) {
+            x0 = (ncol > 0) ? listPtr->xColumnMax[ncol-1]+1 : 0;
+            x1 = listPtr->xColumnMax[ncol];
+
+            if (x >= x0 && x <= x1) {
+                break;
+            }
+        }
+    }
+    colIndex = ncol*listPtr->elemsPerColumn;
+
+    index = (y - listPtr->inset)/listPtr->lineHeight;
+    if (index >= listPtr->elemsPerColumn) {
+	index = listPtr->elemsPerColumn;
+    }
+    if (index < 0) {
+	index = 0;
+    }
+    index += colIndex;
+    if (index >= listPtr->nElements) {
+	index = listPtr->nElements-1;
+    }
+    return index;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxSelect --
+ *
+ *	Select or deselect one or more elements in a listbox..
+ *
+ * Results:
+ *	Standard Tcl result.
+ *
+ * Side effects:
+ *	All of the elements in the range between first and last are
+ *	marked as either selected or deselected, depending on the
+ *	"select" argument.  Any items whose state changes are redisplayed.
+ *	The selection is claimed from X when the number of selected
+ *	elements changes from zero to non-zero.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxSelect(listPtr, first, last, select)
+    register Listbox *listPtr;		/* Information about widget. */
+    int first;				/* Index of first element to
+					 * select or deselect. */
+    int last;				/* Index of last element to
+					 * select or deselect. */
+    int select;				/* 1 means select items, 0 means
+					 * deselect them. */
+{
+    int i, firstRedisplay, oldCount;
+    Tcl_HashEntry *entry;
+    int new;
+    
+    if (last < first) {
+	i = first;
+	first = last;
+	last = i;
+    }
+    if ((last < 0) || (first >= listPtr->nElements)) {
+	return TCL_OK;
+    }
+    if (first < 0) {
+	first = 0;
+    }
+    if (last >= listPtr->nElements) {
+	last = listPtr->nElements - 1;
+    }
+    oldCount = listPtr->numSelected;
+    firstRedisplay = -1;
+
+    /*
+     * For each index in the range, find it in our selection hash table.
+     * If it's not there but should be, add it.  If it's there but shouldn't
+     * be, remove it.
+     */
+    for (i = first; i <= last; i++) {
+	entry = Tcl_FindHashEntry(listPtr->selection, (char *)i);
+	if (entry != NULL) {
+	    if (!select) {
+		Tcl_DeleteHashEntry(entry);
+		listPtr->numSelected--;
+		if (firstRedisplay < 0) {
+		    firstRedisplay = i;
+		}
+	    }
+	} else {
+	    if (select) {
+		entry = Tcl_CreateHashEntry(listPtr->selection,
+			(char *)i, &new);
+		Tcl_SetHashValue(entry, (ClientData) NULL);
+		listPtr->numSelected++;
+		if (firstRedisplay < 0) {
+		    firstRedisplay = i;
+		}
+	    }
+	}
+    }
+
+    if (firstRedisplay >= 0) {
+	RpEventuallyRedrawRange(listPtr, first, last);
+    }
+    if ((oldCount == 0) && (listPtr->numSelected > 0)
+	    && (listPtr->exportSelection)) {
+	Tk_OwnSelection(listPtr->tkwin, XA_PRIMARY, RpListboxLostSelection,
+		(ClientData) listPtr);
+    }
+    return TCL_OK;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxFetchSelection --
+ *
+ *	This procedure is called back by Tk when the selection is
+ *	requested by someone.  It returns part or all of the selection
+ *	in a buffer provided by the caller.
+ *
+ * Results:
+ *	The return value is the number of non-NULL bytes stored
+ *	at buffer.  Buffer is filled (or partially filled) with a
+ *	NULL-terminated string containing part or all of the selection,
+ *	as given by offset and maxBytes.  The selection is returned
+ *	as a Tcl list with one list element for each element in the
+ *	listbox.
+ *
+ * Side effects:
+ *	None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+RpListboxFetchSelection(clientData, offset, buffer, maxBytes)
+    ClientData clientData;		/* Information about listbox widget. */
+    int offset;				/* Offset within selection of first
+					 * byte to be returned. */
+    char *buffer;			/* Location in which to place
+					 * selection. */
+    int maxBytes;			/* Maximum number of bytes to place
+					 * at buffer, not including terminating
+					 * NULL character. */
+{
+    register Listbox *listPtr = (Listbox *) clientData;
+    Tcl_DString selection;
+    int length, count, needNewline;
+    Tcl_Obj *curElement;
+    char *stringRep;
+    int stringLen;
+    Tcl_HashEntry *entry;
+    int i;
+    
+    if (!listPtr->exportSelection) {
+	return -1;
+    }
+
+    /*
+     * Use a dynamic string to accumulate the contents of the selection.
+     */
+
+    needNewline = 0;
+    Tcl_DStringInit(&selection);
+    for (i = 0; i < listPtr->nElements; i++) {
+	entry = Tcl_FindHashEntry(listPtr->selection, (char *)i);
+	if (entry != NULL) {
+	    if (needNewline) {
+		Tcl_DStringAppend(&selection, "\n", 1);
+	    }
+	    Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i,
+		    &curElement);
+	    stringRep = Tcl_GetStringFromObj(curElement, &stringLen);
+	    Tcl_DStringAppend(&selection, stringRep, stringLen);
+	    needNewline = 1;
+	}
+    }
+
+    length = Tcl_DStringLength(&selection);
+    if (length == 0) {
+	return -1;
+    }
+
+    /*
+     * Copy the requested portion of the selection to the buffer.
+     */
+
+    count = length - offset;
+    if (count <= 0) {
+	count = 0;
+    } else {
+	if (count > maxBytes) {
+	    count = maxBytes;
+	}
+	memcpy((VOID *) buffer,
+		(VOID *) (Tcl_DStringValue(&selection) + offset),
+		(size_t) count);
+    }
+    buffer[count] = '\0';
+    Tcl_DStringFree(&selection);
+    return count;
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxLostSelection --
+ *
+ *	This procedure is called back by Tk when the selection is
+ *	grabbed away from a listbox widget.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	The existing selection is unhighlighted, and the window is
+ *	marked as not containing a selection.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxLostSelection(clientData)
+    ClientData clientData;		/* Information about listbox widget. */
+{
+    register Listbox *listPtr = (Listbox *) clientData;
+    
+    if ((listPtr->exportSelection) && (listPtr->nElements > 0)) {
+	RpListboxSelect(listPtr, 0, listPtr->nElements-1, 0);
+    }
+}
+
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpEventuallyRedrawRange --
+ *
+ *	Ensure that a given range of elements is eventually redrawn on
+ *	the display (if those elements in fact appear on the display).
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Information gets redisplayed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpEventuallyRedrawRange(listPtr, first, last)
+    register Listbox *listPtr;		/* Information about widget. */
+    int first;				/* Index of first element in list
+					 * that needs to be redrawn. */
+    int last;				/* Index of last element in list
+					 * that needs to be redrawn.  May
+					 * be less than first;
+					 * these just bracket a range. */
+{
+    /* We don't have to register a redraw callback if one is already pending,
+     * or if the window doesn't exist, or if the window isn't mapped */
+    if ((listPtr->flags & REDRAW_PENDING)
+	    || (listPtr->flags & LISTBOX_DELETED)
+	    || !Tk_IsMapped(listPtr->tkwin)) {
+	return;
+    }
+    listPtr->flags |= REDRAW_PENDING;
+    Tcl_DoWhenIdle(RpDisplayListbox, (ClientData) listPtr);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxUpdateVScrollbar --
+ *
+ *	This procedure is invoked whenever information has changed in
+ *	a listbox in a way that would invalidate a vertical scrollbar
+ *	display.  If there is an associated scrollbar, then this command
+ *	updates it by invoking a Tcl command.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	A Tcl command is invoked, and an additional command may be
+ *	invoked to process errors in the command.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxUpdateVScrollbar(listPtr)
+    register Listbox *listPtr;		/* Information about widget. */
+{
+    char string[TCL_DOUBLE_SPACE * 2];
+    double first, last, worldHeight;
+    int result;
+    Tcl_Interp *interp;
+    
+    if (listPtr->yScrollCmd == NULL) {
+	return;
+    }
+    if (listPtr->nElements == 0) {
+	first = 0.0;
+	last = 1.0;
+    } else {
+        /* need to update geometry/layout to figure out scrollbar bubble */
+        if (listPtr->flags & GEOMETRY_IS_STALE) {
+            RpListboxComputeGeometry(listPtr, 0, 1, 0);
+            listPtr->flags &= ~GEOMETRY_IS_STALE;
+        }
+        worldHeight = listPtr->elemsPerColumn*listPtr->lineHeight;
+
+	first = listPtr->yOffset/((double)worldHeight);
+	last  = (listPtr->yOffset+Tk_Height(listPtr->tkwin))
+                  / ((double)worldHeight);
+	if (last > 1.0) {
+	    last = 1.0;
+	}
+    }
+    sprintf(string, " %g %g", first, last);
+
+    /*
+     * We must hold onto the interpreter from the listPtr because the data
+     * at listPtr might be freed as a result of the Tcl_VarEval.
+     */
+    
+    interp = listPtr->interp;
+    Tcl_Preserve((ClientData) interp);
+    result = Tcl_VarEval(interp, listPtr->yScrollCmd, string,
+	    (char *) NULL);
+    if (result != TCL_OK) {
+	Tcl_AddErrorInfo(interp,
+		"\n    (vertical scrolling command executed by listbox)");
+	Tcl_BackgroundError(interp);
+    }
+    Tcl_Release((ClientData) interp);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpListboxUpdateHScrollbar --
+ *
+ *	This procedure is invoked whenever information has changed in
+ *	a listbox in a way that would invalidate a horizontal scrollbar
+ *	display.  If there is an associated horizontal scrollbar, then
+ *	this command updates it by invoking a Tcl command.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	A Tcl command is invoked, and an additional command may be
+ *	invoked to process errors in the command.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpListboxUpdateHScrollbar(listPtr)
+    register Listbox *listPtr;		/* Information about widget. */
+{
+    char string[TCL_DOUBLE_SPACE * 2];
+    int result, windowWidth;
+    double first, last;
+    Tcl_Interp *interp;
+
+    if (listPtr->xScrollCmd == NULL) {
+	return;
+    }
+    windowWidth = Tk_Width(listPtr->tkwin) - 2*listPtr->inset;
+
+    if (listPtr->maxWidth < 5) {
+	first = 0;
+	last = 1.0;
+    } else {
+	first = listPtr->xOffset/((double)listPtr->maxWidth);
+	last = (listPtr->xOffset + windowWidth)
+		/((double)listPtr->maxWidth);
+	if (last > 1.0) {
+	    last = 1.0;
+	}
+    }
+    sprintf(string, " %g %g", first, last);
+
+    /*
+     * We must hold onto the interpreter because the data referred to at
+     * listPtr might be freed as a result of the call to Tcl_VarEval.
+     */
+    
+    interp = listPtr->interp;
+    Tcl_Preserve((ClientData) interp);
+    result = Tcl_VarEval(interp, listPtr->xScrollCmd, string,
+	    (char *) NULL);
+    if (result != TCL_OK) {
+	Tcl_AddErrorInfo(interp,
+		"\n    (horizontal scrolling command executed by listbox)");
+	Tcl_BackgroundError(interp);
+    }
+    Tcl_Release((ClientData) interp);
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RpMigrateHashEntries --
+ *
+ *	Given a hash table with entries keyed by a single integer value,
+ *	move all entries in a given range by a fixed amount, so that
+ *	if in the original table there was an entry with key n and
+ *	the offset was i, in the new table that entry would have key n + i.
+ *
+ * Results:
+ *	None.
+ *
+ * Side effects:
+ *	Rekeys some hash table entries.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RpMigrateHashEntries(table, first, last, offset)
+    Tcl_HashTable *table;
+    int first;
+    int last;
+    int offset;
+{
+    int i, new;
+    Tcl_HashEntry *entry;
+    ClientData clientData;
+
+    if (offset == 0) {
+	return;
+    }
+    /* It's more efficient to do one if/else and nest the for loops inside,
+     * although we could avoid some code duplication if we nested the if/else
+     * inside the for loops */
+    if (offset > 0) {
+	for (i = last; i >= first; i--) {
+	    entry = Tcl_FindHashEntry(table, (char *)i);
+	    if (entry != NULL) {
+		clientData = Tcl_GetHashValue(entry);
+		Tcl_DeleteHashEntry(entry);
+		entry = Tcl_CreateHashEntry(table, (char *)(i + offset), &new);
+		Tcl_SetHashValue(entry, clientData);
+	    }
+	}
+    } else {
+	for (i = first; i <= last; i++) {
+	    entry = Tcl_FindHashEntry(table, (char *)i);
+	    if (entry != NULL) {
+		clientData = Tcl_GetHashValue(entry);
+		Tcl_DeleteHashEntry(entry);
+		entry = Tcl_CreateHashEntry(table, (char *)(i + offset), &new);
+		Tcl_SetHashValue(entry, clientData);
+	    }
+	}
+    }
+    return;
+}
+
+
Index: /branches/r9/packages/PdbToVtk/pdbToVtk.c
===================================================================
--- /branches/r9/packages/PdbToVtk/pdbToVtk.c	(revision 4841)
+++ /branches/r9/packages/PdbToVtk/pdbToVtk.c	(revision 4841)
@@ -0,0 +1,742 @@
+
+/* 
+ * ----------------------------------------------------------------------
+ *  RpPdbToVtk - 
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  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 <stdlib.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+#include <float.h>
+#include "tcl.h"
+
+#define BOND_NONE	(0)
+#define BOND_CONECT	(1<<0)
+#define BOND_COMPUTE	(1<<1)
+#define BOND_BOTH	(BOND_CONECT|BOND_COMPUTE)
+
+typedef struct {
+    int from, to;
+} ConnectKey;
+
+typedef struct {
+    double x, y, z;			/* Coordinates of atom. */
+    int number;				/* Atomic number */
+    int ordinal;			/* Index of the atom in VTK. */
+    int numConnections;			/*  */
+} PdbAtom;
+
+typedef struct {
+    const char *symbol;			/* Atom symbol. */
+    float radius;			/* Covalent radius of atom. */
+} PeriodicElement;
+
+static PeriodicElement elements[] = {
+    {    "X",	    0.1f,	},	/* 0 */
+    {    "H",	    0.32f,	},	/* 1 */		
+    {    "He", 	    0.93f,	},	/* 2 */		
+    {    "Li", 	    1.23f,	},	/* 3 */		
+    {    "Be", 	    0.90f,	},	/* 4 */		
+    {    "B", 	    0.82f,	},	/* 5 */		
+    {    "C", 	    0.77f,	},	/* 6 */		
+    {    "N", 	    0.75f,	},	/* 7 */		
+    {    "O", 	    0.73f,	},	/* 8 */		
+    {    "F", 	    0.72f,	},	/* 9 */		
+    {    "Ne", 	    0.71f, 	},	/* 10 */	
+    {    "Na", 	    1.54f, 	},	/* 11 */	
+    {    "Mg", 	    1.36f, 	},	/* 12 */	
+    {    "Al", 	    1.18f, 	},	/* 13 */	
+    {    "Si", 	    1.11f, 	},	/* 14 */	
+    {    "P", 	    1.06f, 	},	/* 15 */	
+    {    "S", 	    1.02f, 	},	/* 16 */	
+    {    "Cl", 	    0.99f, 	},	/* 17 */	
+    {    "Ar", 	    0.98f, 	},	/* 18 */	
+    {    "K", 	    2.03f, 	},	/* 19 */	
+    {    "Ca", 	    1.74f, 	},	/* 20 */	
+    {    "Sc", 	    1.44f, 	},	/* 21 */	
+    {    "Ti", 	    1.32f, 	},	/* 22 */	
+    {    "V", 	    1.22f, 	},	/* 23 */	
+    {    "Cr", 	    1.18f, 	},	/* 24 */	
+    {    "Mn", 	    1.17f, 	},	/* 25 */	
+    {    "Fe", 	    1.17f, 	},	/* 26 */	
+    {    "Co", 	    1.16f, 	},	/* 27 */	
+    {    "Ni", 	    1.15f, 	},	/* 28 */	
+    {    "Cu", 	    1.17f, 	},	/* 29 */	
+    {    "Zn", 	    1.25f, 	},	/* 30 */	
+    {    "Ga", 	    1.26f, 	},	/* 31 */	
+    {    "Ge", 	    1.22f, 	},	/* 32 */	
+    {    "As", 	    1.20f, 	},	/* 33 */	
+    {    "Se", 	    1.16f, 	},	/* 34 */	
+    {    "Br", 	    1.14f, 	},	/* 35 */	
+    {    "Kr", 	    1.12f, 	},	/* 36 */	
+    {    "Rb", 	    2.16f, 	},	/* 37 */	
+    {    "Sr", 	    1.91f, 	},	/* 38 */	
+    {    "Y", 	    1.62f, 	},	/* 39 */	
+    {    "Zr", 	    1.45f, 	},	/* 40 */	
+    {    "Nb", 	    1.34f, 	},	/* 41 */	
+    {    "Mo", 	    1.30f, 	},	/* 42 */	
+    {    "Tc", 	    1.27f, 	},	/* 43 */	
+    {    "Ru", 	    1.25f, 	},	/* 44 */	
+    {    "Rh", 	    1.25f, 	},	/* 45 */	
+    {    "Pd", 	    1.28f, 	},	/* 46 */	
+    {    "Ag", 	    1.34f, 	},	/* 47 */	
+    {    "Cd", 	    1.48f, 	},	/* 48 */	
+    {    "In", 	    1.44f, 	},	/* 49 */	
+    {    "Sn", 	    1.41f, 	},	/* 50 */	
+    {    "Sb", 	    1.40f, 	},	/* 51 */	
+    {    "Te", 	    1.36f, 	},	/* 52 */	
+    {    "I", 	    1.33f, 	},	/* 53 */	
+    {    "Xe", 	    1.31f, 	},	/* 54 */	
+    {    "Cs", 	    2.35f, 	},	/* 55 */	
+    {    "Ba", 	    1.98f, 	},	/* 56 */	
+    {    "La", 	    1.69f, 	},	/* 57 */	
+    {    "Ce", 	    1.65f, 	},	/* 58 */	
+    {    "Pr", 	    1.65f, 	},	/* 59 */	
+    {    "Nd", 	    1.64f, 	},	/* 60 */	
+    {    "Pm", 	    1.63f, 	},	/* 61 */	
+    {    "Sm", 	    1.62f, 	},	/* 62 */	
+    {    "Eu", 	    1.85f, 	},	/* 63 */	
+    {    "Gd", 	    1.61f, 	},	/* 64 */	
+    {    "Tb", 	    1.59f, 	},	/* 65 */	
+    {    "Dy", 	    1.59f, 	},	/* 66 */	
+    {    "Ho", 	    1.58f, 	},	/* 67 */	
+    {    "Er", 	    1.57f, 	},	/* 68 */	
+    {    "Tm", 	    1.56f, 	},	/* 69 */	
+    {    "Yb", 	    1.74f, 	},	/* 70 */	
+    {    "Lu", 	    1.56f, 	},	/* 71 */	
+    {    "Hf", 	    1.44f, 	},	/* 72 */	
+    {    "Ta", 	    1.34f, 	},	/* 73 */	
+    {    "W", 	    1.30f, 	},	/* 74 */	
+    {    "Re", 	    1.28f, 	},	/* 75 */	
+    {    "Os", 	    1.26f, 	},	/* 76 */	
+    {    "Ir", 	    1.27f, 	},	/* 77 */	
+    {    "Pt", 	    1.30f, 	},	/* 78 */	
+    {    "Au", 	    1.34f, 	},	/* 79 */	
+    {    "Hg", 	    1.49f, 	},	/* 80 */	
+    {    "Tl", 	    1.48f, 	},	/* 81 */	
+    {    "Pb", 	    1.47f, 	},	/* 82 */	
+    {    "Bi", 	    1.46f, 	},	/* 83 */	
+    {    "Po", 	    1.46f, 	},	/* 84 */	
+    {    "At", 	    1.45f, 	},	/* 85 */	
+    {    "Rn", 	    1.50f, 	},	/* 86 */	
+    {    "Fr", 	    2.60f, 	},	/* 87 */	
+    {    "Ra", 	    2.21f, 	},	/* 88 */	
+    {    "Ac", 	    2.15f, 	},	/* 89 */	
+    {    "Th", 	    2.06f, 	},	/* 90 */	
+    {    "Pa", 	    2.00f, 	},	/* 91 */	
+    {    "U", 	    1.96f, 	},	/* 92 */	
+    {    "Np", 	    1.90f, 	},	/* 93 */	
+    {    "Pu", 	    1.87f, 	},	/* 94 */	
+    {    "Am", 	    1.80f, 	},	/* 95 */	
+    {    "Cm", 	    1.69f, 	},	/* 96 */	
+    {    "Bk", 	    0.1f, 	},	/* 97 */	
+    {    "Cf", 	    0.1f, 	},	/* 98 */	
+    {    "Es", 	    0.1f, 	},	/* 99 */	
+    {    "Fm", 	    0.1f, 	},	/* 100 */	
+    {    "Md", 	    0.1f, 	},	/* 101 */	
+    {    "No", 	    0.1f, 	},	/* 102 */	
+    {    "Lr", 	    0.1f, 	},	/* 103 */	
+    {    "Rf", 	    0.1f, 	},	/* 104 */	
+    {    "Db", 	    0.1f, 	},	/* 105 */	
+    {    "Sg", 	    0.1f, 	},	/* 106 */	
+    {    "Bh", 	    0.1f, 	},	/* 107 */	
+    {    "Hs", 	    0.1f, 	},	/* 108 */	
+    {    "Mt", 	    0.1f, 	},	/* 109 */	
+    {    "Ds",	    0.1f, 	},	/* 110 */	
+    {    "Rg",	    0.1f, 	},	/* 111 */	
+    {    "Cn",	    0.1f, 	},	/* 112 */	
+    {    "Uut",	    0.1f, 	},	/* 113 */	
+    {    "Fl",	    0.1f, 	},	/* 114 */	
+    {    "Uup",	    0.1f, 	},	/* 115 */	
+    {    "Lv", 	    0.1f, 	},	/* 116 */	
+    {    "Uus",	    0.1f, 	},	/* 117 */	
+    {    "Uuo",     0.1f,	}	/* 118 */	
+};
+
+static int numPeriodicElements = sizeof(elements) / sizeof(PeriodicElement);
+
+static int lineNum = 0;
+
+static const char *
+Itoa(int number) 
+{
+    static char buf[200];
+    sprintf(buf, "%d", number);
+    return buf;
+}
+
+static void
+ComputeBonds(Tcl_HashTable *atomTablePtr, Tcl_HashTable *conectTablePtr)
+{
+    PdbAtom **array;
+    int i;
+    Tcl_HashEntry *hPtr;
+    Tcl_HashSearch iter;
+
+#define TOLERANCE 0.45			/* Fuzz factor for comparing distances
+					 * (in angstroms) */
+    array = calloc(atomTablePtr->numEntries, sizeof(PdbAtom *));
+    if (array == NULL) {
+	return;
+    }
+    for (i = 0, hPtr = Tcl_FirstHashEntry(atomTablePtr, &iter); hPtr != NULL;
+	 hPtr = Tcl_NextHashEntry(&iter), i++) {
+	PdbAtom *atomPtr;
+
+	atomPtr = Tcl_GetHashValue(hPtr);
+	array[i] = atomPtr;
+    }
+    for (i = 0; i < atomTablePtr->numEntries; i++) {
+	PdbAtom *atom1Ptr;
+	double r1;
+	int j;
+
+	atom1Ptr = array[i];
+        r1 = elements[atom1Ptr->number].radius;
+        for (j = i+1; j < atomTablePtr->numEntries; j++) {
+	    PdbAtom *atom2Ptr;
+            double ds2, cut;
+	    double r2;
+
+	    atom2Ptr  = array[j];
+            if ((atom2Ptr->number == 1) && (atom1Ptr->number == 1)) {
+                continue;
+	    }
+	    r2 = elements[atom2Ptr->number].radius;
+	    cut = (r1 + r2 + TOLERANCE);
+	    ds2 = (((atom1Ptr->x - atom2Ptr->x) * (atom1Ptr->x - atom2Ptr->x)) +
+		   ((atom1Ptr->y - atom2Ptr->y) * (atom1Ptr->y - atom2Ptr->y)) +
+		   ((atom1Ptr->z - atom2Ptr->z) * (atom1Ptr->z - atom2Ptr->z)));
+
+	    // perform distance test, but ignore pairs between atoms
+	    // with nearly identical coords
+	    if (ds2 < 0.16) {
+		continue;
+	    }
+	    if (ds2 < (cut*cut)) {
+		ConnectKey key;
+		int isNew;
+
+		if (atom1Ptr->ordinal > atom2Ptr->ordinal) {
+		    key.from = atom2Ptr->ordinal;
+		    key.to = atom1Ptr->ordinal;
+		} else {
+		    key.from = atom1Ptr->ordinal;
+		    key.to = atom2Ptr->ordinal;
+		}
+		Tcl_CreateHashEntry(conectTablePtr, (char *)&key, &isNew);
+		if (isNew) {
+		    atom1Ptr->numConnections++;
+		    atom2Ptr->numConnections++;
+		}
+            }
+        }
+    }
+    free(array);
+}
+
+int 
+GetAtomicNumber(Tcl_Interp *interp, PdbAtom *atomPtr, const char *atomName, 
+		const char *symbolName, const char *residueName)
+{
+    char name[5], *namePtr;
+    int elemIndex, symbolIndex;
+    int count;
+    const char *p;
+
+    symbolIndex = elemIndex = -1;
+
+    /* 
+     * The atomic symbol may be found the atom name in various locations
+     *	"C   "
+     *  " C  "
+     *  "  C "
+     "  "   C"
+     "  "C 12"
+     "  " C12"
+     */
+    count = 0;
+    for (p = atomName; *p != '\0'; p++) {
+	if (isspace(*p) || isdigit(*p)) {
+	    continue;
+	}
+	name[count] = *p;
+	count++;
+    }
+    name[count] = '\0';
+    if (count > 0) {
+	int i;
+
+	namePtr = name;
+	if (name[0] == ' ') {
+	    namePtr++;
+	}
+	if (name[1] == ' ') {
+	    name[1] = '\0';
+	}
+	for (i = 0; i < numPeriodicElements; i++) {
+	    if (strcasecmp(namePtr, elements[i].symbol) == 0) {
+		elemIndex = i;
+		break;
+	    }
+	}
+	if ((elemIndex == -1) && (count > 1)) {
+	    name[1] = '\0';
+	    for (i = 0; i < numPeriodicElements; i++) {
+		if (strcasecmp(namePtr, elements[i].symbol) == 0) {
+		    elemIndex = i;
+		    break;
+		}
+	    }
+	}
+    }
+    name[0] = symbolName[0];
+    name[1] = symbolName[1];
+    name[2] = '\0';
+    if (isdigit(name[1])) {
+	sscanf(name, "%d", &atomPtr->number);
+	return TCL_OK;
+    }
+    if ((name[0] != ' ') || (name[1] != ' ')) {
+	int i;
+
+	namePtr = name;
+	if (name[0] == ' ') {
+	    namePtr++;
+	}
+	if (name[1] == ' ') {
+	    name[1] = '\0';
+	}
+	for (i = 0; i < numPeriodicElements; i++) {
+	    if (strcasecmp(namePtr, elements[i].symbol) == 0) {
+		symbolIndex = i;
+		break;
+	    }
+	}
+    }
+    if (symbolIndex > 0) {
+	if (elemIndex < 0) {
+	    atomPtr->number = symbolIndex;
+	    return TCL_OK;
+	}
+	if (symbolIndex != elemIndex) {
+	    fprintf(stderr, "atomName %s and symbolName %s don't match\n",
+		    atomName, symbolName);
+	    atomPtr->number = symbolIndex;
+	    return TCL_OK;
+	}
+	atomPtr->number = elemIndex;
+	return TCL_OK;
+    } else if (elemIndex > 0) {
+	atomPtr->number = elemIndex;
+	return TCL_OK;
+    }
+    Tcl_AppendResult(interp, "line ", Itoa(lineNum), ": bad atom \"", 
+		     atomName, "\" or element \"", 
+		     symbolName, "\"", (char *)NULL);
+    return TCL_ERROR;
+}
+
+static PdbAtom *
+NewAtom(Tcl_Interp *interp, int ordinal, const char *line, int lineLength)
+{
+    PdbAtom *atomPtr;
+    char buf[200];
+    char atomName[6];			/* Atom name.  */
+    char symbolName[3];			/* Symbol name.  */
+    char residueName[4];		/* Residue name. */
+    double value;
+
+    atomPtr = calloc(1, sizeof(PdbAtom));
+    if (atomPtr == NULL) {
+	return NULL;
+    }
+    atomPtr->ordinal = ordinal;
+    strncpy(atomName, line + 12, 4);
+    atomName[4] = '\0';
+    strncpy(residueName, line + 17, 3);
+    residueName[3] = '\0';
+
+    strncpy(buf, line + 30, 8);
+    buf[8] = '\0';
+    if (Tcl_GetDouble(interp, buf, &value) != TCL_OK) {
+	Tcl_AppendResult(interp, "bad x-coordinate \"", buf,
+			 "\"", (char *)NULL);
+	return NULL;
+    }
+    atomPtr->x = value;
+    strncpy(buf, line + 38, 8);
+    buf[8] = '\0';
+    if (Tcl_GetDouble(interp, buf, &value) != TCL_OK) {
+	Tcl_AppendResult(interp, "bad y-coordinate \"", buf,
+			 "\"", (char *)NULL);
+	return NULL;
+    }
+    atomPtr->y = value;
+    strncpy(buf, line + 46, 8);
+    buf[8] = '\0';
+    if (Tcl_GetDouble(interp, buf, &value) != TCL_OK) {
+	Tcl_AppendResult(interp, "bad z-coordinate \"", buf,
+			 "\"", (char *)NULL);
+	return NULL;
+    }
+    atomPtr->z = value;
+    symbolName[2] = symbolName[1]  = symbolName[0] = '\0';
+    if (lineLength >= 77) {
+	symbolName[0] = line[76];
+	symbolName[1] = line[77];
+	symbolName[2] = '\0';
+    }
+    if (GetAtomicNumber(interp, atomPtr, atomName, symbolName, residueName) 
+	!= TCL_OK) {
+	return NULL;
+    }
+    return atomPtr;
+}
+
+static void
+FreeAtoms(Tcl_HashTable *tablePtr)
+{
+    Tcl_HashEntry *hPtr;
+    Tcl_HashSearch iter;
+
+    for (hPtr = Tcl_FirstHashEntry(tablePtr, &iter); hPtr != NULL;
+	 hPtr = Tcl_NextHashEntry(&iter)) {
+	PdbAtom *atomPtr;
+	
+	atomPtr = Tcl_GetHashValue(hPtr);
+	free(atomPtr);
+    }
+    Tcl_DeleteHashTable(tablePtr);
+}
+
+
+static INLINE const char *
+SkipSpaces(const char *string) 
+{
+    while (isspace(*string)) {
+	string++;
+    }
+    return string;
+}
+
+static int
+IsSpaces(const char *string) 
+{
+    const char *p;
+    for (p = string; *p != '\0'; p++) {
+	if (!isspace(*p)) {
+	    return 0;
+	}
+    }
+    return 1;
+}
+
+static INLINE const char *
+GetLine(const char **stringPtr, const char *endPtr, int *lengthPtr) 
+{
+    const char *line;
+    const char *p;
+
+    line = SkipSpaces(*stringPtr);
+    for (p = line; p < endPtr; p++) {
+	if (*p == '\n') {
+	    *stringPtr = p + 1;
+	    *lengthPtr = p - line;
+	    return line;
+	}
+    }
+    *lengthPtr = (p - line) - 1;
+    *stringPtr = p;
+    return line;
+}
+
+
+static int 
+SerialToAtom(Tcl_Interp *interp, Tcl_HashTable *tablePtr, const char *string,
+             PdbAtom **atomPtrPtr)
+{
+    int serial;
+    long lserial;
+    Tcl_HashEntry *hPtr;
+
+    string = SkipSpaces(string);
+    if (Tcl_GetInt(interp, string, &serial) != TCL_OK) {
+	Tcl_AppendResult(interp, ": line ", Itoa(lineNum), 
+		": invalid serial number \"", string, 
+		"\" in CONECT record", (char *)NULL);
+	return TCL_ERROR;
+    }
+    lserial = (long)serial;
+    hPtr = Tcl_FindHashEntry(tablePtr, (char *)lserial);
+    if (hPtr == NULL) {
+	Tcl_AppendResult(interp, "serial number \"", string, 
+			 "\" not found in table", (char *)NULL);
+	return TCL_ERROR;
+    }
+    *atomPtrPtr = Tcl_GetHashValue(hPtr);
+    return TCL_OK;
+}
+
+/* 
+ *  PdbToVtk string ?-bonds none|both|auto|conect?
+ */
+static int
+PdbToVtkCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+	   Tcl_Obj *const *objv) 
+{
+    Tcl_Obj *objPtr, *pointsObjPtr, *atomsObjPtr;
+    const char *p, *pend;
+    const char *string;
+    char mesg[2000];
+    int length, nextOrdinal;
+    Tcl_HashTable atomTable, conectTable;
+    Tcl_HashEntry *hPtr;
+    Tcl_HashSearch iter;
+    int isNew;
+    int bondFlags;
+
+    bondFlags = BOND_BOTH;
+    lineNum = nextOrdinal = 0;
+    if ((objc != 2) && (objc != 4)) {
+	Tcl_AppendResult(interp, "wrong # arguments: should be \"",
+		Tcl_GetString(objv[0]), 
+		" string ?-bonds none|both|auto|conect\"", (char *)NULL);
+	return TCL_ERROR;
+    }
+    if (objc == 4) {
+	const char *string;
+	char c;
+
+	string = Tcl_GetString(objv[2]);
+	if ((string[0] != '-') || (strcmp(string, "-bonds") != 0)) {
+	    Tcl_AppendResult(interp, "bad switch \"", string, 
+			     "\": should be \"-bonds\"", (char *)NULL);
+	    return TCL_ERROR;
+	}
+	string = Tcl_GetString(objv[3]);
+	c = string[0];
+	if ((c == 'n') && (strcmp(string, "none") == 0)) {
+	    bondFlags = BOND_NONE;
+	} else if ((c == 'b') && (strcmp(string, "both") == 0)) {
+	    bondFlags = BOND_BOTH;
+	} else if ((c == 'a') && (strcmp(string, "auto") == 0)) {
+	    bondFlags = BOND_COMPUTE;
+	} else if ((c == 'c') && (strcmp(string, "conect") == 0)) {
+	    bondFlags = BOND_CONECT;
+	} else {
+	    Tcl_AppendResult(interp, "bad value \"", string, 
+		"\" for -bonds switch: should be none, both, manual, or auto",
+		(char *)NULL);
+	    return TCL_ERROR;
+	}
+    }
+    Tcl_InitHashTable(&atomTable, TCL_ONE_WORD_KEYS);
+    Tcl_InitHashTable(&conectTable, sizeof(ConnectKey) / sizeof(int));
+    string = Tcl_GetStringFromObj(objv[1], &length);
+    pointsObjPtr = Tcl_NewStringObj("", -1);
+    atomsObjPtr = Tcl_NewStringObj("", -1);
+    Tcl_IncrRefCount(pointsObjPtr);
+    Tcl_IncrRefCount(atomsObjPtr);
+    objPtr = NULL;
+    for (p = string, pend = p + length; p < pend; /*empty*/) {
+	const char *line;
+	char c;
+	int lineLength;
+
+	lineLength = 0;			/* Suppress compiler warning. */
+	lineNum++;
+	line = GetLine(&p, pend, &lineLength);
+        if (line >= pend) {
+	    break;			/* EOF */
+	}
+        if ((line[0] == '#') || (line[0] == '\n')) {
+	    continue;			/* Skip blank or comment lines. */
+	}
+	c = line[0];
+	if (((c == 'A') && (strncmp(line, "ATOM  ", 6) == 0)) ||
+	    ((c == 'H') && (strncmp(line, "HETATM", 6) == 0))) {
+	    char buf[200];
+	    int serialNum;
+	    long lserial;
+	    PdbAtom *atomPtr;
+
+	    if (lineLength < 47) {
+		char *msg;
+
+		msg = (char *)line;
+		msg[lineLength] = '\0';
+		Tcl_AppendResult(interp, "line ", Itoa(lineNum), 
+			" short ATOM line \"", line, "\"", (char *)NULL);
+		return TCL_ERROR;
+	    }		
+	    strncpy(buf, line + 6, 5);
+	    buf[5] = '\0';
+	    if (Tcl_GetInt(interp, buf, &serialNum) != TCL_OK) {
+		Tcl_AppendResult(interp, "line ", Itoa(lineNum), 
+			": bad serial number \"", buf, "\"", (char *)NULL);
+		goto error;
+	    }
+	    lserial = (long)serialNum;
+	    hPtr = Tcl_CreateHashEntry(&atomTable, (char *)lserial, &isNew);
+	    if (!isNew) {
+		Tcl_AppendResult(interp, "line ", Itoa(lineNum), 
+			": serial number \"", buf, "\" found more than once", 
+			(char *)NULL);
+		goto error;
+	    }
+	    atomPtr = NewAtom(interp, nextOrdinal, line, lineLength);
+	    if (atomPtr == NULL) {
+		goto error;
+	    }
+	    Tcl_SetHashValue(hPtr, atomPtr);
+	    Tcl_ListObjAppendElement(interp, pointsObjPtr, 
+				     Tcl_NewDoubleObj(atomPtr->x));
+	    Tcl_ListObjAppendElement(interp, pointsObjPtr, 
+				     Tcl_NewDoubleObj(atomPtr->y));
+	    Tcl_ListObjAppendElement(interp, pointsObjPtr, 
+				     Tcl_NewDoubleObj(atomPtr->z));
+	    Tcl_ListObjAppendElement(interp, atomsObjPtr, 
+				     Tcl_NewIntObj(atomPtr->number));
+	    nextOrdinal++;
+	} else if ((c == 'C') && (strncmp(line, "CONECT", 6) == 0)) {
+	    PdbAtom *atom1Ptr;
+	    int i, n;
+	    char buf[200];
+
+	    if ((bondFlags & BOND_CONECT) == 0) {
+		continue;
+	    }
+	    strncpy(buf, line + 6, 5);
+	    buf[5] = '\0';
+	    if (lineLength < 11) {
+		char *msg;
+
+		msg = (char *)line;
+		msg[lineLength] = '\0';
+		Tcl_AppendResult(interp, "line ", Itoa(lineNum), 
+			": bad CONECT record \"", msg, "\"",
+			(char *)NULL);
+		goto error;
+	    }
+	    if (SerialToAtom(interp, &atomTable, buf, &atom1Ptr) != TCL_OK) {
+		goto error;
+	    }
+	    /* Allow basic maximum of 4 connections. */
+	    for (n = 11, i = 0; i < 4; i++, n += 5) {
+		ConnectKey key;
+		PdbAtom *atom2Ptr;
+
+		if (n >= lineLength) {
+		    break;		
+		}
+		strncpy(buf, line + n, 5);
+		buf[5] = '\0';
+		if (IsSpaces(buf)) {
+		    break;		/* No more entries */
+		}
+		if (SerialToAtom(interp, &atomTable, buf, &atom2Ptr) !=TCL_OK) {
+		    goto error;
+		}
+		if (atom1Ptr->ordinal > atom2Ptr->ordinal) {
+		    key.from = atom2Ptr->ordinal;
+		    key.to = atom1Ptr->ordinal;
+		} else {
+		    key.from = atom1Ptr->ordinal;
+		    key.to = atom2Ptr->ordinal;
+		}
+		Tcl_CreateHashEntry(&conectTable, (char *)&key, &isNew);
+		if (isNew) {
+		    atom1Ptr->numConnections++;
+		    atom2Ptr->numConnections++;
+		}
+	    }
+	}
+    }
+    if (bondFlags & BOND_COMPUTE) {
+	ComputeBonds(&atomTable, &conectTable);
+    }
+    objPtr = Tcl_NewStringObj("# vtk DataFile Version 2.0\n", -1);
+    Tcl_AppendToObj(objPtr, "Converted from PDB format\n", -1);
+    Tcl_AppendToObj(objPtr, "ASCII\n", -1);
+    Tcl_AppendToObj(objPtr, "DATASET POLYDATA\n", -1);
+    sprintf(mesg, "POINTS %d float\n", atomTable.numEntries);
+    Tcl_AppendToObj(objPtr, mesg, -1);
+    Tcl_AppendObjToObj(objPtr, pointsObjPtr);
+    sprintf(mesg, "\n");
+    Tcl_AppendToObj(objPtr, mesg, -1);
+
+    if (conectTable.numEntries > 0) {
+	sprintf(mesg, "LINES %d %d\n", conectTable.numEntries, 
+		conectTable.numEntries * 3);
+	Tcl_AppendToObj(objPtr, mesg, -1);
+	for (hPtr = Tcl_FirstHashEntry(&conectTable, &iter); hPtr != NULL;
+	     hPtr = Tcl_NextHashEntry(&iter)) {
+	    ConnectKey *keyPtr;
+
+	    keyPtr = (ConnectKey *)Tcl_GetHashKey(&conectTable, hPtr);
+	    sprintf(mesg, "2 %d %d\n", keyPtr->from, keyPtr->to);
+	    Tcl_AppendToObj(objPtr, mesg, -1);
+	}
+	sprintf(mesg, "\n");
+	Tcl_AppendToObj(objPtr, mesg, -1);
+    }
+    for (hPtr = Tcl_FirstHashEntry(&atomTable, &iter); hPtr != NULL;
+	 hPtr = Tcl_NextHashEntry(&iter)) {
+	PdbAtom *atomPtr;
+
+	atomPtr = Tcl_GetHashValue(hPtr);
+	fprintf(stderr, "%d %s %d connections\n", atomPtr->ordinal,
+		elements[atomPtr->number].symbol, atomPtr->numConnections);
+    }
+    sprintf(mesg, "POINT_DATA %d\n", atomTable.numEntries);
+    Tcl_AppendToObj(objPtr, mesg, -1);
+    sprintf(mesg, "SCALARS element int\n");
+    Tcl_AppendToObj(objPtr, mesg, -1);
+    sprintf(mesg, "LOOKUP_TABLE default\n");
+    Tcl_AppendToObj(objPtr, mesg, -1);
+    Tcl_AppendObjToObj(objPtr, atomsObjPtr);
+    FreeAtoms(&atomTable);
+    Tcl_DeleteHashTable(&conectTable);
+    Tcl_DecrRefCount(pointsObjPtr);
+    Tcl_DecrRefCount(atomsObjPtr);
+    if (objPtr != NULL) {
+	Tcl_SetObjResult(interp, objPtr);
+    }
+    return TCL_OK;
+ error:
+    Tcl_DeleteHashTable(&conectTable);
+    FreeAtoms(&atomTable);
+    Tcl_DecrRefCount(pointsObjPtr);
+    Tcl_DecrRefCount(atomsObjPtr);
+    return TCL_ERROR;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpPdbToVtk_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "PdbToVtk" command into the interpreter.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpPdbToVtk_Init(Tcl_Interp *interp)
+{
+    /* install the widget command */
+    Tcl_CreateObjCommand(interp, "Rappture::PdbToVtk", PdbToVtkCmd,
+        NULL, NULL);
+    return TCL_OK;
+}
Index: /branches/r9/packages/ReadPoints/readPoints.c
===================================================================
--- /branches/r9/packages/ReadPoints/readPoints.c	(revision 4841)
+++ /branches/r9/packages/ReadPoints/readPoints.c	(revision 4841)
@@ -0,0 +1,185 @@
+
+/* 
+ * ----------------------------------------------------------------------
+ *  RpReadPoints - 
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  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 <stdlib.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+#include <errno.h>
+#include <float.h>
+#include "tcl.h"
+
+#define UCHAR(c) ((unsigned char) (c))
+
+static INLINE const char *
+SkipSpaces(const char *s, const char *endPtr) 
+{
+    while ((s < endPtr) && ((*s == ' ') || (*s == '\t'))) {
+	s++;
+    }
+    return s;
+}
+
+static INLINE const char *
+GetLine(Tcl_DString *dsPtr, const char *s, const char *endPtr) 
+{
+    const char *line, *p;
+    Tcl_DStringSetLength(dsPtr, 0);
+    line = SkipSpaces(s, endPtr);
+    for (p = line; p < endPtr; p++) {
+	if (*p == '\n') {
+	    if (p == line) {
+		line++;
+		continue;
+	    }
+	    Tcl_DStringAppend(dsPtr, line, p - line);
+	    return p + 1;
+	}
+    }
+    Tcl_DStringAppend(dsPtr, line, p - line);
+    return p;
+}
+
+int
+GetDouble(Tcl_Interp *interp, const char *s, double *valuePtr)
+{
+    char *end;
+    double d;
+    
+    errno = 0;
+    d = strtod(s, &end); /* INTL: TCL source. */
+    if (end == s) {
+	badDouble:
+        if (interp != (Tcl_Interp *) NULL) {
+            Tcl_AppendResult(interp,
+		"expected floating-point number but got \"", s, "\"", 
+		(char *) NULL);
+        }
+	return TCL_ERROR;
+    }
+    if (errno != 0 && (d == HUGE_VAL || d == -HUGE_VAL || d == 0)) {
+        if (interp != (Tcl_Interp *) NULL) {
+	    char msg[64 + TCL_INTEGER_SPACE];
+	
+	    sprintf(msg, "unknown floating-point error, errno = %d", errno);
+	    Tcl_AppendToObj(Tcl_GetObjResult(interp), msg, -1);
+	    Tcl_SetErrorCode(interp, "ARITH", "UNKNOWN", msg, (char *) NULL);
+        }
+	return TCL_ERROR;
+    }
+    while ((*end != 0) && isspace(UCHAR(*end))) { /* INTL: ISO space. */
+	end++;
+    }
+    if (*end != 0) {
+	goto badDouble;
+    }
+    *valuePtr = d;
+    return TCL_OK;
+}
+    
+/* 
+ *  ReadPoints string dimVar pointsVar
+ */
+static int
+ReadPoints(ClientData clientData, Tcl_Interp *interp, int objc,
+	   Tcl_Obj *const *objv) 
+{
+    Tcl_Obj *listObjPtr;
+    const char *p, *pend;
+    const char *string;
+    int count, length, dim;
+    Tcl_DString ds;
+
+    if (objc != 4) {
+	Tcl_AppendResult(interp, "wrong # arguments: should be \"",
+		Tcl_GetString(objv[0]), " string dimVar pointsVar\"", 
+		(char *)NULL);
+	return TCL_ERROR;
+    }
+    dim = 0;
+    string = Tcl_GetStringFromObj(objv[1], &length);
+    count = 0;
+    Tcl_DStringInit(&ds);
+    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+    for (p = string, pend = p + length; p < pend; /*empty*/) {
+	int i, argc;
+	const char **argv;
+	int result;
+
+	p = GetLine(&ds, p, pend);
+	if (Tcl_DStringLength(&ds) == 0) {
+	    break;			/* EOF */
+	}
+	result = Tcl_SplitList(interp, Tcl_DStringValue(&ds), &argc, &argv);
+	if (result != TCL_OK) {
+	    goto error;
+	}
+	if (argc == 0) {
+	    Tcl_Free((char *)argv);
+	    goto error;
+	}
+	if (dim == 0) {
+	    dim = argc;
+	}
+	if (dim != argc) {
+	    Tcl_AppendResult(interp, "wrong # of elements on line", (char *)NULL);
+	    Tcl_Free((char *)argv);
+	    goto error;
+	}
+	for (i = 0; i < argc; i++) {
+	    double d;
+
+	    if (GetDouble(interp, argv[i], &d) != TCL_OK) {
+		Tcl_Free((char *)argv);
+		goto error;
+	    }
+	    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(d));
+	    count++;
+	}
+	Tcl_Free((char *)argv);
+    }
+    Tcl_DStringFree(&ds);
+    if (Tcl_ObjSetVar2(interp, objv[2], NULL, Tcl_NewIntObj(dim),
+		      TCL_LEAVE_ERR_MSG) == NULL) {
+	return TCL_ERROR;
+    }
+    if (Tcl_ObjSetVar2(interp, objv[3], NULL, listObjPtr, 
+		      TCL_LEAVE_ERR_MSG) == NULL) {
+	return TCL_ERROR;
+    }
+    return TCL_OK;
+ error:
+    Tcl_DStringFree(&ds);
+    return TCL_ERROR;
+}
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpReadPoints_Init --
+ *
+ *  Invoked when the Rappture GUI library is being initialized
+ *  to install the "ConvertDxToVtk" command into the interpreter.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpReadPoints_Init(Tcl_Interp *interp)
+{
+    /* install the widget command */
+    Tcl_CreateObjCommand(interp, "Rappture::ReadPoints", ReadPoints,
+        NULL, NULL);
+    return TCL_OK;
+}
Index: /branches/r9/packages/Squeezer/RpSqueezer.c
===================================================================
--- /branches/r9/packages/Squeezer/RpSqueezer.c	(revision 4841)
+++ /branches/r9/packages/Squeezer/RpSqueezer.c	(revision 4841)
@@ -0,0 +1,207 @@
+/*
+ * ----------------------------------------------------------------------
+ *  RpSqueezer - image distortion for "cover flow" effect.
+ *
+ *  Modifies a Tk image to squeeze one side or the other and perhaps
+ *  foreshorten the image.
+ *
+ *    squeezer ?options...? <original> <newImage>
+ *        -side left|right     << which side to squeeze
+ *        -amount <fraction>   << squeeze down to this size (0-1)
+ *        -shorten <fraction>  << squeeze horizontally to this size (0-1)
+ *        -shadow <fraction>   << apply shadow horizontally (strength 0-1)
+ *
+ * ======================================================================
+ *  AUTHOR:  Michael McLennan, Purdue University
+ *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
+ *
+ *  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"
+
+/*
+ * Forward declarations.
+ */
+static int RpSqueezerCmd _ANSI_ARGS_((ClientData clientData,
+    Tcl_Interp *interp, int argc, CONST char **argv));
+
+/*
+ * ------------------------------------------------------------------------
+ *  RpSqueezer_Init --
+ *
+ *  Installs the "squeezer" command in a Tcl interpreter.
+ *
+ *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
+ *  message in the interp) if anything goes wrong.
+ * ------------------------------------------------------------------------
+ */
+int
+RpSqueezer_Init(interp)
+    Tcl_Interp *interp;         /* interpreter being initialized */
+{
+    /* install the widget command */
+    Tcl_CreateCommand(interp, "squeezer", RpSqueezerCmd,
+        NULL, NULL);
+
+    return TCL_OK;
+}
+
+/*
+ * ----------------------------------------------------------------------
+ * RpSqueezerCmd()
+ *
+ * Called to handle the "squeezer" command.  Returns TCL_OK on success
+ * and TCL_ERROR otherwise.
+ * ----------------------------------------------------------------------
+ */
+static int
+RpSqueezerCmd(clientData, interp, argc, argv)
+    ClientData clientData;        /* not used */
+    Tcl_Interp *interp;           /* current interpreter */
+    int argc;                     /* number of command arguments */
+    CONST char **argv;            /* command argument strings */
+{
+    int sideRight = 1;
+    double amount = 0.8;
+    double shorten = 0.7;
+    double shadow = 0.0;
+
+    int i, j, j0, j1, p, srcw, srch, destw, desth, nbytes;
+    int srci, srcj, rval, gval, bval, aval;
+    double shfac;
+    Tk_PhotoHandle srcPhoto, destPhoto;
+    Tk_PhotoImageBlock srcBlock, destBlock;
+    unsigned char* pixelPtr;
+
+   /* parse command line options */
+    i = 1;
+    while (i < argc) {
+        if (*argv[i] != '-') {
+            break;
+        }
+        if (i+1 >= argc) {
+            Tcl_AppendResult(interp, "missing value for option \"",
+                argv[i], "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+
+        if (strcmp(argv[i],"-side") == 0) {
+            if (strcmp(argv[i+1],"left") == 0) {
+                sideRight = 0;
+            } else if (strcmp(argv[i+1],"right") == 0) {
+                sideRight = 1;
+            } else {
+                Tcl_AppendResult(interp, "bad value \"",
+                    argv[i+1], "\": should be left or right", (char*)NULL);
+                return TCL_ERROR;
+            }
+        }
+        else if (strcmp(argv[i],"-amount") == 0) {
+            if (Tcl_GetDouble(interp, argv[i+1], &amount) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        else if (strcmp(argv[i],"-shorten") == 0) {
+            if (Tcl_GetDouble(interp, argv[i+1], &shorten) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        else if (strcmp(argv[i],"-shadow") == 0) {
+            if (Tcl_GetDouble(interp, argv[i+1], &shadow) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        else {
+            Tcl_AppendResult(interp, "bad option \"", argv[i],
+                "\": should be -side, -amount, -shorten, -shadow",
+                (char*)NULL);
+            return TCL_ERROR;
+        }
+
+        i += 2;
+    }
+
+    if (i+2 != argc) {
+        Tcl_AppendResult(interp, "wrong # args: should be \"",
+            argv[0], " ?options? origImg newImg\"", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    /* get the last two args... source and destination images */
+    srcPhoto = Tk_FindPhoto(interp, argv[i]);
+    if (srcPhoto == NULL) {
+        Tcl_AppendResult(interp, "image \"", argv[i], "doesn't exist",
+            " or isn't a photo image", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    destPhoto = Tk_FindPhoto(interp, argv[i+1]);
+    if (destPhoto == NULL) {
+        Tcl_AppendResult(interp, "image \"", argv[i+1], "doesn't exist",
+            " or isn't a photo image", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    /*
+     * Update the destination image to the proper size.
+     */
+    Tk_PhotoGetSize(srcPhoto, &srcw, &srch);
+    destw = round(srcw * shorten);
+
+    /*
+     * Scan through each column along the width.  Compute the height
+     * of the shortened column and then compute the pixels along the
+     * height.
+     */
+    Tk_PhotoGetImage(srcPhoto, &srcBlock);
+
+    destBlock.width = destw;
+    destBlock.height = srch;
+    destBlock.pixelSize = 4;
+    destBlock.offset[0] = 0;
+    destBlock.offset[1] = 1;
+    destBlock.offset[2] = 2;
+    destBlock.offset[3] = 3;
+    destBlock.pitch = destBlock.pixelSize * destw;
+    nbytes = destBlock.pitch * srch;
+    destBlock.pixelPtr = (unsigned char*)ckalloc((unsigned)nbytes);
+    memset(destBlock.pixelPtr, 0, (size_t)nbytes);
+
+    for (p=0; p < destw; p++) {
+        i = (sideRight) ? p : destw-p-1;
+        desth = round(srch - srch*(1.0-amount)*p/(double)destw);
+        j0 = (srch-desth)/2;
+        j1 = j0 + desth;
+
+        /* strength of shadow */
+        shfac = 1.0 - shadow * p/(double)destw;
+
+        for (j=j0; j < j1; j++) {
+            srci = srcw * i/(double)destw;
+            srcj = srch * (j-j0)/(double)desth;
+            pixelPtr = srcBlock.pixelPtr + srcj*srcBlock.pitch 
+                + srci*srcBlock.pixelSize;
+            rval = *(pixelPtr+srcBlock.offset[0]);
+            gval = *(pixelPtr+srcBlock.offset[1]);
+            bval = *(pixelPtr+srcBlock.offset[2]);
+            aval = *(pixelPtr+srcBlock.offset[3]);
+
+            pixelPtr = destBlock.pixelPtr + j*destBlock.pitch 
+                + i*destBlock.pixelSize;
+            *(pixelPtr+0) = round(rval*shfac);
+            *(pixelPtr+1) = round(gval*shfac);
+            *(pixelPtr+2) = round(bval*shfac);
+            *(pixelPtr+3) = aval;
+        }
+    }
+    Tk_PhotoSetSize(destPhoto, destw, srch);
+    Tk_PhotoPutBlock(destPhoto, &destBlock, 0, 0,
+        destBlock.width, destBlock.height, TK_PHOTO_COMPOSITE_SET);
+
+    ckfree((char*)destBlock.pixelPtr);
+    return TCL_OK;
+}
