OSDN Git Service

Updated to tk 8.4.1
[pf3gnuchains/sourceware.git] / tk / generic / tkEntry.c
index 6ed11d6..f0638fa 100644 (file)
@@ -1,12 +1,15 @@
 /* 
- * tkEntry.c --
+ * Entry.c --
  *
- *     This module implements entry widgets for the Tk
- *     toolkit.  An entry displays a string and allows
- *     the string to be edited.
+ *     This module implements entry and spinbox widgets for the Tk toolkit.
+ *     An entry displays a string and allows the string to be edited.
+ *     A spinbox expands on the entry by adding up/down buttons that control
+ *     the value of the entry widget.
  *
  * Copyright (c) 1990-1994 The Regents of the University of California.
  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 2000 Ajuba Solutions.
+ * Copyright (c) 2002 ActiveState Corporation.
  *
  * See the file "license.terms" for information on usage and redistribution
  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 #include "tkInt.h"
 #include "default.h"
 
+enum EntryType {
+    TK_ENTRY, TK_SPINBOX
+};
+
 /*
- * A data structure of the following type is kept for each entry
+ * A data structure of the following type is kept for each Entry
  * widget managed by this file:
  */
 
@@ -34,13 +41,13 @@ typedef struct {
     Tcl_Command widgetCmd;     /* Token for entry's widget command. */
     Tk_OptionTable optionTable;        /* Table that defines configuration options
                                 * available for this widget. */
-
+    enum EntryType type;       /* Specialized type of Entry widget */
 
     /*
      * Fields that are set by widget commands other than "configure".
      */
      
-    char *string;              /* Pointer to storage for string;
+    CONST char *string;                /* Pointer to storage for string;
                                 * NULL-terminated;  malloc-ed. */
     int insertPos;             /* Character index before which next typed
                                 * character will be inserted. */
@@ -72,12 +79,19 @@ typedef struct {
 
     Tk_3DBorder normalBorder;  /* Used for drawing border around whole
                                 * window, plus used for background. */
+    Tk_3DBorder disabledBorder;        /* Used for drawing  border around whole
+                                * window in disabled state, plus used for
+                                * background. */
+    Tk_3DBorder readonlyBorder;        /* Used for drawing  border around whole
+                                * window in readonly state, plus used for
+                                * background. */
     int borderWidth;           /* Width of 3-D border around window. */
     Tk_Cursor cursor;          /* Current cursor for window, or None. */
     int exportSelection;       /* Non-zero means tie internal entry selection
                                 * to X selection. */
     Tk_Font tkfont;            /* Information about text font, or NULL. */
     XColor *fgColorPtr;                /* Text color in normal mode. */
+    XColor *dfgColorPtr;       /* Text color in disabled mode. */
     XColor *highlightBgColorPtr;/* Color for drawing traversal highlight
                                 * area when highlight is off. */
     XColor *highlightColorPtr; /* Color for drawing traversal highlight. */
@@ -99,9 +113,6 @@ typedef struct {
                                 * characters. */
     int selBorderWidth;                /* Width of border around selection. */
     XColor *selFgColorPtr;     /* Foreground color for selected text. */
-    char *showChar;            /* Value of -show option.  If non-NULL, first
-                                * character is used for displaying all
-                                * characters in entry.  Malloc'ed. */
     int state;                 /* Normal or disabled.  Entry is read-only
                                 * when disabled. */
     char *textVarName;         /* Name of variable (malloc'ed) or NULL.
@@ -115,23 +126,27 @@ typedef struct {
     char *scrollCmd;           /* Command prefix for communicating with
                                 * scrollbar(s).  Malloc'ed.  NULL means
                                 * no command to issue. */
+    char *showChar;            /* Value of -show option.  If non-NULL, first
+                                * character is used for displaying all
+                                * characters in entry.  Malloc'ed.
+                                * This is only used by the Entry widget. */
 
     /*
      * Fields whose values are derived from the current values of the
      * configuration settings above.
      */
 
+    CONST char *displayString; /* String to use when displaying.  This may
+                                * be a pointer to string, or a pointer to
+                                * malloced memory with the same character
+                                * length as string but whose characters
+                                * are all equal to showChar. */
     int numBytes;              /* Length of string in bytes. */
     int numChars;              /* Length of string in characters.  Both
                                 * string and displayString have the same
                                 * character length, but may have different
                                 * byte lengths due to being made from
                                 * different UTF-8 characters. */
-    char *displayString;       /* String to use when displaying.  This may
-                                * be a pointer to string, or a pointer to
-                                * malloced memory with the same character
-                                * length as string but whose characters
-                                * are all equal to showChar. */
     int numDisplayBytes;       /* Length of displayString in bytes. */
     int inset;                 /* Number of pixels on the left and right
                                 * sides that are taken up by XPAD, borderWidth
@@ -149,19 +164,78 @@ typedef struct {
     GC selTextGC;              /* For drawing selected text. */
     GC highlightGC;            /* For drawing traversal highlight. */
     int avgWidth;              /* Width of average character. */
+    int xWidth;                        /* Extra width to reserve for widget.
+                                * Used by spinboxes for button space. */
     int flags;                 /* Miscellaneous flags;  see below for
                                 * definitions. */
-    Tk_TSOffset tsoffset;
 
+    int validate;               /* Non-zero means try to validate */
     char *validateCmd;          /* Command prefix to use when invoking
                                 * validate command.  NULL means don't
                                 * invoke commands.  Malloc'ed. */
-    int validate;               /* Non-zero means try to validate */
     char *invalidCmd;          /* Command called when a validation returns 0
                                 * (successfully fails), defaults to {}. */
+
 } Entry;
 
 /*
+ * A data structure of the following type is kept for each spinbox
+ * widget managed by this file:
+ */
+
+typedef struct {
+    Entry entry;               /* A pointer to the generic entry structure.
+                                * This must be the first element of the
+                                * Spinbox. */
+
+    /*
+     * Spinbox specific configuration settings.
+     */
+
+    Tk_3DBorder activeBorder;  /* Used for drawing border around active
+                                * buttons. */
+    Tk_3DBorder buttonBorder;  /* Used for drawing border around buttons. */
+    Tk_Cursor bCursor;         /* cursor for buttons, or None. */
+    int bdRelief;              /* 3-D effect: TK_RELIEF_RAISED, etc. */
+    int buRelief;              /* 3-D effect: TK_RELIEF_RAISED, etc. */
+    char *command;             /* Command to invoke for spin buttons.
+                                * NULL means no command to issue. */
+
+    /*
+     * Spinbox specific fields for use with configuration settings above.
+     */
+
+    int wrap;                  /* whether to wrap around when spinning */
+
+    int selElement;            /* currently selected control */
+    int curElement;            /* currently mouseover control */
+
+    int repeatDelay;           /* repeat delay */
+    int repeatInterval;                /* repeat interval */
+
+    double fromValue;          /* Value corresponding to left/top of dial */
+    double toValue;            /* Value corresponding to right/bottom
+                                * of dial */
+    double increment;          /* If > 0, all values are rounded to an
+                                * even multiple of this value. */
+    char *formatBuf;           /* string into which to format value.
+                                * Malloc'ed. */
+    char *reqFormat;           /* Sprintf conversion specifier used for the
+                                * value that the users requests. Malloc'ed. */
+    char *valueFormat;         /* Sprintf conversion specifier used for
+                                * the value. */
+    char digitFormat[10];      /* Sprintf conversion specifier computed from
+                                * digits and other information; used for
+                                * the value. */
+
+    char *valueStr;            /* Values List. Malloc'ed. */
+    Tcl_Obj *listObj;          /* Pointer to the list object being used */
+    int eIndex;                        /* Holds the current index into elements */
+    int nElements;             /* Holds the current count of elements */
+
+} Spinbox;
+
+/*
  * Assigned bits of "flags" fields of Entry structures, and what those
  * bits mean:
  *
@@ -205,17 +279,23 @@ typedef struct {
 #define YPAD 1
 
 /*
+ * A comparison function for double values.  For Spinboxes.
+ */
+#define MIN_DBL_VAL            1E-9
+#define DOUBLES_EQ(d1, d2)     (fabs((d1) - (d2)) < MIN_DBL_VAL)
+
+/*
  * 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
+    STATE_DISABLED, STATE_NORMAL, STATE_READONLY
 };
 
 static char *stateStrings[] = {
-    "disabled", "normal", (char *) NULL
+    "disabled", "normal", "readonly", (char *) NULL
 };
 
 /*
@@ -231,16 +311,16 @@ enum validateType {
     /*
      * These extra enums are for use with EntryValidateChange
      */
-    VALIDATE_FORCED, VALIDATE_DELETE, VALIDATE_INSERT
+    VALIDATE_FORCED, VALIDATE_DELETE, VALIDATE_INSERT, VALIDATE_BUTTON
 };
 #define DEF_ENTRY_VALIDATE     "none"
 #define DEF_ENTRY_INVALIDCMD   ""
 
 /*
- * Information used for argv parsing.
+ * Information used for Entry objv parsing.
  */
 
-static Tk_OptionSpec optionSpecs[] = {
+static Tk_OptionSpec entryOptSpec[] = {
     {TK_OPTION_BORDER, "-background", "background", "Background",
        DEF_ENTRY_BG_COLOR, -1, Tk_Offset(Entry, normalBorder),
        0, (ClientData) DEF_ENTRY_BG_MONO, 0},
@@ -254,6 +334,13 @@ static Tk_OptionSpec optionSpecs[] = {
     {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
        DEF_ENTRY_CURSOR, -1, Tk_Offset(Entry, cursor),
        TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_BORDER, "-disabledbackground", "disabledBackground",
+        "DisabledBackground", DEF_ENTRY_DISABLED_BG_COLOR, -1,
+        Tk_Offset(Entry, disabledBorder), TK_OPTION_NULL_OK,
+        (ClientData) DEF_ENTRY_DISABLED_BG_MONO, 0},
+    {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
+        "DisabledForeground", DEF_ENTRY_DISABLED_FG, -1,
+        Tk_Offset(Entry, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0},
     {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
         "ExportSelection", DEF_ENTRY_EXPORT_SELECTION, -1, 
         Tk_Offset(Entry, exportSelection), 0, 0, 0},
@@ -298,6 +385,10 @@ static Tk_OptionSpec optionSpecs[] = {
        (char *) NULL, 0, -1, 0, (ClientData) "-invalidcommand", 0},
     {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
        DEF_ENTRY_JUSTIFY, -1, Tk_Offset(Entry, justify), 0, 0, 0},
+    {TK_OPTION_BORDER, "-readonlybackground", "readonlyBackground",
+        "ReadonlyBackground", DEF_ENTRY_READONLY_BG_COLOR, -1,
+        Tk_Offset(Entry, readonlyBorder), TK_OPTION_NULL_OK,
+        (ClientData) DEF_ENTRY_READONLY_BG_MONO, 0},
     {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
        DEF_ENTRY_RELIEF, -1, Tk_Offset(Entry, relief), 
         0, 0, 0},
@@ -341,11 +432,166 @@ static Tk_OptionSpec optionSpecs[] = {
 };
 
 /*
- * Flags for GetEntryIndex procedure:
+ * Information used for Spinbox objv parsing.
  */
 
-#define ZERO_OK                        1
-#define LAST_PLUS_ONE_OK       2
+#define DEF_SPINBOX_REPEAT_DELAY       "400"
+#define DEF_SPINBOX_REPEAT_INTERVAL    "100"
+
+#define DEF_SPINBOX_CMD                        ""
+
+#define DEF_SPINBOX_FROM               "0"
+#define DEF_SPINBOX_TO                 "0"
+#define DEF_SPINBOX_INCREMENT          "1"
+#define DEF_SPINBOX_FORMAT             ""
+
+#define DEF_SPINBOX_VALUES             ""
+#define DEF_SPINBOX_WRAP               "0"
+
+static Tk_OptionSpec sbOptSpec[] = {
+    {TK_OPTION_BORDER, "-activebackground", "activeBackground", "Background",
+       DEF_BUTTON_ACTIVE_BG_COLOR, -1, Tk_Offset(Spinbox, activeBorder),
+       0, (ClientData) DEF_BUTTON_ACTIVE_BG_MONO, 0},
+    {TK_OPTION_BORDER, "-background", "background", "Background",
+       DEF_ENTRY_BG_COLOR, -1, Tk_Offset(Entry, normalBorder),
+       0, (ClientData) DEF_ENTRY_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_ENTRY_BORDER_WIDTH, -1, Tk_Offset(Entry, borderWidth), 
+        0, 0, 0},
+    {TK_OPTION_BORDER, "-buttonbackground", "Button.background", "Background",
+       DEF_BUTTON_BG_COLOR, -1, Tk_Offset(Spinbox, buttonBorder),
+       0, (ClientData) DEF_BUTTON_BG_MONO, 0},
+    {TK_OPTION_CURSOR, "-buttoncursor", "Button.cursor", "Cursor",
+       DEF_BUTTON_CURSOR, -1, Tk_Offset(Spinbox, bCursor),
+       TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_RELIEF, "-buttondownrelief", "Button.relief", "Relief",
+       DEF_BUTTON_RELIEF, -1, Tk_Offset(Spinbox, bdRelief),
+        0, 0, 0},
+    {TK_OPTION_RELIEF, "-buttonuprelief", "Button.relief", "Relief",
+       DEF_BUTTON_RELIEF, -1, Tk_Offset(Spinbox, buRelief),
+        0, 0, 0},
+    {TK_OPTION_STRING, "-command", "command", "Command",
+       DEF_SPINBOX_CMD, -1, Tk_Offset(Spinbox, command),
+       TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
+       DEF_ENTRY_CURSOR, -1, Tk_Offset(Entry, cursor),
+       TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_BORDER, "-disabledbackground", "disabledBackground",
+        "DisabledBackground", DEF_ENTRY_DISABLED_BG_COLOR, -1,
+        Tk_Offset(Entry, disabledBorder), TK_OPTION_NULL_OK,
+        (ClientData) DEF_ENTRY_DISABLED_BG_MONO, 0},
+    {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
+        "DisabledForeground", DEF_ENTRY_DISABLED_FG, -1,
+        Tk_Offset(Entry, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
+        "ExportSelection", DEF_ENTRY_EXPORT_SELECTION, -1, 
+        Tk_Offset(Entry, 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_ENTRY_FONT, -1, Tk_Offset(Entry, tkfont), 0, 0, 0},
+    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
+       DEF_ENTRY_FG, -1, Tk_Offset(Entry, fgColorPtr), 0, 
+        0, 0},
+    {TK_OPTION_STRING, "-format", "format", "Format",
+       DEF_SPINBOX_FORMAT, -1, Tk_Offset(Spinbox, reqFormat),
+       TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_DOUBLE, "-from", "from", "From",
+       DEF_SPINBOX_FROM, -1, Tk_Offset(Spinbox, fromValue), 0, 0, 0},
+    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
+       "HighlightBackground", DEF_ENTRY_HIGHLIGHT_BG,
+       -1, Tk_Offset(Entry, highlightBgColorPtr), 
+        0, 0, 0},
+    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
+       DEF_ENTRY_HIGHLIGHT, -1, Tk_Offset(Entry, highlightColorPtr),
+       0, 0, 0},
+    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
+       "HighlightThickness", DEF_ENTRY_HIGHLIGHT_WIDTH, -1, 
+       Tk_Offset(Entry, highlightWidth), 0, 0, 0},
+    {TK_OPTION_DOUBLE, "-increment", "increment", "Increment",
+       DEF_SPINBOX_INCREMENT, -1, Tk_Offset(Spinbox, increment), 0, 0, 0},
+    {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
+       DEF_ENTRY_INSERT_BG, -1, Tk_Offset(Entry, insertBorder), 
+        0, 0, 0},
+    {TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth", 
+        "BorderWidth", DEF_ENTRY_INSERT_BD_COLOR, -1, 
+        Tk_Offset(Entry, insertBorderWidth), 0, 
+        (ClientData) DEF_ENTRY_INSERT_BD_MONO, 0},
+    {TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime",
+        DEF_ENTRY_INSERT_OFF_TIME, -1, Tk_Offset(Entry, insertOffTime), 
+        0, 0, 0},
+    {TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime",
+        DEF_ENTRY_INSERT_ON_TIME, -1, Tk_Offset(Entry, insertOnTime), 
+        0, 0, 0},
+    {TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
+       DEF_ENTRY_INSERT_WIDTH, -1, Tk_Offset(Entry, insertWidth), 
+        0, 0, 0},
+    {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand",
+       DEF_ENTRY_INVALIDCMD, -1, Tk_Offset(Entry, invalidCmd),
+       TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_SYNONYM, "-invcmd", (char *) NULL, (char *) NULL,
+       (char *) NULL, 0, -1, 0, (ClientData) "-invalidcommand", 0},
+    {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
+       DEF_ENTRY_JUSTIFY, -1, Tk_Offset(Entry, justify), 0, 0, 0},
+    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
+       DEF_ENTRY_RELIEF, -1, Tk_Offset(Entry, relief), 
+        0, 0, 0},
+    {TK_OPTION_BORDER, "-readonlybackground", "readonlyBackground",
+        "ReadonlyBackground", DEF_ENTRY_READONLY_BG_COLOR, -1,
+        Tk_Offset(Entry, readonlyBorder), TK_OPTION_NULL_OK,
+        (ClientData) DEF_ENTRY_READONLY_BG_MONO, 0},
+    {TK_OPTION_INT, "-repeatdelay", "repeatDelay", "RepeatDelay",
+        DEF_SPINBOX_REPEAT_DELAY, -1, Tk_Offset(Spinbox, repeatDelay), 
+        0, 0, 0},
+    {TK_OPTION_INT, "-repeatinterval", "repeatInterval", "RepeatInterval",
+        DEF_SPINBOX_REPEAT_INTERVAL, -1, Tk_Offset(Spinbox, repeatInterval), 
+        0, 0, 0},
+    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
+        DEF_ENTRY_SELECT_COLOR, -1, Tk_Offset(Entry, selBorder),
+        0, (ClientData) DEF_ENTRY_SELECT_MONO, 0},
+    {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth", 
+        "BorderWidth", DEF_ENTRY_SELECT_BD_COLOR, -1, 
+        Tk_Offset(Entry, selBorderWidth), 
+        0, (ClientData) DEF_ENTRY_SELECT_BD_MONO, 0},
+    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
+       DEF_ENTRY_SELECT_FG_COLOR, -1, Tk_Offset(Entry, selFgColorPtr),
+       0, (ClientData) DEF_ENTRY_SELECT_FG_MONO, 0},
+    {TK_OPTION_STRING_TABLE, "-state", "state", "State",
+       DEF_ENTRY_STATE, -1, Tk_Offset(Entry, state), 
+        0, (ClientData) stateStrings, 0},
+    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
+       DEF_ENTRY_TAKE_FOCUS, -1, Tk_Offset(Entry, takeFocus), 
+        TK_CONFIG_NULL_OK, 0, 0},
+    {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
+       DEF_ENTRY_TEXT_VARIABLE, -1, Tk_Offset(Entry, textVarName),
+       TK_CONFIG_NULL_OK, 0, 0},
+    {TK_OPTION_DOUBLE, "-to", "to", "To",
+       DEF_SPINBOX_TO, -1, Tk_Offset(Spinbox, toValue), 0, 0, 0},
+    {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate",
+       DEF_ENTRY_VALIDATE, -1, Tk_Offset(Entry, validate),
+       0, (ClientData) validateStrings, 0},
+    {TK_OPTION_STRING, "-validatecommand", "validateCommand", "ValidateCommand",
+       (char *) NULL, -1, Tk_Offset(Entry, validateCmd),
+       TK_CONFIG_NULL_OK, 0, 0},
+    {TK_OPTION_STRING, "-values", "values", "Values",
+        DEF_SPINBOX_VALUES, -1, Tk_Offset(Spinbox, valueStr),
+        TK_OPTION_NULL_OK, 0, 0},
+    {TK_OPTION_SYNONYM, "-vcmd", (char *) NULL, (char *) NULL,
+       (char *) NULL, 0, -1, 0, (ClientData) "-validatecommand", 0},
+    {TK_OPTION_INT, "-width", "width", "Width",
+       DEF_ENTRY_WIDTH, -1, Tk_Offset(Entry, prefWidth), 0, 0, 0},
+    {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap",
+       DEF_SPINBOX_WRAP, -1, Tk_Offset(Spinbox, wrap), 0, 0, 0},
+    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
+       DEF_ENTRY_SCROLL_COMMAND, -1, Tk_Offset(Entry, scrollCmd),
+       TK_CONFIG_NULL_OK, 0, 0},
+    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
+       (char *) NULL, 0, -1, 0, 0, 0}
+};
 
 /*
  * The following tables define the entry widget commands (and sub-
@@ -353,27 +599,74 @@ static Tk_OptionSpec optionSpecs[] = {
  * enumerated types used to dispatch the entry widget command.
  */
 
-static char *commandNames[] = {
+static CONST char *entryCmdNames[] = {
     "bbox", "cget", "configure", "delete", "get", "icursor", "index", 
     "insert", "scan", "selection", "validate", "xview", (char *) NULL
 };
 
-enum command {
+enum entryCmd {
     COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_DELETE, 
     COMMAND_GET, COMMAND_ICURSOR, COMMAND_INDEX, COMMAND_INSERT, 
     COMMAND_SCAN, COMMAND_SELECTION, COMMAND_VALIDATE, COMMAND_XVIEW
 };
 
-static char *selCommandNames[] = {
+static CONST char *selCmdNames[] = {
     "adjust", "clear", "from", "present", "range", "to", (char *) NULL
 };
 
-enum selcommand {
+enum selCmd {
     SELECTION_ADJUST, SELECTION_CLEAR, SELECTION_FROM,
     SELECTION_PRESENT, SELECTION_RANGE, SELECTION_TO
 };
 
 /*
+ * The following tables define the spinbox widget commands (and sub-
+ * commands) and map the indexes into the string tables into 
+ * enumerated types used to dispatch the spinbox widget command.
+ */
+
+static CONST char *sbCmdNames[] = {
+    "bbox", "cget", "configure", "delete", "get", "icursor", "identify",
+    "index", "insert", "invoke", "scan", "selection", "set",
+    "validate", "xview", (char *) NULL
+};
+
+enum sbCmd {
+    SB_CMD_BBOX, SB_CMD_CGET, SB_CMD_CONFIGURE, SB_CMD_DELETE, 
+    SB_CMD_GET, SB_CMD_ICURSOR, SB_CMD_IDENTIFY, SB_CMD_INDEX,
+    SB_CMD_INSERT, SB_CMD_INVOKE, SB_CMD_SCAN, SB_CMD_SELECTION,
+    SB_CMD_SET, SB_CMD_VALIDATE, SB_CMD_XVIEW
+};
+
+static CONST char *sbSelCmdNames[] = {
+    "adjust", "clear", "element", "from", "present", "range", "to",
+    (char *) NULL
+};
+
+enum sbselCmd {
+    SB_SEL_ADJUST, SB_SEL_CLEAR, SB_SEL_ELEMENT, SB_SEL_FROM, 
+    SB_SEL_PRESENT, SB_SEL_RANGE, SB_SEL_TO
+};
+
+/*
+ * Extra for selection of elements
+ */
+
+static CONST char *selElementNames[] = {
+    "none", "buttondown", "buttonup", (char *) NULL, "entry"
+};
+enum selelement {
+    SEL_NONE, SEL_BUTTONDOWN, SEL_BUTTONUP, SEL_NULL, SEL_ENTRY
+};
+
+/*
+ * Flags for GetEntryIndex procedure:
+ */
+
+#define ZERO_OK                        1
+#define LAST_PLUS_ONE_OK       2
+
+/*
  * Forward declarations for procedures defined later in this file:
  */
 
@@ -399,21 +692,23 @@ static void               EntryLostSelection _ANSI_ARGS_((
 static void            EventuallyRedraw _ANSI_ARGS_((Entry *entryPtr));
 static void            EntryScanTo _ANSI_ARGS_((Entry *entryPtr, int y));
 static void            EntrySetValue _ANSI_ARGS_((Entry *entryPtr,
-                           char *value));
+                           CONST char *value));
 static void            EntrySelectTo _ANSI_ARGS_((
                            Entry *entryPtr, int index));
 static char *          EntryTextVarProc _ANSI_ARGS_((ClientData clientData,
-                           Tcl_Interp *interp, char *name1, char *name2,
-                           int flags));
+                           Tcl_Interp *interp, CONST char *name1,
+                           CONST char *name2, int flags));
 static void            EntryUpdateScrollbar _ANSI_ARGS_((Entry *entryPtr));
 static int             EntryValidate _ANSI_ARGS_((Entry *entryPtr,
                            char *cmd));
 static int             EntryValidateChange _ANSI_ARGS_((Entry *entryPtr,
-                           char *change, char *new, int index, int type));
+                           char *change, CONST char *new, int index,
+                           int type));
 static void            ExpandPercents _ANSI_ARGS_((Entry *entryPtr,
-                           char *before, char *change, char *new,
+                           CONST char *before, char *change, CONST char *new,
                            int index, int type, Tcl_DString *dsPtr));
-static void            EntryValueChanged _ANSI_ARGS_((Entry *entryPtr));
+static void            EntryValueChanged _ANSI_ARGS_((Entry *entryPtr,
+                           CONST char *newValue));
 static void            EntryVisibleRange _ANSI_ARGS_((Entry *entryPtr,
                            double *firstPtr, double *lastPtr));
 static int             EntryWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
@@ -427,14 +722,26 @@ static void               InsertChars _ANSI_ARGS_((Entry *entryPtr, int index,
                            char *string));
 
 /*
- * The structure below defines entry class behavior by means of procedures
+ * These forward declarations are the spinbox specific ones:
+ */
+
+static int             SpinboxWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
+                           Tcl_Interp *interp, int objc, 
+                           Tcl_Obj *CONST objv[]));
+static int             GetSpinboxElement _ANSI_ARGS_((Spinbox *sbPtr,
+                           int x, int y));
+static int             SpinboxInvoke _ANSI_ARGS_((Tcl_Interp *interp,
+                           Spinbox *sbPtr, int element));
+static int             ComputeFormat _ANSI_ARGS_((Spinbox *sbPtr));
+
+/*
+ * The structure below defines widget class behavior by means of procedures
  * that can be invoked from generic window code.
  */
 
-static TkClassProcs entryClass = {
-    NULL,                      /* createProc. */
-    EntryWorldChanged,         /* geometryProc. */
-    NULL                       /* modalProc. */
+static Tk_ClassProcs entryClass = {
+    sizeof(Tk_ClassProcs),     /* size */
+    EntryWorldChanged,         /* worldChangedProc */
 };
 
 \f
@@ -458,7 +765,7 @@ static TkClassProcs entryClass = {
 
 int
 Tk_EntryObjCmd(clientData, interp, objc, objv)
-    ClientData clientData;     /* Either NULL or pointer to option table. */
+    ClientData clientData;     /* NULL. */
     Tcl_Interp *interp;                /* Current interpreter. */
     int objc;                  /* Number of arguments. */
     Tcl_Obj *CONST objv[];      /* Argument objects. */
@@ -466,25 +773,7 @@ Tk_EntryObjCmd(clientData, interp, objc, objv)
     register Entry *entryPtr;
     Tk_OptionTable optionTable;
     Tk_Window tkwin;
-
-    optionTable = (Tk_OptionTable) clientData;
-    if (optionTable == NULL) {
-       Tcl_CmdInfo info;
-       char *name;
-
-       /*
-        * We haven't created the option table for this widget class
-        * yet.  Do it now and save the table as the clientData for
-        * the command, so we'll have access to it in future
-        * invocations of the command.
-        */
-
-       optionTable = Tk_CreateOptionTable(interp, optionSpecs);
-       name = Tcl_GetString(objv[0]);
-       Tcl_GetCommandInfo(interp, name, &info);
-       info.objClientData = (ClientData) optionTable;
-       Tcl_SetCommandInfo(interp, name, &info);
-    }
+    char *tmp;
 
     if (objc < 2) {
        Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
@@ -498,12 +787,22 @@ Tk_EntryObjCmd(clientData, interp, objc, objv)
     }
 
     /*
+     * Create the option table for this widget class.  If it has already
+     * been created, Tk will return the cached value.
+     */
+
+    optionTable = Tk_CreateOptionTable(interp, entryOptSpec);
+
+    /*
      * Initialize the fields of the structure that won't be initialized
      * by ConfigureEntry, or that ConfigureEntry requires to be
-     * initialized already (e.g. resource pointers).
+     * initialized already (e.g. resource pointers).  Only the non-NULL/0
+     * data must be initialized as memset covers the rest.
      */
 
     entryPtr                   = (Entry *) ckalloc(sizeof(Entry));
+    memset((VOID *) entryPtr, 0, sizeof(Entry));
+
     entryPtr->tkwin            = tkwin;
     entryPtr->display          = Tk_Display(tkwin);
     entryPtr->interp           = interp;
@@ -511,62 +810,35 @@ Tk_EntryObjCmd(clientData, interp, objc, objv)
            Tk_PathName(entryPtr->tkwin), EntryWidgetObjCmd,
            (ClientData) entryPtr, EntryCmdDeletedProc);
     entryPtr->optionTable      = optionTable;
-    entryPtr->string           = (char *) ckalloc(1);
-    entryPtr->string[0]                = '\0';
-    entryPtr->insertPos                = 0;
+    entryPtr->type             = TK_ENTRY;
+    tmp                                = (char *) ckalloc(1);
+    tmp[0]                     = '\0';
+    entryPtr->string           = tmp;
     entryPtr->selectFirst      = -1;
     entryPtr->selectLast       = -1;
-    entryPtr->selectAnchor     = 0;
-    entryPtr->scanMarkX                = 0;
-    entryPtr->scanMarkIndex    = 0;
 
-    entryPtr->normalBorder     = NULL;
-    entryPtr->borderWidth      = 0;
     entryPtr->cursor           = None;
     entryPtr->exportSelection  = 1;
-    entryPtr->tkfont           = NULL;
-    entryPtr->fgColorPtr       = NULL;
-    entryPtr->highlightBgColorPtr      = NULL;
-    entryPtr->highlightColorPtr        = NULL;
-    entryPtr->highlightWidth   = 0;
-    entryPtr->insertBorder     = NULL;
-    entryPtr->insertBorderWidth        = 0;
-    entryPtr->insertOffTime    = 0;
-    entryPtr->insertOnTime     = 0;
-    entryPtr->insertWidth      = 0;
     entryPtr->justify          = TK_JUSTIFY_LEFT;
     entryPtr->relief           = TK_RELIEF_FLAT;
-    entryPtr->selBorder                = NULL;
-    entryPtr->selBorderWidth   = 0;
-    entryPtr->selFgColorPtr    = NULL;
-    entryPtr->showChar         = NULL;
     entryPtr->state            = STATE_NORMAL;
-    entryPtr->textVarName      = NULL;
-    entryPtr->takeFocus                = NULL;
-    entryPtr->prefWidth                = 0;
-    entryPtr->scrollCmd                = NULL;
-    entryPtr->numBytes         = 0;
-    entryPtr->numChars         = 0;
     entryPtr->displayString    = entryPtr->string;
-    entryPtr->numDisplayBytes  = 0;
     entryPtr->inset            = XPAD;
-    entryPtr->textLayout       = NULL;
-    entryPtr->layoutX          = 0;
-    entryPtr->layoutY          = 0;
-    entryPtr->leftX            = 0;
-    entryPtr->leftIndex                = 0;
-    entryPtr->insertBlinkHandler       = (Tcl_TimerToken) NULL;
     entryPtr->textGC           = None;
     entryPtr->selTextGC                = None;
     entryPtr->highlightGC      = None;
     entryPtr->avgWidth         = 1;
-    entryPtr->flags            = 0;
-    entryPtr->validateCmd      = NULL;
     entryPtr->validate         = VALIDATE_NONE;
-    entryPtr->invalidCmd       = NULL;
+
+    /*
+     * 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) entryPtr->tkwin);
 
     Tk_SetClass(entryPtr->tkwin, "Entry");
-    TkSetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr);
+    Tk_SetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr);
     Tk_CreateEventHandler(entryPtr->tkwin,
            ExposureMask|StructureNotifyMask|FocusChangeMask,
            EntryEventProc, (ClientData) entryPtr);
@@ -579,7 +851,7 @@ Tk_EntryObjCmd(clientData, interp, objc, objv)
        Tk_DestroyWindow(entryPtr->tkwin);
        return TCL_ERROR;
     }
-    
+
     Tcl_SetResult(interp, Tk_PathName(entryPtr->tkwin), TCL_STATIC);
     return TCL_OK;
 }
@@ -617,20 +889,20 @@ EntryWidgetObjCmd(clientData, interp, objc, objv)
        Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
        return TCL_ERROR;
     }
-    Tcl_Preserve((ClientData) entryPtr);
 
     /* 
      * Parse the widget command by looking up the second token in
      * the list of valid command names. 
      */
 
-    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
+    result = Tcl_GetIndexFromObj(interp, objv[1], entryCmdNames,
            "option", 0, &cmdIndex);
     if (result != TCL_OK) {
        return result;
     }
 
-    switch (cmdIndex) {
+    Tcl_Preserve((ClientData) entryPtr);
+    switch ((enum entryCmd) cmdIndex) {
         case COMMAND_BBOX: {
            int index, x, y, width, height;
            char buf[TCL_INTEGER_SPACE * 4];
@@ -717,7 +989,7 @@ EntryWidgetObjCmd(clientData, interp, objc, objv)
                Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
                goto error;
            }
-           Tcl_SetResult(interp, entryPtr->string, TCL_STATIC);
+           Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1);
            break;
        }
 
@@ -799,19 +1071,27 @@ EntryWidgetObjCmd(clientData, interp, objc, objv)
            int index, index2;
 
            if (objc < 3) {
-               Tcl_WrongNumArgs(interp, 2, objv, "option ?index?");
+               Tcl_WrongNumArgs(interp, 2, objv, "option ?index?");
                goto error;
            }
-
+           
            /* 
             * Parse the selection sub-command, using the command
-            * table "selCommandNames" defined above.
+            * table "selCmdNames" defined above.
             */
            
-           result = Tcl_GetIndexFromObj(interp, objv[2], selCommandNames,
-                    "selection option", 0, &selIndex);
+           result = Tcl_GetIndexFromObj(interp, objv[2], selCmdNames,
+                   "selection option", 0, &selIndex);
            if (result != TCL_OK) {
-               goto error;
+               goto error;
+           }
+
+           /*
+            * Disabled entries don't allow the selection to be modified.
+            */
+
+           if (entryPtr->state == STATE_DISABLED) {
+               goto done;
            }
 
            switch(selIndex) {
@@ -1043,12 +1323,6 @@ DestroyEntry(memPtr)
     char *memPtr;              /* Info about entry widget. */
 {
     Entry *entryPtr = (Entry *) memPtr;
-    entryPtr->flags |= ENTRY_DELETED;
-
-    Tcl_DeleteCommandFromToken(entryPtr->interp, entryPtr->widgetCmd);
-    if (entryPtr->flags & REDRAW_PENDING) {
-        Tcl_CancelIdleCall(DisplayEntry, (ClientData) entryPtr);
-    }
 
     /*
      * Free up all the stuff that requires special handling, then
@@ -1056,7 +1330,7 @@ DestroyEntry(memPtr)
      * stuff.
      */
 
-    ckfree(entryPtr->string);
+    ckfree((char *)entryPtr->string);
     if (entryPtr->textVarName != NULL) {
        Tcl_UntraceVar(entryPtr->interp, entryPtr->textVarName,
                TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
@@ -1070,12 +1344,25 @@ DestroyEntry(memPtr)
     }
     Tcl_DeleteTimerHandler(entryPtr->insertBlinkHandler);
     if (entryPtr->displayString != entryPtr->string) {
-       ckfree(entryPtr->displayString);
+       ckfree((char *)entryPtr->displayString);
+    }
+    if (entryPtr->type == TK_SPINBOX) {
+       Spinbox *sbPtr = (Spinbox *) entryPtr;
+
+       if (sbPtr->listObj != NULL) {
+           Tcl_DecrRefCount(sbPtr->listObj);
+           sbPtr->listObj = NULL;
+       }
+       if (sbPtr->formatBuf) {
+           ckfree(sbPtr->formatBuf);
+       }
     }
     Tk_FreeTextLayout(entryPtr->textLayout);
     Tk_FreeConfigOptions((char *) entryPtr, entryPtr->optionTable,
            entryPtr->tkwin);
+    Tcl_Release((ClientData) entryPtr->tkwin);
     entryPtr->tkwin = NULL;
+
     ckfree((char *) entryPtr);
 }
 \f
@@ -1110,9 +1397,17 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags)
     int flags;                 /* Flags to pass to Tk_ConfigureWidget. */
 {
     Tk_SavedOptions savedOptions;
+    Tk_3DBorder border;
     Tcl_Obj *errorResult = NULL;
+    Spinbox *sbPtr = (Spinbox *) entryPtr;     /* Only used when this widget
+                                                * is of type TK_SPINBOX */
+    char *oldValues = NULL;            /* lint initialization */
+    char *oldFormat = NULL;            /* lint initialization */
     int error;
-    int oldExport;
+    int oldExport = 0;                 /* lint initialization */
+    int valuesChanged = 0;             /* lint initialization */
+    double oldFrom = 0.0;              /* lint initialization */
+    double oldTo = 0.0;                        /* lint initialization */
 
     /*
      * Eliminate any existing trace on a variable monitored by the entry.
@@ -1124,7 +1419,17 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags)
                EntryTextVarProc, (ClientData) entryPtr);
     }
 
-    oldExport = entryPtr->exportSelection;
+    /*
+     * Store old values that we need to effect certain behavior if
+     * they change value
+     */
+    oldExport          = entryPtr->exportSelection;
+    if (entryPtr->type == TK_SPINBOX) {
+       oldValues       = sbPtr->valueStr;
+       oldFormat       = sbPtr->reqFormat;
+       oldFrom         = sbPtr->fromValue;
+       oldTo           = sbPtr->toValue;
+    }
 
     for (error = 0; error <= 1; error++) {
        if (!error) {
@@ -1152,7 +1457,16 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags)
         * the geometry and setting the background from a 3-D border.
         */
 
-       Tk_SetBackgroundFromBorder(entryPtr->tkwin, entryPtr->normalBorder);
+       if ((entryPtr->state == STATE_DISABLED) &&
+               (entryPtr->disabledBorder != NULL)) {
+           border = entryPtr->disabledBorder;
+       } else if ((entryPtr->state == STATE_READONLY) &&
+               (entryPtr->readonlyBorder != NULL)) {
+           border = entryPtr->readonlyBorder;
+       } else {
+           border = entryPtr->normalBorder;
+       }
+       Tk_SetBackgroundFromBorder(entryPtr->tkwin, border);
 
        if (entryPtr->insertWidth <= 0) {
            entryPtr->insertWidth = 2;
@@ -1161,6 +1475,80 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags)
            entryPtr->insertBorderWidth = entryPtr->insertWidth/2;
        }
 
+       if (entryPtr->type == TK_SPINBOX) {
+           if (sbPtr->fromValue > sbPtr->toValue) {
+               Tcl_SetResult(interp,
+                       "-to value must be greater than -from value",
+                       TCL_VOLATILE);
+               continue;
+           }
+
+           if (sbPtr->reqFormat && (oldFormat != sbPtr->reqFormat)) {
+               /*
+                * Make sure that the given format is somewhat correct, and
+                * calculate the minimum space we'll need for the values as
+                * strings.
+                */
+               int min, max;
+               size_t formatLen, formatSpace = TCL_DOUBLE_SPACE;
+               char fbuf[4], *fmt = sbPtr->reqFormat;
+
+               formatLen = strlen(fmt);
+               if ((fmt[0] != '%') || (fmt[formatLen-1] != 'f')) {
+                   badFormatOpt:
+                   Tcl_AppendResult(interp, "bad spinbox format specifier \"",
+                           sbPtr->reqFormat, "\"", (char *) NULL);
+                   continue;
+               }
+               if ((sscanf(fmt, "%%%d.%d%[f]", &min, &max, fbuf) == 3)
+                       && (max >= 0)) {
+                   formatSpace = min + max + 1;
+               } else if (((sscanf(fmt, "%%.%d%[f]", &min, fbuf) == 2)
+                       || (sscanf(fmt, "%%%d%[f]", &min, fbuf) == 2)
+                       || (sscanf(fmt, "%%%d.%[f]", &min, fbuf) == 2))
+                       && (min >= 0)) {
+                   formatSpace = min + 1;
+               } else {
+                   goto badFormatOpt;
+               }
+               if (formatSpace < TCL_DOUBLE_SPACE) {
+                   formatSpace = TCL_DOUBLE_SPACE;
+               }
+               sbPtr->formatBuf = ckrealloc(sbPtr->formatBuf, formatSpace);
+               /*
+                * We perturb the value of oldFrom to allow us to go into
+                * the branch below that will reformat the displayed value.
+                */
+               oldFrom = sbPtr->fromValue - 1;
+           }
+
+           /*
+            * See if we have to rearrange our listObj data
+            */
+           if (oldValues != sbPtr->valueStr) {
+               if (sbPtr->listObj != NULL) {
+                   Tcl_DecrRefCount(sbPtr->listObj);
+               }
+               sbPtr->listObj = NULL;
+               if (sbPtr->valueStr != NULL) {
+                   Tcl_Obj *newObjPtr;
+                   int nelems;
+
+                   newObjPtr = Tcl_NewStringObj(sbPtr->valueStr, -1);
+                   if (Tcl_ListObjLength(interp, newObjPtr, &nelems)
+                           != TCL_OK) {
+                       valuesChanged = -1;
+                       continue;
+                   }
+                   sbPtr->listObj = newObjPtr;
+                   Tcl_IncrRefCount(sbPtr->listObj);
+                   sbPtr->nElements = nelems;
+                   sbPtr->eIndex = 0;
+                   valuesChanged++;
+               }
+           }
+       }
+
        /*
         * Restart the cursor timing sequence in case the on-time or 
         * off-time just changed.  Set validate temporarily to none,
@@ -1205,20 +1593,67 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags)
     }
 
     /*
-     * If the entry is tied to the value of a variable, then set up
-     * a trace on the variable's value, create the variable if it doesn't
-     * exist, and set the entry's value from the variable's value.
+     * If the entry is tied to the value of a variable, create the variable if
+     * it doesn't exist, and set the entry's value from the variable's value.
      */
 
     if (entryPtr->textVarName != NULL) {
-       char *value;
+       CONST char *value;
 
        value = Tcl_GetVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY);
        if (value == NULL) {
-           EntryValueChanged(entryPtr);
+           EntryValueChanged(entryPtr, NULL);
        } else {
            EntrySetValue(entryPtr, value);
        }
+    }
+
+    if (entryPtr->type == TK_SPINBOX) {
+       ComputeFormat(sbPtr);
+
+       if (valuesChanged > 0) {
+           Tcl_Obj *objPtr;
+
+           /*
+            * No check for error return, because there shouldn't be one
+            * given the check for valid list above
+            */
+           Tcl_ListObjIndex(interp, sbPtr->listObj, 0, &objPtr);
+           EntryValueChanged(entryPtr, Tcl_GetString(objPtr));
+       } else if ((sbPtr->valueStr == NULL)
+               && !DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue)
+               && (!DOUBLES_EQ(sbPtr->fromValue, oldFrom)
+                       || !DOUBLES_EQ(sbPtr->toValue, oldTo))) {
+           /*
+            * If the valueStr is empty and -from && -to are specified, check
+            * to see if the current string is within the range.  If not,
+            * it will be constrained to the nearest edge.  If the current
+            * string isn't a double value, we set it to -from.
+            */
+           int code;
+           double dvalue;
+
+           code = Tcl_GetDouble(NULL, entryPtr->string, &dvalue);
+           if (code != TCL_OK) {
+               dvalue = sbPtr->fromValue;
+           } else {
+               if (dvalue > sbPtr->toValue) {
+                   dvalue = sbPtr->toValue;
+               } else if (dvalue < sbPtr->fromValue) {
+                   dvalue = sbPtr->fromValue;
+               }
+           }
+           sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue);
+           EntryValueChanged(entryPtr, sbPtr->formatBuf);
+       }
+    }
+
+    /*
+     * Set up a trace on the variable's value after we've possibly
+     * constrained the value according to new -from/-to values.
+     */
+
+    if (entryPtr->textVarName != NULL) {
        Tcl_TraceVar(interp, entryPtr->textVarName,
                TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
                EntryTextVarProc, (ClientData) entryPtr);
@@ -1259,20 +1694,52 @@ EntryWorldChanged(instanceData)
     XGCValues gcValues;
     GC gc = None;
     unsigned long mask;
-    Entry *entryPtr;
-
-    entryPtr = (Entry *) instanceData;
+    Tk_3DBorder border;
+    XColor *colorPtr;
+    Entry *entryPtr = (Entry *) instanceData;
 
     entryPtr->avgWidth = Tk_TextWidth(entryPtr->tkfont, "0", 1);
     if (entryPtr->avgWidth == 0) {
        entryPtr->avgWidth = 1;
     }
 
-    if (entryPtr->normalBorder != NULL) {
-       Tk_SetBackgroundFromBorder(entryPtr->tkwin, entryPtr->normalBorder);
-    }
+    if (entryPtr->type == TK_SPINBOX) {
+       /*
+        * Compute the button width for a spinbox
+        */
+
+       entryPtr->xWidth = entryPtr->avgWidth + 2 * (1+XPAD);
+       if (entryPtr->xWidth < 11) {
+           entryPtr->xWidth = 11; /* we want a min visible size */
+       }
+    }
+
+    /*
+     * Default background and foreground are from the normal state.
+     * In a disabled state, both of those may be overridden; in the readonly
+     * state, the background may be overridden.
+     */
 
-    gcValues.foreground = entryPtr->fgColorPtr->pixel;
+    border     = entryPtr->normalBorder;
+    colorPtr   = entryPtr->fgColorPtr;
+    switch (entryPtr->state) {
+       case STATE_DISABLED:
+           if (entryPtr->disabledBorder != NULL) {
+               border = entryPtr->disabledBorder;
+           }
+           if (entryPtr->dfgColorPtr != NULL) {
+               colorPtr = entryPtr->dfgColorPtr;
+           }
+           break;
+       case STATE_READONLY:
+           if (entryPtr->readonlyBorder != NULL) {
+               border = entryPtr->readonlyBorder;
+           }
+           break;
+    }
+
+    Tk_SetBackgroundFromBorder(entryPtr->tkwin, border);
+    gcValues.foreground = colorPtr->pixel;
     gcValues.font = Tk_FontId(entryPtr->tkfont);
     gcValues.graphics_exposures = False;
     mask = GCForeground | GCFont | GCGraphicsExposures;
@@ -1324,13 +1791,13 @@ DisplayEntry(clientData)
     Entry *entryPtr = (Entry *) clientData;
     Tk_Window tkwin = entryPtr->tkwin;
     int baseY, selStartX, selEndX, cursorX;
-    int xBound;
+    int showSelection, xBound;
     Tk_FontMetrics fm;
     Pixmap pixmap;
-    int showSelection;
+    Tk_3DBorder border;
 
     entryPtr->flags &= ~REDRAW_PENDING;
-    if ((entryPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
+    if ((entryPtr->flags & ENTRY_DELETED) || !Tk_IsMapped(tkwin)) {
        return;
     }
 
@@ -1342,7 +1809,20 @@ DisplayEntry(clientData)
 
     if (entryPtr->flags & UPDATE_SCROLLBAR) {
        entryPtr->flags &= ~UPDATE_SCROLLBAR;
+
+        /*
+        * Preserve/Release because updating the scrollbar can have
+        * the side-effect of destroying or unmapping the entry widget.
+        */
+
+       Tcl_Preserve((ClientData) entryPtr);
        EntryUpdateScrollbar(entryPtr);
+
+       if ((entryPtr->flags & ENTRY_DELETED) || !Tk_IsMapped(tkwin)) {
+           Tcl_Release((ClientData) entryPtr);
+           return;
+       }
+       Tcl_Release((ClientData) entryPtr);
     }
 
     /*
@@ -1360,7 +1840,7 @@ DisplayEntry(clientData)
      * one, plus vertical position of baseline of text.
      */
 
-    xBound = Tk_Width(tkwin) - entryPtr->inset;
+    xBound = Tk_Width(tkwin) - entryPtr->inset - entryPtr->xWidth;
     baseY = (Tk_Height(tkwin) + fm.ascent - fm.descent) / 2;
 
     /*
@@ -1380,10 +1860,19 @@ DisplayEntry(clientData)
      * insertion cursor background.
      */
 
-    Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->normalBorder,
-               0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
+    if ((entryPtr->state == STATE_DISABLED) &&
+           (entryPtr->disabledBorder != NULL)) {
+       border = entryPtr->disabledBorder;
+    } else if ((entryPtr->state == STATE_READONLY) &&
+           (entryPtr->readonlyBorder != NULL)) {
+       border = entryPtr->readonlyBorder;
+    } else {
+       border = entryPtr->normalBorder;
+    }
+    Tk_Fill3DRectangle(tkwin, pixmap, border,
+           0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
 
-    if (showSelection
+    if (showSelection && (entryPtr->state != STATE_DISABLED)
            && (entryPtr->selectLast > entryPtr->leftIndex)) {
        if (entryPtr->selectFirst <= entryPtr->leftIndex) {
            selStartX = entryPtr->leftX;
@@ -1414,23 +1903,26 @@ DisplayEntry(clientData)
      * cursor isn't on.  Otherwise the selection would hide the cursor.
      */
 
-    if ((entryPtr->insertPos >= entryPtr->leftIndex)
-           && (entryPtr->state == STATE_NORMAL)
-           && (entryPtr->flags & GOT_FOCUS)) {
+    if ((entryPtr->state == STATE_NORMAL) && (entryPtr->flags & GOT_FOCUS)) {
        Tk_CharBbox(entryPtr->textLayout, entryPtr->insertPos, &cursorX, NULL,
                NULL, NULL);
        cursorX += entryPtr->layoutX;
        cursorX -= (entryPtr->insertWidth)/2;
-       if (cursorX < xBound) {
-           if (entryPtr->flags & CURSOR_ON) {
-               Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->insertBorder,
-                       cursorX, baseY - fm.ascent, entryPtr->insertWidth,
-                       fm.ascent + fm.descent, entryPtr->insertBorderWidth,
-                       TK_RELIEF_RAISED);
-           } else if (entryPtr->insertBorder == entryPtr->selBorder) {
-               Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->normalBorder,
-                       cursorX, baseY - fm.ascent, entryPtr->insertWidth,
-                       fm.ascent + fm.descent, 0, TK_RELIEF_FLAT);
+       Tk_SetCaretPos(entryPtr->tkwin, cursorX, baseY - fm.ascent,
+               fm.ascent + fm.descent);
+       if (entryPtr->insertPos >= entryPtr->leftIndex) {
+           if (cursorX < xBound) {
+               if (entryPtr->flags & CURSOR_ON) {
+                   Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->insertBorder,
+                           cursorX, baseY - fm.ascent, entryPtr->insertWidth,
+                           fm.ascent + fm.descent,
+                           entryPtr->insertBorderWidth,
+                           TK_RELIEF_RAISED);
+               } else if (entryPtr->insertBorder == entryPtr->selBorder) {
+                   Tk_Fill3DRectangle(tkwin, pixmap, border,
+                           cursorX, baseY - fm.ascent, entryPtr->insertWidth,
+                           fm.ascent + fm.descent, 0, TK_RELIEF_FLAT);
+               }
            }
        }
     }
@@ -1444,7 +1936,7 @@ DisplayEntry(clientData)
            entryPtr->textLayout, entryPtr->layoutX, entryPtr->layoutY,
            entryPtr->leftIndex, entryPtr->numChars);
 
-    if (showSelection
+    if (showSelection && (entryPtr->state != STATE_DISABLED)
            && (entryPtr->selTextGC != entryPtr->textGC)
            && (entryPtr->selectFirst < entryPtr->selectLast)) {
        int selFirst;
@@ -1459,29 +1951,106 @@ DisplayEntry(clientData)
                selFirst, entryPtr->selectLast);
     }
 
+    if (entryPtr->type == TK_SPINBOX) {
+       int startx, height, inset, pad, tHeight, xWidth;
+       Spinbox *sbPtr = (Spinbox *) entryPtr;
+
+       /*
+        * Draw the spin button controls.
+        */
+       xWidth = entryPtr->xWidth;
+       pad    = XPAD + 1;
+       inset  = entryPtr->inset - XPAD;
+       startx = Tk_Width(tkwin) - (xWidth + inset);
+       height = (Tk_Height(tkwin) - 2*inset)/2;
+#if 0
+       Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
+               startx, inset, xWidth, height, 1, sbPtr->buRelief);
+       Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
+               startx, inset+height, xWidth, height, 1, sbPtr->bdRelief);
+#else
+       Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
+               startx, inset, xWidth, height, 1,
+               (sbPtr->selElement == SEL_BUTTONUP) ?
+               TK_RELIEF_SUNKEN : TK_RELIEF_RAISED);
+       Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
+               startx, inset+height, xWidth, height, 1,
+               (sbPtr->selElement == SEL_BUTTONDOWN) ?
+               TK_RELIEF_SUNKEN : TK_RELIEF_RAISED);
+#endif
+    
+       xWidth -= 2*pad;
+       /*
+        * Only draw the triangles if we have enough display space
+        */
+       if ((xWidth > 1)) {
+           XPoint points[3];
+           int starty, space, offset;
+
+           space = height - 2*pad;
+           /*
+            * Ensure width of triangle is odd to guarantee a sharp tip
+            */
+           if (!(xWidth % 2)) {
+               xWidth++;
+           }
+           tHeight = (xWidth + 1) / 2;
+           if (tHeight > space) {
+               tHeight = space;
+           }
+           space   = (space - tHeight) / 2;
+           startx += pad;
+           starty  = inset + height - pad - space;
+           offset  = (sbPtr->selElement == SEL_BUTTONUP);
+           /*
+            * The points are slightly different for the up and down arrows
+            * because (for *.x), we need to account for a bug in the way
+            * XFillPolygon draws triangles, and we want to shift
+            * the arrows differently when allowing for depressed behavior.
+            */
+           points[0].x = startx + offset;
+           points[0].y = starty + (offset ? 0 : -1);
+           points[1].x = startx + xWidth/2 + offset;
+           points[1].y = starty - tHeight + (offset ? 0 : -1);
+           points[2].x = startx + xWidth + offset;
+           points[2].y = points[0].y;
+           XFillPolygon(entryPtr->display, pixmap, entryPtr->textGC,
+                   points, 3, Convex, CoordModeOrigin);
+
+           starty = inset + height + pad + space;
+           offset = (sbPtr->selElement == SEL_BUTTONDOWN);
+           points[0].x = startx + 1 + offset;
+           points[0].y = starty + (offset ? 1 : 0);
+           points[1].x = startx + xWidth/2 + offset;
+           points[1].y = starty + tHeight + (offset ? 0 : -1);
+           points[2].x = startx - 1 + xWidth + offset;
+           points[2].y = points[0].y;
+           XFillPolygon(entryPtr->display, pixmap, entryPtr->textGC,
+                   points, 3, Convex, CoordModeOrigin);
+       }
+    }
+
     /*
      * Draw the border and focus highlight last, so they will overwrite
      * any text that extends past the viewable part of the window.
      */
 
+    xBound = entryPtr->highlightWidth;
     if (entryPtr->relief != TK_RELIEF_FLAT) {
-       Tk_Draw3DRectangle(tkwin, pixmap, entryPtr->normalBorder,
-               entryPtr->highlightWidth, entryPtr->highlightWidth,
-               Tk_Width(tkwin) - 2 * entryPtr->highlightWidth,
-               Tk_Height(tkwin) - 2 * entryPtr->highlightWidth,
+       Tk_Draw3DRectangle(tkwin, pixmap, border, xBound, xBound,
+               Tk_Width(tkwin) - 2 * xBound,
+               Tk_Height(tkwin) - 2 * xBound,
                entryPtr->borderWidth, entryPtr->relief);
     }
-    if (entryPtr->highlightWidth != 0) {
+    if (xBound > 0) {
        GC fgGC, bgGC;
 
        bgGC = Tk_GCForColor(entryPtr->highlightBgColorPtr, pixmap);
        if (entryPtr->flags & GOT_FOCUS) {
            fgGC = Tk_GCForColor(entryPtr->highlightColorPtr, pixmap);
-           TkpDrawHighlightBorder(tkwin, fgGC, bgGC, 
-                   entryPtr->highlightWidth, pixmap);
+           TkpDrawHighlightBorder(tkwin, fgGC, bgGC, xBound, pixmap);
        } else {
-           TkpDrawHighlightBorder(tkwin, bgGC, bgGC, 
-                   entryPtr->highlightWidth, pixmap);
+           TkpDrawHighlightBorder(tkwin, bgGC, bgGC, xBound, pixmap);
        }
     }
 
@@ -1527,7 +2096,7 @@ EntryComputeGeometry(entryPtr)
     char *p;
 
     if (entryPtr->displayString != entryPtr->string) {
-       ckfree(entryPtr->displayString);
+       ckfree((char *)entryPtr->displayString);
        entryPtr->displayString = entryPtr->string;
        entryPtr->numDisplayBytes = entryPtr->numBytes;
     }
@@ -1553,15 +2122,15 @@ EntryComputeGeometry(entryPtr)
        size = Tcl_UniCharToUtf(ch, buf);
 
        entryPtr->numDisplayBytes = entryPtr->numChars * size;
-       entryPtr->displayString =
-               (char *) ckalloc((unsigned) (entryPtr->numDisplayBytes + 1));
+       p = (char *) ckalloc((unsigned) (entryPtr->numDisplayBytes + 1));
+       entryPtr->displayString = p;
 
-       p = entryPtr->displayString;
        for (i = entryPtr->numChars; --i >= 0; ) {
            p += Tcl_UniCharToUtf(ch, p);
        }
        *p = '\0';
     }
+
     Tk_FreeTextLayout(entryPtr->textLayout);
     entryPtr->textLayout = Tk_ComputeTextLayout(entryPtr->tkfont,
            entryPtr->displayString, entryPtr->numChars, 0,
@@ -1576,16 +2145,18 @@ EntryComputeGeometry(entryPtr)
      * window unless the entire window is full.
      */
 
-    overflow = totalLength - (Tk_Width(entryPtr->tkwin) - 2*entryPtr->inset);
+    overflow = totalLength -
+       (Tk_Width(entryPtr->tkwin) - 2*entryPtr->inset - entryPtr->xWidth);
     if (overflow <= 0) {
        entryPtr->leftIndex = 0;
        if (entryPtr->justify == TK_JUSTIFY_LEFT) {
            entryPtr->leftX = entryPtr->inset;
        } else if (entryPtr->justify == TK_JUSTIFY_RIGHT) {
            entryPtr->leftX = Tk_Width(entryPtr->tkwin) - entryPtr->inset
-                   - totalLength;
+               - entryPtr->xWidth - totalLength;
        } else {
-           entryPtr->leftX = (Tk_Width(entryPtr->tkwin) - totalLength)/2;
+           entryPtr->leftX = (Tk_Width(entryPtr->tkwin)
+                   - entryPtr->xWidth - totalLength)/2;
        }
        entryPtr->layoutX = entryPtr->leftX;
     } else {
@@ -1622,6 +2193,12 @@ EntryComputeGeometry(entryPtr)
            width = totalLength + 2*entryPtr->inset;
        }
     }
+
+    /*
+     * Add one extra length for the spin buttons
+     */
+    width += entryPtr->xWidth;
+
     Tk_GeometryRequest(entryPtr->tkwin, width, height);
 }
 \f
@@ -1651,7 +2228,8 @@ InsertChars(entryPtr, index, value)
                                 * string). */
 {
     int byteIndex, byteCount, oldChars, charsAdded, newByteCount;
-    char *new, *string;
+    CONST char *string;
+    char *new;
 
     string = entryPtr->string;
     byteIndex = Tcl_UtfAtIndex(string, index) - string;
@@ -1674,7 +2252,7 @@ InsertChars(entryPtr, index, value)
        return;
     }
 
-    ckfree(string);
+    ckfree((char *)string);
     entryPtr->string = new;
 
     /*
@@ -1721,7 +2299,7 @@ InsertChars(entryPtr, index, value)
     if (entryPtr->insertPos >= index) {
        entryPtr->insertPos += charsAdded;
     }
-    EntryValueChanged(entryPtr);
+    EntryValueChanged(entryPtr, NULL);
 }
 \f
 /*
@@ -1748,7 +2326,8 @@ DeleteChars(entryPtr, index, count)
     int count;                 /* How many characters to delete. */
 {
     int byteIndex, byteCount, newByteCount;
-    char *new, *string, *todelete;
+    CONST char *string;
+    char *new, *todelete;
 
     if ((index + count) > entryPtr->numChars) {
        count = entryPtr->numChars - index;
@@ -1780,7 +2359,7 @@ DeleteChars(entryPtr, index, count)
     }
 
     ckfree(todelete);
-    ckfree(entryPtr->string);
+    ckfree((char *)entryPtr->string);
     entryPtr->string = new;
     entryPtr->numChars -= count;
     entryPtr->numBytes -= byteCount;
@@ -1835,7 +2414,7 @@ DeleteChars(entryPtr, index, count)
            entryPtr->insertPos = index;
        }
     }
-    EntryValueChanged(entryPtr);
+    EntryValueChanged(entryPtr, NULL);
 }
 \f
 /*
@@ -1858,10 +2437,14 @@ DeleteChars(entryPtr, index, count)
  */
 
 static void
-EntryValueChanged(entryPtr)
+EntryValueChanged(entryPtr, newValue)
     Entry *entryPtr;           /* Entry whose value just changed. */
+    CONST char *newValue;      /* If this value is not NULL, we first
+                                * force the value of the entry to this */
 {
-    char *newValue;
+    if (newValue != NULL) {
+       EntrySetValue(entryPtr, newValue);
+    }
 
     if (entryPtr->textVarName == NULL) {
        newValue = NULL;
@@ -1916,9 +2499,9 @@ EntryValueChanged(entryPtr)
 static void
 EntrySetValue(entryPtr, value)
     Entry *entryPtr;           /* Entry whose value is to be changed. */
-    char *value;               /* New text to display in entry. */
+    CONST char *value;         /* New text to display in entry. */
 {
-    char *oldSource;
+    CONST char *oldSource;
     int code, valueLen, malloced = 0;
 
     if (strcmp(value, entryPtr->string) == 0) {
@@ -1934,9 +2517,9 @@ EntrySetValue(entryPtr, value)
         * point to volatile memory, like the value of the -textvar
         * which may get freed during validation
         */
-       oldSource = (char *) ckalloc((unsigned) (valueLen + 1));
-       strcpy(oldSource, value);
-       value = oldSource;
+       char *tmp = (char *) ckalloc((unsigned) (valueLen + 1));
+       strcpy(tmp, value);
+       value = tmp;
        malloced = 1;
 
        entryPtr->flags |= VALIDATE_VAR;
@@ -1949,19 +2532,20 @@ EntrySetValue(entryPtr, value)
         */
        if (entryPtr->flags & VALIDATE_ABORT) {
            entryPtr->flags &= ~VALIDATE_ABORT;
-           ckfree(value);
+           ckfree((char *)value);
            return;
        }
     }
 
     oldSource = entryPtr->string;
-    ckfree(entryPtr->string);
+    ckfree((char *)entryPtr->string);
 
     if (malloced) {
        entryPtr->string = value;
     } else {
-       entryPtr->string   = (char *) ckalloc((unsigned) (valueLen + 1));
-       strcpy(entryPtr->string, value);
+       char *tmp = (char *) ckalloc((unsigned) (valueLen + 1));
+       strcpy(tmp, value);
+       entryPtr->string = tmp;
     }
     entryPtr->numBytes = valueLen;
     entryPtr->numChars = Tcl_NumUtfChars(value, valueLen);
@@ -2001,7 +2585,7 @@ EntrySetValue(entryPtr, value)
  * EntryEventProc --
  *
  *     This procedure is invoked by the Tk dispatcher for various
- *     events on entryes.
+ *     events on entries.
  *
  * Results:
  *     None.
@@ -2019,25 +2603,62 @@ EntryEventProc(clientData, eventPtr)
     XEvent *eventPtr;          /* Information about event. */
 {
     Entry *entryPtr = (Entry *) clientData;
-    if (eventPtr->type == Expose) {
-       EventuallyRedraw(entryPtr);
-       entryPtr->flags |= BORDER_NEEDED;
-    } else if (eventPtr->type == DestroyNotify) {
-        DestroyEntry((char *) clientData);
-    } else if (eventPtr->type == ConfigureNotify) {
-       Tcl_Preserve((ClientData) entryPtr);
-       entryPtr->flags |= UPDATE_SCROLLBAR;
-       EntryComputeGeometry(entryPtr);
-       EventuallyRedraw(entryPtr);
-       Tcl_Release((ClientData) entryPtr);
-    } else if (eventPtr->type == FocusIn) {
-       if (eventPtr->xfocus.detail != NotifyInferior) {
-           EntryFocusProc(entryPtr, 1);
-       }
-    } else if (eventPtr->type == FocusOut) {
-       if (eventPtr->xfocus.detail != NotifyInferior) {
-           EntryFocusProc(entryPtr, 0);
+
+    if ((entryPtr->type == TK_SPINBOX) && (eventPtr->type == MotionNotify)) {
+       Spinbox *sbPtr = (Spinbox *) clientData;
+       int elem;
+
+       elem = GetSpinboxElement(sbPtr, eventPtr->xmotion.x,
+               eventPtr->xmotion.y);
+       if (elem != sbPtr->curElement) {
+           Tk_Cursor cursor;
+
+           sbPtr->curElement = elem;
+           if (elem == SEL_ENTRY) {
+               cursor = entryPtr->cursor;
+           } else if ((elem == SEL_BUTTONDOWN) || (elem == SEL_BUTTONUP)) {
+               cursor = sbPtr->bCursor;
+           } else {
+               cursor = None;
+           }
+           if (cursor != None) {
+               Tk_DefineCursor(entryPtr->tkwin, cursor);
+           } else {
+               Tk_UndefineCursor(entryPtr->tkwin);
+           }
        }
+       return;
+    }
+
+    switch (eventPtr->type) {
+       case Expose:
+           EventuallyRedraw(entryPtr);
+           entryPtr->flags |= BORDER_NEEDED;
+           break;
+       case DestroyNotify:
+           if (!(entryPtr->flags & ENTRY_DELETED)) {
+               entryPtr->flags |= (ENTRY_DELETED | VALIDATE_ABORT);
+               Tcl_DeleteCommandFromToken(entryPtr->interp,
+                       entryPtr->widgetCmd);
+               if (entryPtr->flags & REDRAW_PENDING) {
+                   Tcl_CancelIdleCall(DisplayEntry, clientData);
+               }
+               Tcl_EventuallyFree(clientData, DestroyEntry);
+           }
+           break;
+       case ConfigureNotify:
+           Tcl_Preserve((ClientData) entryPtr);
+           entryPtr->flags |= UPDATE_SCROLLBAR;
+           EntryComputeGeometry(entryPtr);
+           EventuallyRedraw(entryPtr);
+           Tcl_Release((ClientData) entryPtr);
+           break;
+       case FocusIn:
+       case FocusOut:
+           if (eventPtr->xfocus.detail != NotifyInferior) {
+               EntryFocusProc(entryPtr, (eventPtr->type == FocusIn));
+           }
+           break;
     }
 }
 \f
@@ -2123,8 +2744,9 @@ GetEntryIndex(interp, entryPtr, string, indexPtr)
             */
 
            Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
-           Tcl_AppendResult(interp, "bad entry index \"", string,
-                   "\"", (char *) NULL);
+           Tcl_AppendResult(interp, "bad ",
+                   (entryPtr->type == TK_ENTRY) ? "entry" : "spinbox",
+                   " index \"", string, "\"", (char *) NULL);
            return TCL_ERROR;
        }
     } else if (string[0] == 'e') {
@@ -2141,7 +2763,9 @@ GetEntryIndex(interp, entryPtr, string, indexPtr)
        }
     } else if (string[0] == 's') {
        if (entryPtr->selectFirst < 0) {
-           Tcl_SetResult(interp, "selection isn't in entry", TCL_STATIC);
+           Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
+           Tcl_AppendResult(interp, "selection isn't in widget ",
+                   Tk_PathName(entryPtr->tkwin), (char *) NULL);
            return TCL_ERROR;
        }
        if (length < 5) {
@@ -2155,7 +2779,7 @@ GetEntryIndex(interp, entryPtr, string, indexPtr)
            goto badIndex;
        }
     } else if (string[0] == '@') {
-       int x, roundUp;
+       int x, roundUp, maxWidth;
 
        if (Tcl_GetInt(interp, string + 1, &x) != TCL_OK) {
            goto badIndex;
@@ -2164,8 +2788,10 @@ GetEntryIndex(interp, entryPtr, string, indexPtr)
            x = entryPtr->inset;
        }
        roundUp = 0;
-       if (x >= (Tk_Width(entryPtr->tkwin) - entryPtr->inset)) {
-           x = Tk_Width(entryPtr->tkwin) - entryPtr->inset - 1;
+       maxWidth = Tk_Width(entryPtr->tkwin) - entryPtr->inset
+           - entryPtr->xWidth - 1;
+       if (x > maxWidth) {
+           x = maxWidth;
            roundUp = 1;
        }
        *indexPtr = Tk_PointToChar(entryPtr->textLayout,
@@ -2191,8 +2817,6 @@ GetEntryIndex(interp, entryPtr, string, indexPtr)
            *indexPtr = entryPtr->numChars;
        } 
     }
-    if(*indexPtr > entryPtr->numChars)
-        *indexPtr = entryPtr->numChars;
     return TCL_OK;
 }
 \f
@@ -2348,7 +2972,8 @@ EntryFetchSelection(clientData, offset, buffer, maxBytes)
 {
     Entry *entryPtr = (Entry *) clientData;
     int byteCount;
-    char *string, *selStart, *selEnd;
+    CONST char *string;
+    CONST char *selStart, *selEnd;
 
     if ((entryPtr->selectFirst < 0) || !(entryPtr->exportSelection)) {
        return -1;
@@ -2433,7 +3058,7 @@ static void
 EventuallyRedraw(entryPtr)
     Entry *entryPtr;           /* Information about widget. */
 {
-    if ((entryPtr->tkwin == NULL) || !Tk_IsMapped(entryPtr->tkwin)) {
+    if ((entryPtr->flags & ENTRY_DELETED) || !Tk_IsMapped(entryPtr->tkwin)) {
        return;
     }
 
@@ -2484,7 +3109,7 @@ EntryVisibleRange(entryPtr, firstPtr, lastPtr)
     } else {
        charsInWindow = Tk_PointToChar(entryPtr->textLayout,
                Tk_Width(entryPtr->tkwin) - entryPtr->inset
-                       - entryPtr->layoutX - 1, 0);
+               - entryPtr->xWidth - entryPtr->layoutX - 1, 0);
        if (charsInWindow < entryPtr->numChars) {
            charsInWindow++;
        }
@@ -2539,7 +3164,9 @@ EntryUpdateScrollbar(entryPtr)
     code = Tcl_VarEval(interp, entryPtr->scrollCmd, args, (char *) NULL);
     if (code != TCL_OK) {
        Tcl_AddErrorInfo(interp,
-               "\n    (horizontal scrolling command executed by entry)");
+               "\n    (horizontal scrolling command executed by ");
+       Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->tkwin));
+       Tcl_AddErrorInfo(interp, ")");
        Tcl_BackgroundError(interp);
     }
     Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
@@ -2571,17 +3198,18 @@ EntryBlinkProc(clientData)
     Entry *entryPtr = (Entry *) clientData;
 
     if ((entryPtr->state == STATE_DISABLED) ||
+           (entryPtr->state == STATE_READONLY) ||
            !(entryPtr->flags & GOT_FOCUS) || (entryPtr->insertOffTime == 0)) {
        return;
     }
     if (entryPtr->flags & CURSOR_ON) {
        entryPtr->flags &= ~CURSOR_ON;
        entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
-               entryPtr->insertOffTime, EntryBlinkProc, (ClientData) entryPtr);
+           entryPtr->insertOffTime, EntryBlinkProc, (ClientData) entryPtr);
     } else {
        entryPtr->flags |= CURSOR_ON;
        entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
-               entryPtr->insertOnTime, EntryBlinkProc, (ClientData) entryPtr);
+           entryPtr->insertOnTime, EntryBlinkProc, (ClientData) entryPtr);
     }
     EventuallyRedraw(entryPtr);
 }
@@ -2660,12 +3288,19 @@ static char *
 EntryTextVarProc(clientData, interp, name1, name2, flags)
     ClientData clientData;     /* Information about button. */
     Tcl_Interp *interp;                /* Interpreter containing variable. */
-    char *name1;               /* Not used. */
-    char *name2;               /* Not used. */
+    CONST char *name1;         /* Not used. */
+    CONST char *name2;         /* Not used. */
     int flags;                 /* Information about what happened. */
 {
     Entry *entryPtr = (Entry *) clientData;
-    char *value;
+    CONST char *value;
+
+    if (entryPtr->flags & ENTRY_DELETED) {
+       /*
+        * Just abort early if we entered here while being deleted.
+        */
+       return (char *) NULL;
+    }
 
     /*
      * If the variable is unset, then immediately recreate it unless
@@ -2729,13 +3364,21 @@ EntryValidate(entryPtr, cmd)
 
     code = Tcl_EvalEx(interp, cmd, -1, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
 
+    /*
+     * We accept TCL_OK and TCL_RETURN as valid return codes from the
+     * command callback.
+     */
     if (code != TCL_OK && code != TCL_RETURN) {
-       Tcl_AddErrorInfo(interp,
-                        "\n\t(in validation command executed by entry)");
+       Tcl_AddErrorInfo(interp, "\n\t(in validation command executed by ");
+       Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->tkwin));
+       Tcl_AddErrorInfo(interp, ")");
        Tcl_BackgroundError(interp);
        return TCL_ERROR;
     }
 
+    /*
+     * The command callback should return an acceptable Tcl boolean.
+     */
     if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp),
                              &bool) != TCL_OK) {
        Tcl_AddErrorInfo(interp,
@@ -2774,7 +3417,7 @@ EntryValidateChange(entryPtr, change, new, index, type)
      register Entry *entryPtr; /* Entry that needs validation. */
      char *change;             /* Characters to be added/deleted
                                 * (NULL-terminated string). */
-     char *new;                 /* Potential new value of entry string */
+     CONST char *new;           /* Potential new value of entry string */
      int index;                 /* index of insert/delete, -1 otherwise */
      int type;                  /* forced, delete, insert,
                                 * focusin or focusout */
@@ -2819,15 +3462,27 @@ EntryValidateChange(entryPtr, change, new, index, type)
      * it means that a loop condition almost occured.  Do not allow
      * this validation result to finish.
      */
+
     if (entryPtr->validate == VALIDATE_NONE
            || (!varValidate && (entryPtr->flags & VALIDATE_VAR))) {
        code = TCL_ERROR;
     }
+
+    /*
+     * It's possible that the user deleted the entry during validation.
+     * In that case, abort future validation and return an error.
+     */
+
+    if (entryPtr->flags & ENTRY_DELETED) {
+       return TCL_ERROR;
+    }
+
     /*
      * If validate will return ERROR, then disallow further validations
      * Otherwise, if it didn't accept the new string (returned TCL_BREAK)
      * then eval the invalidCmd (if it's set)
      */
+
     if (code == TCL_ERROR) {
        entryPtr->validate = VALIDATE_NONE;
     } else if (code == TCL_BREAK) {
@@ -2839,6 +3494,7 @@ EntryValidateChange(entryPtr, change, new, index, type)
         * may want to do entry manipulation which the setting of the
         * var will later wipe anyway.
         */
+
        if (varValidate) {
            entryPtr->validate = VALIDATE_NONE;
        } else if (entryPtr->invalidCmd != NULL) {
@@ -2856,6 +3512,15 @@ EntryValidateChange(entryPtr, change, new, index, type)
                entryPtr->validate = VALIDATE_NONE;
            }
            Tcl_DStringFree(&script);
+
+           /*
+            * It's possible that the user deleted the entry during validation.
+            * In that case, abort future validation and return an error.
+            */
+
+           if (entryPtr->flags & ENTRY_DELETED) {
+               return TCL_ERROR;
+           }
        }
     }
 
@@ -2886,11 +3551,12 @@ EntryValidateChange(entryPtr, change, new, index, type)
 static void
 ExpandPercents(entryPtr, before, change, new, index, type, dsPtr)
      register Entry *entryPtr; /* Entry that needs validation. */
-     register char *before;    /* Command containing percent
+     register CONST char *before;
+                               /* Command containing percent
                                 * expressions to be replaced. */
      char *change;             /* Characters to added/deleted
                                 * (NULL-terminated string). */
-     char *new;                        /* Potential new value of entry string */
+     CONST char *new;          /* Potential new value of entry string */
      int index;                        /* index of insert/delete */
      int type;                 /* INSERT or DELETE */
      Tcl_DString *dsPtr;       /* Dynamic string in which to append
@@ -2899,7 +3565,7 @@ ExpandPercents(entryPtr, before, change, new, index, type, dsPtr)
     int spaceNeeded, cvtFlags; /* Used to substitute string as proper Tcl
                                 * list element. */
     int number, length;
-    register char *string;
+    register CONST char *string;
     Tcl_UniChar ch;
     char numStorage[2*TCL_INTEGER_SPACE];
 
@@ -2933,60 +3599,85 @@ ExpandPercents(entryPtr, before, change, new, index, type, dsPtr)
        } else {
            ch = '%';
        }
-       switch (ch) {
-       case 'd': /* Type of call that caused validation */
-           switch (type) {
-           case VALIDATE_INSERT:
-               number = 1;
-               break;
-           case VALIDATE_DELETE:
-               number = 0;
-               break;
-           default:
-               number = -1;
-               break;
-           }
-           sprintf(numStorage, "%d", number);
-           string = numStorage;
-           break;
-       case 'i': /* index of insert/delete */
-           sprintf(numStorage, "%d", index);
-           string = numStorage;
-           break;
-       case 'P': /* 'Peeked' new value of the string */
-           string = new;
-           break;
-       case 's': /* Current string value of entry */
-           string = entryPtr->string;
-           break;
-       case 'S': /* string to be inserted/deleted, if any */
-           string = change;
-           break;
-       case 'v': /* type of validation currently set */
-           string = validateStrings[entryPtr->validate];
-           break;
-       case 'V': /* type of validation in effect */
-           switch (type) {
-           case VALIDATE_INSERT:
-           case VALIDATE_DELETE:
-               string = validateStrings[VALIDATE_KEY];
-               break;
-           case VALIDATE_FORCED:
-               string = "forced";
-               break;
-           default:
-               string = validateStrings[type];
-               break;
+       if (type == VALIDATE_BUTTON) {
+           /*
+            * -command %-substitution
+            */
+           switch (ch) {
+               case 's': /* Current string value of spinbox */
+                   string = entryPtr->string;
+                   break;
+               case 'd': /* direction, up or down */
+                   string = change;
+                   break;
+               case 'W': /* widget name */
+                   string = Tk_PathName(entryPtr->tkwin);
+                   break;
+               default:
+                   length = Tcl_UniCharToUtf(ch, numStorage);
+                   numStorage[length] = '\0';
+                   string = numStorage;
+                   break;
+           }
+       } else {
+           /*
+            * -validatecommand / -invalidcommand %-substitution
+            */
+           switch (ch) {
+               case 'd': /* Type of call that caused validation */
+                   switch (type) {
+                       case VALIDATE_INSERT:
+                           number = 1;
+                           break;
+                       case VALIDATE_DELETE:
+                           number = 0;
+                           break;
+                       default:
+                           number = -1;
+                           break;
+                   }
+                   sprintf(numStorage, "%d", number);
+                   string = numStorage;
+                   break;
+               case 'i': /* index of insert/delete */
+                   sprintf(numStorage, "%d", index);
+                   string = numStorage;
+                   break;
+               case 'P': /* 'Peeked' new value of the string */
+                   string = new;
+                   break;
+               case 's': /* Current string value of spinbox */
+                   string = entryPtr->string;
+                   break;
+               case 'S': /* string to be inserted/deleted, if any */
+                   string = change;
+                   break;
+               case 'v': /* type of validation currently set */
+                   string = validateStrings[entryPtr->validate];
+                   break;
+               case 'V': /* type of validation in effect */
+                   switch (type) {
+                       case VALIDATE_INSERT:
+                       case VALIDATE_DELETE:
+                           string = validateStrings[VALIDATE_KEY];
+                           break;
+                       case VALIDATE_FORCED:
+                           string = "forced";
+                           break;
+                       default:
+                           string = validateStrings[type];
+                           break;
+                   }
+                   break;
+               case 'W': /* widget name */
+                   string = Tk_PathName(entryPtr->tkwin);
+                   break;
+               default:
+                   length = Tcl_UniCharToUtf(ch, numStorage);
+                   numStorage[length] = '\0';
+                   string = numStorage;
+                   break;
            }
-           break;
-       case 'W': /* widget name */
-           string = Tk_PathName(entryPtr->tkwin);
-           break;
-       default:
-           length = Tcl_UniCharToUtf(ch, numStorage);
-           numStorage[length] = '\0';
-           string = numStorage;
-           break;
        }
 
        spaceNeeded = Tcl_ScanCountedElement(string, -1, &cvtFlags);
@@ -2998,4 +3689,939 @@ ExpandPercents(entryPtr, before, change, new, index, type, dsPtr)
        Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
     }
 }
+\f
+/*
+ *--------------------------------------------------------------
+ *
+ * Tk_SpinboxObjCmd --
+ *
+ *     This procedure is invoked to process the "spinbox" Tcl
+ *     command.  See the user documentation for details on what
+ *     it does.
+ *
+ * Results:
+ *     A standard Tcl result.
+ *
+ * Side effects:
+ *     See the user documentation.
+ *
+ *--------------------------------------------------------------
+ */
+
+int
+Tk_SpinboxObjCmd(clientData, interp, objc, objv)
+    ClientData clientData;     /* NULL. */
+    Tcl_Interp *interp;                /* Current interpreter. */
+    int objc;                  /* Number of arguments. */
+    Tcl_Obj *CONST objv[];      /* Argument objects. */
+{
+    register Entry *entryPtr;
+    register Spinbox *sbPtr;
+    Tk_OptionTable optionTable;
+    Tk_Window tkwin;
+    char *tmp;
+
+    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 the option table for this widget class.  If it has already
+     * been created, Tk will return the cached value.
+     */
+
+    optionTable = Tk_CreateOptionTable(interp, sbOptSpec);
+
+    /*
+     * Initialize the fields of the structure that won't be initialized
+     * by ConfigureEntry, or that ConfigureEntry requires to be
+     * initialized already (e.g. resource pointers).  Only the non-NULL/0
+     * data must be initialized as memset covers the rest.
+     */
+
+    sbPtr                      = (Spinbox *) ckalloc(sizeof(Spinbox));
+    entryPtr                   = (Entry *) sbPtr;
+    memset((VOID *) sbPtr, 0, sizeof(Spinbox));
+
+    entryPtr->tkwin            = tkwin;
+    entryPtr->display          = Tk_Display(tkwin);
+    entryPtr->interp           = interp;
+    entryPtr->widgetCmd                = Tcl_CreateObjCommand(interp,
+           Tk_PathName(entryPtr->tkwin), SpinboxWidgetObjCmd,
+           (ClientData) sbPtr, EntryCmdDeletedProc);
+    entryPtr->optionTable      = optionTable;
+    entryPtr->type             = TK_SPINBOX;
+    tmp                                = (char *) ckalloc(1);
+    tmp[0]                     = '\0';
+    entryPtr->string           = tmp;
+    entryPtr->selectFirst      = -1;
+    entryPtr->selectLast       = -1;
+
+    entryPtr->cursor           = None;
+    entryPtr->exportSelection  = 1;
+    entryPtr->justify          = TK_JUSTIFY_LEFT;
+    entryPtr->relief           = TK_RELIEF_FLAT;
+    entryPtr->state            = STATE_NORMAL;
+    entryPtr->displayString    = entryPtr->string;
+    entryPtr->inset            = XPAD;
+    entryPtr->textGC           = None;
+    entryPtr->selTextGC                = None;
+    entryPtr->highlightGC      = None;
+    entryPtr->avgWidth         = 1;
+    entryPtr->validate         = VALIDATE_NONE;
+
+    sbPtr->selElement          = SEL_NONE;
+    sbPtr->curElement          = SEL_NONE;
+    sbPtr->bCursor             = None;
+    sbPtr->repeatDelay         = 400;
+    sbPtr->repeatInterval      = 100;
+    sbPtr->fromValue           = 0.0;
+    sbPtr->toValue             = 100.0;
+    sbPtr->increment           = 1.0;
+    sbPtr->formatBuf           = (char *) ckalloc(TCL_DOUBLE_SPACE);
+    sbPtr->bdRelief            = TK_RELIEF_FLAT;
+    sbPtr->buRelief            = TK_RELIEF_FLAT;
+
+    /*
+     * 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) entryPtr->tkwin);
+
+    Tk_SetClass(entryPtr->tkwin, "Spinbox");
+    Tk_SetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr);
+    Tk_CreateEventHandler(entryPtr->tkwin,
+           PointerMotionMask|ExposureMask|StructureNotifyMask|FocusChangeMask,
+           EntryEventProc, (ClientData) entryPtr);
+    Tk_CreateSelHandler(entryPtr->tkwin, XA_PRIMARY, XA_STRING,
+           EntryFetchSelection, (ClientData) entryPtr, XA_STRING);
+
+    if (Tk_InitOptions(interp, (char *) sbPtr, optionTable, tkwin)
+           != TCL_OK) {
+       Tk_DestroyWindow(entryPtr->tkwin);
+       return TCL_ERROR;
+    }
+    if (ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0) != TCL_OK) {
+       goto error;
+    }
+    
+    Tcl_SetResult(interp, Tk_PathName(entryPtr->tkwin), TCL_STATIC);
+    return TCL_OK;
+
+    error:
+    Tk_DestroyWindow(entryPtr->tkwin);
+    return TCL_ERROR;
+}
+\f
+/*
+ *--------------------------------------------------------------
+ *
+ * SpinboxWidgetObjCmd --
+ *
+ *     This 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
+SpinboxWidgetObjCmd(clientData, interp, objc, objv)
+    ClientData clientData;     /* Information about spinbox widget. */
+    Tcl_Interp *interp;                /* Current interpreter. */
+    int objc;                  /* Number of arguments. */
+    Tcl_Obj *CONST objv[];     /* Argument objects. */
+{
+    Entry *entryPtr = (Entry *) clientData;
+    Spinbox *sbPtr = (Spinbox *) clientData;
+    int cmdIndex, selIndex, result;
+    Tcl_Obj *objPtr;
 
+    if (objc < 2) {
+       Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
+       return TCL_ERROR;
+    }
+
+    /*
+     * Parse the widget command by looking up the second token in
+     * the list of valid command names. 
+     */
+
+    result = Tcl_GetIndexFromObj(interp, objv[1], sbCmdNames,
+           "option", 0, &cmdIndex);
+    if (result != TCL_OK) {
+       return result;
+    }
+
+    Tcl_Preserve((ClientData) entryPtr);
+    switch ((enum sbCmd) cmdIndex) {
+        case SB_CMD_BBOX: {
+           int index, x, y, width, height;
+           char buf[TCL_INTEGER_SPACE * 4];
+
+           if (objc != 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "index");
+               goto error;
+           }
+           if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), 
+                    &index) != TCL_OK) {
+               goto error;
+           }
+           if ((index == entryPtr->numChars) && (index > 0)) {
+               index--;
+           }
+           Tk_CharBbox(entryPtr->textLayout, index, &x, &y, 
+                    &width, &height);
+           sprintf(buf, "%d %d %d %d", x + entryPtr->layoutX,
+                   y + entryPtr->layoutY, width, height);
+           Tcl_SetResult(interp, buf, TCL_VOLATILE);
+           break;
+       } 
+       
+        case SB_CMD_CGET: {
+           if (objc != 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "option");
+               goto error;
+           }
+           
+           objPtr = Tk_GetOptionValue(interp, (char *) entryPtr,
+                   entryPtr->optionTable, objv[2], entryPtr->tkwin);
+           if (objPtr == NULL) {
+                goto error;
+           } else {
+               Tcl_SetObjResult(interp, objPtr);
+           }
+           break;
+       }
+
+        case SB_CMD_CONFIGURE: {
+           if (objc <= 3) {
+               objPtr = Tk_GetOptionInfo(interp, (char *) entryPtr,
+                       entryPtr->optionTable,
+                       (objc == 3) ? objv[2] : (Tcl_Obj *) NULL,
+                       entryPtr->tkwin);
+               if (objPtr == NULL) {
+                   goto error;
+               } else {
+                   Tcl_SetObjResult(interp, objPtr);
+               }
+           } else {
+               result = ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0);
+           }
+           break;
+       }
+
+        case SB_CMD_DELETE: {
+           int first, last;
+
+           if ((objc < 3) || (objc > 4)) {
+               Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
+               goto error;
+           }
+           if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), 
+                    &first) != TCL_OK) {
+               goto error;
+           }
+           if (objc == 3) {
+               last = first + 1;
+           } else {
+               if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[3]), 
+                        &last) != TCL_OK) {
+                   goto error;
+               }
+           }
+           if ((last >= first) && (entryPtr->state == STATE_NORMAL)) {
+               DeleteChars(entryPtr, first, last - first);
+           }
+           break;
+       }
+
+        case SB_CMD_GET: {
+           if (objc != 2) {
+               Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
+               goto error;
+           }
+           Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1);
+           break;
+       }
+
+        case SB_CMD_ICURSOR: {
+           if (objc != 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "pos");
+               goto error;
+           }
+           if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
+                    &entryPtr->insertPos) != TCL_OK) {
+               goto error;
+           }
+           EventuallyRedraw(entryPtr);
+           break;
+       }
+       
+        case SB_CMD_IDENTIFY: {
+           int x, y, elem;
+
+           if (objc != 4) {
+               Tcl_WrongNumArgs(interp, 2, objv, "x y");
+               goto error;
+           }
+           if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) ||
+                   (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
+               goto error;
+           }
+           elem = GetSpinboxElement(sbPtr, x, y);
+           if (elem != SEL_NONE) {
+               Tcl_SetStringObj(Tcl_GetObjResult(interp),
+                       selElementNames[elem], -1);
+           }
+           break;
+       }
+       
+        case SB_CMD_INDEX: {
+           int index;
+
+           if (objc != 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "string");
+               goto error;
+           }
+           if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), 
+                    &index) != TCL_OK) {
+               goto error;
+           }
+           Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
+           break;
+       }
+
+        case SB_CMD_INSERT: {
+           int index;
+
+           if (objc != 4) {
+               Tcl_WrongNumArgs(interp, 2, objv, "index text");
+               goto error;
+           }
+           if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), 
+                    &index) != TCL_OK) {
+               goto error;
+           }
+           if (entryPtr->state == STATE_NORMAL) {
+               InsertChars(entryPtr, index, Tcl_GetString(objv[3]));
+           }
+           break;
+       }
+
+        case SB_CMD_INVOKE: {
+           if (objc != 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "elemName");
+               goto error;
+           }
+           result = Tcl_GetIndexFromObj(interp, objv[2],
+                   selElementNames, "element", 0, &cmdIndex);
+           if (result != TCL_OK) {
+               goto error;
+           }
+           if (entryPtr->state != STATE_DISABLED) {
+               if (SpinboxInvoke(interp, sbPtr, cmdIndex) != TCL_OK) {
+                   goto error;
+               }
+           }
+           break;
+       }
+
+        case SB_CMD_SCAN: {
+           int x;
+           char *minorCmd;
+
+           if (objc != 4) {
+               Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x");
+               goto error;
+           }
+           if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
+               goto error;
+           }
+
+           minorCmd = Tcl_GetString(objv[2]);
+           if (minorCmd[0] == 'm' 
+                    && (strncmp(minorCmd, "mark", strlen(minorCmd)) == 0)) {
+               entryPtr->scanMarkX = x;
+               entryPtr->scanMarkIndex = entryPtr->leftIndex;
+           } else if ((minorCmd[0] == 'd')
+               && (strncmp(minorCmd, "dragto", strlen(minorCmd)) == 0)) {
+               EntryScanTo(entryPtr, x);
+           } else {
+               Tcl_AppendResult(interp, "bad scan option \"", 
+                        Tcl_GetString(objv[2]), "\": must be mark or dragto", 
+                        (char *) NULL);
+               goto error;
+           }
+           break;
+       }
+
+       case SB_CMD_SELECTION: {
+           int index, index2;
+
+           if (objc < 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "option ?index?");
+               goto error;
+           }
+
+           /* 
+            * Parse the selection sub-command, using the command
+            * table "sbSelCmdNames" defined above.
+            */
+           
+           result = Tcl_GetIndexFromObj(interp, objv[2], sbSelCmdNames,
+                    "selection option", 0, &selIndex);
+           if (result != TCL_OK) {
+               goto error;
+           }
+
+           /*
+            * Disabled entries don't allow the selection to be modified.
+            */
+
+           if (entryPtr->state == STATE_DISABLED) {
+               goto done;
+           }
+           
+           switch(selIndex) {
+               case SB_SEL_ADJUST: {
+                   if (objc != 4) {
+                       Tcl_WrongNumArgs(interp, 3, objv, "index");
+                       goto error;
+                   }
+                   if (GetEntryIndex(interp, entryPtr, 
+                            Tcl_GetString(objv[3]), &index) != TCL_OK) {
+                       goto error;
+                   }
+                   if (entryPtr->selectFirst >= 0) {
+                       int half1, half2;
+               
+                       half1 = (entryPtr->selectFirst 
+                               + entryPtr->selectLast)/2;
+                       half2 = (entryPtr->selectFirst 
+                               + entryPtr->selectLast + 1)/2;
+                       if (index < half1) {
+                           entryPtr->selectAnchor = entryPtr->selectLast;
+                       } else if (index > half2) {
+                           entryPtr->selectAnchor = entryPtr->selectFirst;
+                       } else {
+                         /*
+                          * We're at about the halfway point in the 
+                          * selection; just keep the existing anchor.
+                          */
+                       }
+                   }
+                   EntrySelectTo(entryPtr, index);
+                   break;
+               }
+
+               case SB_SEL_CLEAR: {
+                   if (objc != 3) {
+                       Tcl_WrongNumArgs(interp, 3, objv, (char *) NULL);
+                       goto error;
+                   }
+                   if (entryPtr->selectFirst >= 0) {
+                       entryPtr->selectFirst = -1;
+                       entryPtr->selectLast = -1;
+                       EventuallyRedraw(entryPtr);
+                   }
+                   goto done;
+               }
+
+               case SB_SEL_FROM: {
+                   if (objc != 4) {
+                       Tcl_WrongNumArgs(interp, 3, objv, "index");
+                       goto error;
+                   }
+                   if (GetEntryIndex(interp, entryPtr, 
+                            Tcl_GetString(objv[3]), &index) != TCL_OK) {
+                       goto error;
+                   }
+                   entryPtr->selectAnchor = index;
+                   break;
+               }
+
+               case SB_SEL_PRESENT: {
+                   if (objc != 3) {
+                       Tcl_WrongNumArgs(interp, 3, objv, (char *) NULL);
+                       goto error;
+                   }
+                   Tcl_SetObjResult(interp,
+                           Tcl_NewBooleanObj((entryPtr->selectFirst >= 0)));
+                   goto done;
+               }
+
+               case SB_SEL_RANGE: {
+                   if (objc != 5) {
+                       Tcl_WrongNumArgs(interp, 3, objv, "start end");
+                       goto error;
+                   }
+                   if (GetEntryIndex(interp, entryPtr, 
+                            Tcl_GetString(objv[3]), &index) != TCL_OK) {
+                       goto error;
+                   }
+                   if (GetEntryIndex(interp, entryPtr, 
+                            Tcl_GetString(objv[4]),& index2) != TCL_OK) {
+                       goto error;
+                   }
+                   if (index >= index2) {
+                       entryPtr->selectFirst = -1;
+                       entryPtr->selectLast = -1;
+                   } else {
+                       entryPtr->selectFirst = index;
+                       entryPtr->selectLast = index2;
+                   }
+                   if (!(entryPtr->flags & GOT_SELECTION)
+                           && (entryPtr->exportSelection)) {
+                       Tk_OwnSelection(entryPtr->tkwin, XA_PRIMARY, 
+                               EntryLostSelection, (ClientData) entryPtr);
+                       entryPtr->flags |= GOT_SELECTION;
+                   }
+                   EventuallyRedraw(entryPtr);
+                   break;
+               }
+               
+               case SB_SEL_TO: {
+                   if (objc != 4) {
+                       Tcl_WrongNumArgs(interp, 3, objv, "index");
+                       goto error;
+                   }
+                   if (GetEntryIndex(interp, entryPtr, 
+                            Tcl_GetString(objv[3]), &index) != TCL_OK) {
+                       goto error;
+                   }
+                   EntrySelectTo(entryPtr, index);
+                   break;
+               }
+
+               case SB_SEL_ELEMENT: {
+                   if ((objc < 3) || (objc > 4)) {
+                       Tcl_WrongNumArgs(interp, 3, objv, "?elemName?");
+                       goto error;
+                   }
+                   if (objc == 3) {
+                       Tcl_SetStringObj(Tcl_GetObjResult(interp),
+                               selElementNames[sbPtr->selElement], -1);
+                   } else {
+                       int lastElement = sbPtr->selElement;
+
+                       result = Tcl_GetIndexFromObj(interp, objv[3],
+                               selElementNames, "selection element", 0,
+                               &(sbPtr->selElement));
+                       if (result != TCL_OK) {
+                           goto error;
+                       }
+                       if (lastElement != sbPtr->selElement) {
+                           EventuallyRedraw(entryPtr);
+                       }
+                   }
+                   break;
+               }
+           }
+           break;
+       }
+
+        case SB_CMD_SET: {
+           if (objc > 3) {
+               Tcl_WrongNumArgs(interp, 2, objv, "?string?");
+               goto error;
+           }
+           if (objc == 3) {
+               EntryValueChanged(entryPtr, Tcl_GetString(objv[2]));
+           }
+           Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1);
+           break;
+       }
+
+        case SB_CMD_VALIDATE: {
+           int code;
+
+           if (objc != 2) {
+               Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL);
+               goto error;
+           }
+           selIndex = entryPtr->validate;
+           entryPtr->validate = VALIDATE_ALL;
+           code = EntryValidateChange(entryPtr, (char *) NULL,
+                   entryPtr->string, -1, VALIDATE_FORCED);
+           if (entryPtr->validate != VALIDATE_NONE) {
+               entryPtr->validate = selIndex;
+           }
+           Tcl_SetObjResult(interp, Tcl_NewBooleanObj((code == TCL_OK)));
+           break;
+       }
+
+        case SB_CMD_XVIEW: {
+           int index;
+
+           if (objc == 2) {
+               double first, last;
+               char buf[TCL_DOUBLE_SPACE * 2];
+           
+               EntryVisibleRange(entryPtr, &first, &last);
+               sprintf(buf, "%g %g", first, last);
+               Tcl_SetResult(interp, buf, TCL_VOLATILE);
+               goto done;
+           } else if (objc == 3) {
+               if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), 
+                        &index) != TCL_OK) {
+                   goto error;
+               }
+           } else {
+               double fraction;
+               int count;
+
+               index = entryPtr->leftIndex;
+               switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, 
+                        &count)) {
+                   case TK_SCROLL_ERROR: {
+                       goto error;
+                   }
+                   case TK_SCROLL_MOVETO: {
+                       index = (int) ((fraction * entryPtr->numChars) + 0.5);
+                       break;
+                   }
+                   case TK_SCROLL_PAGES: {
+                       int charsPerPage;
+                   
+                       charsPerPage = ((Tk_Width(entryPtr->tkwin)
+                               - 2 * entryPtr->inset - entryPtr->xWidth) 
+                                / entryPtr->avgWidth) - 2;
+                       if (charsPerPage < 1) {
+                           charsPerPage = 1;
+                       }
+                       index += count * charsPerPage;
+                       break;
+                   }
+                   case TK_SCROLL_UNITS: {
+                       index += count;
+                       break;
+                   }
+               }
+           }
+           if (index >= entryPtr->numChars) {
+               index = entryPtr->numChars - 1;
+           }
+           if (index < 0) {
+               index = 0;
+           }
+           entryPtr->leftIndex = index;
+           entryPtr->flags |= UPDATE_SCROLLBAR;
+           EntryComputeGeometry(entryPtr);
+           EventuallyRedraw(entryPtr);
+           break;
+       }
+    }
+
+    done:
+    Tcl_Release((ClientData) entryPtr);
+    return result;
+
+    error:
+    Tcl_Release((ClientData) entryPtr);
+    return TCL_ERROR;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * GetSpinboxElement --
+ *
+ *     Return the element associated with an x,y coord.
+ *
+ * Results:
+ *     Element type as enum selelement.
+ *
+ * Side effects:
+ *     None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static int
+GetSpinboxElement(sbPtr, x, y)
+    Spinbox *sbPtr;            /* Spinbox for which the index is being
+                                * specified. */
+    int x;                     /* x coord */
+    int y;                     /* y coord */
+{
+    Entry *entryPtr = (Entry *) sbPtr;
+
+    if ((x < 0) || (y < 0) || (y > Tk_Height(entryPtr->tkwin))
+           || (x > Tk_Width(entryPtr->tkwin))) {
+       return SEL_NONE;
+    }
+
+    if (x > (Tk_Width(entryPtr->tkwin) - entryPtr->inset - entryPtr->xWidth)) {
+       if (y > (Tk_Height(entryPtr->tkwin) / 2)) {
+           return SEL_BUTTONDOWN;
+       } else {
+           return SEL_BUTTONUP;
+       }
+    }
+    return SEL_ENTRY;
+}
+\f
+/*
+ *--------------------------------------------------------------
+ *
+ * SpinboxInvoke --
+ *
+ *     This procedure is invoked when the invoke method for the
+ *     widget is called.
+ *
+ * Results:
+ *     TCL_OK.
+ *
+ * Side effects:
+ *      An background error condition may arise when invoking the
+ *     callback.  The widget value may change.
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+SpinboxInvoke(interp, sbPtr, element)
+    register Tcl_Interp *interp;       /* Current interpreter. */
+    register Spinbox *sbPtr;           /* Spinbox to invoke. */
+    int element;                       /* element to invoke, either the "up"
+                                        * or "down" button. */
+{
+    Entry *entryPtr = (Entry *) sbPtr;
+    char *type;
+    int code, up;
+    Tcl_DString script;
+
+    switch (element) {
+       case SEL_BUTTONUP:
+           type = "up";
+           up = 1;
+           break;
+       case SEL_BUTTONDOWN:
+           type = "down";
+           up = 0;
+           break;
+       default:
+           return TCL_OK;
+    }
+
+    if (fabs(sbPtr->increment) > MIN_DBL_VAL) {
+       if (sbPtr->listObj != NULL) {
+           Tcl_Obj *objPtr;
+
+           Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr);
+           if (strcmp(Tcl_GetString(objPtr), entryPtr->string)) {
+               /*
+                * Somehow the string changed from what we expected,
+                * so let's do a search on the list to see if the current
+                * value is there.  If not, move to the first element of
+                * the list.
+                */
+               int i, listc, elemLen, length = entryPtr->numChars;
+               char *bytes;
+               Tcl_Obj **listv;
+
+               Tcl_ListObjGetElements(interp, sbPtr->listObj, &listc, &listv);
+               for (i = 0; i < listc; i++) {
+                   bytes = Tcl_GetStringFromObj(listv[i], &elemLen);
+                   if ((length == elemLen) &&
+                           (memcmp(bytes, entryPtr->string,
+                                   (size_t) length) == 0)) {
+                       sbPtr->eIndex = i;
+                       break;
+                   }
+               }
+           }
+           if (up) {
+               if (++sbPtr->eIndex >= sbPtr->nElements) {
+                   if (sbPtr->wrap) {
+                       sbPtr->eIndex = 0;
+                   } else {
+                       sbPtr->eIndex = sbPtr->nElements-1;
+                   }
+               }
+           } else {
+               if (--sbPtr->eIndex < 0) {
+                   if (sbPtr->wrap) {
+                       sbPtr->eIndex = sbPtr->nElements-1;
+                   } else {
+                       sbPtr->eIndex = 0;
+                   }
+               }
+           }
+           Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr);
+           EntryValueChanged(entryPtr, Tcl_GetString(objPtr));
+       } else if (!DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue)) {
+           double dvalue;
+
+           if (Tcl_GetDouble(NULL, entryPtr->string, &dvalue) != TCL_OK) {
+               /*
+                * If the string is empty, or isn't a valid double value,
+                * just use the -from value
+                */
+               dvalue = sbPtr->fromValue;
+           } else {
+               if (up) {
+                   dvalue += sbPtr->increment;
+                   if (dvalue > sbPtr->toValue) {
+                       if (sbPtr->wrap) {
+                           dvalue = sbPtr->fromValue;
+                       } else {
+                           dvalue = sbPtr->toValue;
+                       }
+                   } else if (dvalue < sbPtr->fromValue) {
+                       /*
+                        * It's possible that when pressing up, we are
+                        * still less than the fromValue, because the
+                        * user may have manipulated the value by hand.
+                        */
+                       dvalue = sbPtr->fromValue;
+                   }
+               } else {
+                   dvalue -= sbPtr->increment;
+                   if (dvalue < sbPtr->fromValue) {
+                       if (sbPtr->wrap) {
+                           dvalue = sbPtr->toValue;
+                       } else {
+                           dvalue = sbPtr->fromValue;
+                       }
+                   } else if (dvalue > sbPtr->toValue) {
+                       /*
+                        * It's possible that when pressing down, we are
+                        * still greater than the toValue, because the
+                        * user may have manipulated the value by hand.
+                        */
+                       dvalue = sbPtr->toValue;
+                   }
+               }
+           }
+           sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue);
+           EntryValueChanged(entryPtr, sbPtr->formatBuf);
+       }
+    }
+
+    if (sbPtr->command != NULL) {
+       Tcl_DStringInit(&script);
+       ExpandPercents(entryPtr, sbPtr->command, type, "", 0,
+               VALIDATE_BUTTON, &script);
+       Tcl_DStringAppend(&script, "", 1);
+
+       code = Tcl_EvalEx(interp, Tcl_DStringValue(&script), -1,
+               TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
+       Tcl_DStringFree(&script);
+
+       if (code != TCL_OK) {
+           Tcl_AddErrorInfo(interp, "\n\t(in command executed by spinbox)");
+           Tcl_BackgroundError(interp);
+           /*
+            * Yes, it's an error, but a bg one, so we return OK
+            */
+           return TCL_OK;
+       }
+
+       Tcl_SetResult(interp, NULL, 0);
+    }
+
+    return TCL_OK;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * ComputeFormat --
+ *
+ *     This procedure is invoked to recompute the "format" fields
+ *     of a spinbox's widget record, which determines how the value
+ *     of the dial is converted to a string.
+ *
+ * Results:
+ *     Tcl result code.
+ *
+ * Side effects:
+ *     The format fields of the spinbox are modified.
+ *
+ *----------------------------------------------------------------------
+ */
+static int
+ComputeFormat(sbPtr)
+     Spinbox *sbPtr;                   /* Information about dial widget. */
+{
+    double maxValue, x;
+    int mostSigDigit, numDigits, leastSigDigit, afterDecimal;
+    int eDigits, fDigits;
+
+    /*
+     * Compute the displacement from the decimal of the most significant
+     * digit required for any number in the dial's range.
+     */
+
+    if (sbPtr->reqFormat) {
+       sbPtr->valueFormat = sbPtr->reqFormat;
+       return TCL_OK;
+    }
+
+    maxValue = fabs(sbPtr->fromValue);
+    x = fabs(sbPtr->toValue);
+    if (x > maxValue) {
+       maxValue = x;
+    }
+    if (maxValue == 0) {
+       maxValue = 1;
+    }
+    mostSigDigit = (int) floor(log10(maxValue));
+
+    if (fabs(sbPtr->increment) > MIN_DBL_VAL) {
+       /*
+        * A increment was specified, so use it.
+        */
+       leastSigDigit = (int) floor(log10(sbPtr->increment));
+    } else {
+       leastSigDigit = 0;
+    }
+    numDigits = mostSigDigit - leastSigDigit + 1;
+    if (numDigits < 1) {
+       numDigits = 1;
+    }
+
+    /*
+     * Compute the number of characters required using "e" format and
+     * "f" format, and then choose whichever one takes fewer characters.
+     */
+
+    eDigits = numDigits + 4;
+    if (numDigits > 1) {
+       eDigits++;                      /* Decimal point. */
+    }
+    afterDecimal = numDigits - mostSigDigit - 1;
+    if (afterDecimal < 0) {
+       afterDecimal = 0;
+    }
+    fDigits = (mostSigDigit >= 0) ? mostSigDigit + afterDecimal : afterDecimal;
+    if (afterDecimal > 0) {
+       fDigits++;                      /* Decimal point. */
+    }
+    if (mostSigDigit < 0) {
+       fDigits++;                      /* Zero to left of decimal point. */
+    }
+    if (fDigits <= eDigits) {
+       sprintf(sbPtr->digitFormat, "%%.%df", afterDecimal);
+    } else {
+       sprintf(sbPtr->digitFormat, "%%.%de", numDigits-1);
+    }
+    sbPtr->valueFormat = sbPtr->digitFormat;
+    return TCL_OK;
+}