diff options
Diffstat (limited to 'tk/generic/tkEntry.c')
-rw-r--r-- | tk/generic/tkEntry.c | 2179 |
1 files changed, 1899 insertions, 280 deletions
diff --git a/tk/generic/tkEntry.c b/tk/generic/tkEntry.c index 6ed11d6f9b7..5ab3846d292 100644 --- a/tk/generic/tkEntry.c +++ b/tk/generic/tkEntry.c @@ -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. @@ -17,8 +20,12 @@ #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 */ }; @@ -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); } @@ -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. + */ + + 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; } - gcValues.foreground = entryPtr->fgColorPtr->pixel; + 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); } @@ -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); } /* @@ -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); } /* @@ -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; } } @@ -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; } @@ -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,12 @@ 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 the variable is unset, then immediately recreate it unless @@ -2729,13 +3357,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 +3410,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 +3455,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 +3487,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 +3505,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 +3544,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 +3558,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,69 +3592,1029 @@ 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; + 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; + } + } + + spaceNeeded = Tcl_ScanCountedElement(string, -1, &cvtFlags); + length = Tcl_DStringLength(dsPtr); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + spaceNeeded = Tcl_ConvertCountedElement(string, -1, + Tcl_DStringValue(dsPtr) + length, + cvtFlags | TCL_DONT_USE_BRACES); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + } +} + +/* + *-------------------------------------------------------------- + * + * 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; +} + +/* + *-------------------------------------------------------------- + * + * 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 'i': /* index of insert/delete */ - sprintf(numStorage, "%d", index); - string = numStorage; + } + + 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 'P': /* 'Peeked' new value of the string */ - string = new; + } + + 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 's': /* Current string value of entry */ - string = entryPtr->string; + } + + 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 'S': /* string to be inserted/deleted, if any */ - string = change; + } + + 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 'v': /* type of validation currently set */ - string = validateStrings[entryPtr->validate]; + } + + 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 '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; + } + + 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 'W': /* widget name */ - string = Tk_PathName(entryPtr->tkwin); + } + + 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; - default: - length = Tcl_UniCharToUtf(ch, numStorage); - numStorage[length] = '\0'; - string = numStorage; + } + + 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; } - spaceNeeded = Tcl_ScanCountedElement(string, -1, &cvtFlags); - length = Tcl_DStringLength(dsPtr); - Tcl_DStringSetLength(dsPtr, length + spaceNeeded); - spaceNeeded = Tcl_ConvertCountedElement(string, -1, - Tcl_DStringValue(dsPtr) + length, - cvtFlags | TCL_DONT_USE_BRACES); - Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + 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; +} + +/* + *--------------------------------------------------------------------------- + * + * 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; +} + +/* + *-------------------------------------------------------------- + * + * 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; } + +/* + *---------------------------------------------------------------------- + * + * 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; +} |