diff options
author | hobbs <hobbs@noemail.net> | 2000-05-29 01:43:13 (GMT) |
---|---|---|
committer | hobbs <hobbs@noemail.net> | 2000-05-29 01:43:13 (GMT) |
commit | 5447dc502d21645645f7a2121557e1876fb54d64 (patch) | |
tree | 8e72919b463211e30307b0e8eb87f7a28e578327 /generic/tkEntry.c | |
parent | 451b11f12e5e98bd43fa6385cdf2abb5d03137b2 (diff) | |
download | tk-5447dc502d21645645f7a2121557e1876fb54d64.zip tk-5447dc502d21645645f7a2121557e1876fb54d64.tar.gz tk-5447dc502d21645645f7a2121557e1876fb54d64.tar.bz2 |
* doc/spinbox.n: (new file) docs for spinbox widget
* generic/tkInt.h: added Tk_SpinboxObjCmd declaration
* generic/tkEntry.c: added 'spinbox' widget - an extension of the
entry widget type.
* generic/tkWindow.c: added 'spinbox' to core Tk commands
* library/spinbox.tcl: (new file) binding and helper procs for spinbox
* library/tk.tcl: added spinbox.tcl to list of files to source
* tests/entry.test: updated changed error messages
* tests/spinbox.test: (new file) test suite for spinbox
FossilOrigin-Name: 73da332473d5aadcb1fa8f6a108a1947d501e87e
Diffstat (limited to 'generic/tkEntry.c')
-rw-r--r-- | generic/tkEntry.c | 1893 |
1 files changed, 1697 insertions, 196 deletions
diff --git a/generic/tkEntry.c b/generic/tkEntry.c index 2271c49..f9031b9 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -1,5 +1,5 @@ /* - * tkEntry.c -- + * Entry.c -- * * This module implements entry widgets for the Tk * toolkit. An entry displays a string and allows @@ -7,18 +7,23 @@ * * Copyright (c) 1990-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 2000 Ajuba Solutions. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkEntry.c,v 1.16 2000/05/17 22:23:25 ericm Exp $ + * RCS: @(#) $Id: tkEntry.c,v 1.17 2000/05/29 01:43:14 hobbs Exp $ */ #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,7 +39,7 @@ 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". @@ -106,9 +111,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. @@ -122,23 +124,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. */ + 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 @@ -156,19 +162,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: * @@ -212,6 +277,12 @@ 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. @@ -238,16 +309,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}, @@ -359,11 +430,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- @@ -371,27 +597,74 @@ static Tk_OptionSpec optionSpecs[] = { * enumerated types used to dispatch the entry widget command. */ -static char *commandNames[] = { +static 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 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 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 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 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: */ @@ -431,7 +704,8 @@ static int EntryValidateChange _ANSI_ARGS_((Entry *entryPtr, static void ExpandPercents _ANSI_ARGS_((Entry *entryPtr, char *before, char *change, char *new, int index, int type, Tcl_DString *dsPtr)); -static void EntryValueChanged _ANSI_ARGS_((Entry *entryPtr)); +static void EntryValueChanged _ANSI_ARGS_((Entry *entryPtr, + char *newValue)); static void EntryVisibleRange _ANSI_ARGS_((Entry *entryPtr, double *firstPtr, double *lastPtr)); static int EntryWidgetObjCmd _ANSI_ARGS_((ClientData clientData, @@ -445,7 +719,20 @@ 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. */ @@ -497,7 +784,7 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) * invocations of the command. */ - optionTable = Tk_CreateOptionTable(interp, optionSpecs); + optionTable = Tk_CreateOptionTable(interp, entryOptSpec); name = Tcl_GetString(objv[0]); Tcl_GetCommandInfo(interp, name, &info); info.objClientData = (ClientData) optionTable; @@ -518,10 +805,13 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) /* * 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; @@ -529,62 +819,24 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) Tk_PathName(entryPtr->tkwin), EntryWidgetObjCmd, (ClientData) entryPtr, EntryCmdDeletedProc); entryPtr->optionTable = optionTable; + entryPtr->type = TK_ENTRY; entryPtr->string = (char *) ckalloc(1); entryPtr->string[0] = '\0'; - entryPtr->insertPos = 0; entryPtr->selectFirst = -1; entryPtr->selectLast = -1; - entryPtr->selectAnchor = 0; - entryPtr->scanMarkX = 0; - entryPtr->scanMarkIndex = 0; - - entryPtr->normalBorder = NULL; - entryPtr->disabledBorder = NULL; - entryPtr->readonlyBorder = NULL; - entryPtr->borderWidth = 0; + entryPtr->cursor = None; entryPtr->exportSelection = 1; - entryPtr->tkfont = NULL; - entryPtr->fgColorPtr = NULL; - entryPtr->dfgColorPtr = 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; Tk_SetClass(entryPtr->tkwin, "Entry"); TkSetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr); @@ -645,13 +897,13 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) * 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) { + switch ((enum entryCmd) cmdIndex) { case COMMAND_BBOX: { int index, x, y, width, height; char buf[TCL_INTEGER_SPACE * 4]; @@ -826,15 +1078,15 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) /* * Parse the selection sub-command, using the command - * table "selCommandNames" defined above. + * table "selCmdNames" defined above. */ - result = Tcl_GetIndexFromObj(interp, objv[2], selCommandNames, + result = Tcl_GetIndexFromObj(interp, objv[2], selCmdNames, "selection option", 0, &selIndex); if (result != TCL_OK) { goto error; } - + /* * Disabled entries don't allow the selection to be modified. */ @@ -842,7 +1094,7 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) if (entryPtr->state == STATE_DISABLED) { goto done; } - + switch(selIndex) { case SELECTION_ADJUST: { if (objc != 4) { @@ -1076,7 +1328,7 @@ DestroyEntry(memPtr) Tcl_DeleteCommandFromToken(entryPtr->interp, entryPtr->widgetCmd); if (entryPtr->flags & REDRAW_PENDING) { - Tcl_CancelIdleCall(DisplayEntry, (ClientData) entryPtr); + Tcl_CancelIdleCall(DisplayEntry, (ClientData) entryPtr); } /* @@ -1101,6 +1353,17 @@ DestroyEntry(memPtr) if (entryPtr->displayString != entryPtr->string) { ckfree(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); @@ -1139,10 +1402,17 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) int flags; /* Flags to pass to Tk_ConfigureWidget. */ { Tk_SavedOptions savedOptions; - Tcl_Obj *errorResult = NULL; 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. @@ -1154,7 +1424,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) { @@ -1182,11 +1462,11 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) * the geometry and setting the background from a 3-D border. */ - if (entryPtr->state == STATE_DISABLED && - entryPtr->disabledBorder != NULL) { + if ((entryPtr->state == STATE_DISABLED) && + (entryPtr->disabledBorder != NULL)) { border = entryPtr->disabledBorder; - } else if (entryPtr->state == STATE_READONLY && - entryPtr->readonlyBorder != NULL) { + } else if ((entryPtr->state == STATE_READONLY) && + (entryPtr->readonlyBorder != NULL)) { border = entryPtr->readonlyBorder; } else { border = entryPtr->normalBorder; @@ -1200,6 +1480,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, @@ -1243,6 +1597,46 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) Tk_FreeSavedOptions(&savedOptions); } + 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); + } + } + /* * 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 @@ -1254,7 +1648,7 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) value = Tcl_GetVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY); if (value == NULL) { - EntryValueChanged(entryPtr); + EntryValueChanged(entryPtr, NULL); } else { EntrySetValue(entryPtr, value); } @@ -1298,23 +1692,32 @@ EntryWorldChanged(instanceData) XGCValues gcValues; GC gc = None; unsigned long mask; - Entry *entryPtr; Tk_3DBorder border; XColor *colorPtr; - - entryPtr = (Entry *) instanceData; + Entry *entryPtr = (Entry *) instanceData; entryPtr->avgWidth = Tk_TextWidth(entryPtr->tkfont, "0", 1); if (entryPtr->avgWidth == 0) { entryPtr->avgWidth = 1; } + 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) { @@ -1332,7 +1735,7 @@ EntryWorldChanged(instanceData) } break; } - + Tk_SetBackgroundFromBorder(entryPtr->tkwin, border); gcValues.foreground = colorPtr->pixel; gcValues.font = Tk_FontId(entryPtr->tkfont); @@ -1386,12 +1789,11 @@ 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)) { return; @@ -1423,7 +1825,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; /* @@ -1443,11 +1845,11 @@ DisplayEntry(clientData) * insertion cursor background. */ - if (entryPtr->state == STATE_DISABLED && - entryPtr->disabledBorder != NULL) { + if ((entryPtr->state == STATE_DISABLED) && + (entryPtr->disabledBorder != NULL)) { border = entryPtr->disabledBorder; - } else if (entryPtr->state == STATE_READONLY && - entryPtr->readonlyBorder != NULL) { + } else if ((entryPtr->state == STATE_READONLY) && + (entryPtr->readonlyBorder != NULL)) { border = entryPtr->readonlyBorder; } else { border = entryPtr->normalBorder; @@ -1455,7 +1857,7 @@ DisplayEntry(clientData) Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); - if (showSelection && entryPtr->state != STATE_DISABLED + if (showSelection && (entryPtr->state != STATE_DISABLED) && (entryPtr->selectLast > entryPtr->leftIndex)) { if (entryPtr->selectFirst <= entryPtr->leftIndex) { selStartX = entryPtr->leftX; @@ -1516,7 +1918,7 @@ DisplayEntry(clientData) entryPtr->textLayout, entryPtr->layoutX, entryPtr->layoutY, entryPtr->leftIndex, entryPtr->numChars); - if (showSelection && entryPtr->state != STATE_DISABLED + if (showSelection && (entryPtr->state != STATE_DISABLED) && (entryPtr->selTextGC != entryPtr->textGC) && (entryPtr->selectFirst < entryPtr->selectLast)) { int selFirst; @@ -1531,29 +1933,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, border, - 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); } } @@ -1634,6 +2113,7 @@ EntryComputeGeometry(entryPtr) } *p = '\0'; } + Tk_FreeTextLayout(entryPtr->textLayout); entryPtr->textLayout = Tk_ComputeTextLayout(entryPtr->tkfont, entryPtr->displayString, entryPtr->numChars, 0, @@ -1648,16 +2128,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 { @@ -1694,6 +2176,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); } @@ -1793,7 +2281,7 @@ InsertChars(entryPtr, index, value) if (entryPtr->insertPos >= index) { entryPtr->insertPos += charsAdded; } - EntryValueChanged(entryPtr); + EntryValueChanged(entryPtr, NULL); } /* @@ -1907,7 +2395,7 @@ DeleteChars(entryPtr, index, count) entryPtr->insertPos = index; } } - EntryValueChanged(entryPtr); + EntryValueChanged(entryPtr, NULL); } /* @@ -1930,10 +2418,14 @@ DeleteChars(entryPtr, index, count) */ static void -EntryValueChanged(entryPtr) +EntryValueChanged(entryPtr, newValue) Entry *entryPtr; /* Entry whose value just changed. */ + 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; @@ -2073,7 +2565,7 @@ EntrySetValue(entryPtr, value) * EntryEventProc -- * * This procedure is invoked by the Tk dispatcher for various - * events on entryes. + * events on entries. * * Results: * None. @@ -2091,26 +2583,54 @@ 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); + } } } + + switch (eventPtr->type) { + case Expose: + EventuallyRedraw(entryPtr); + entryPtr->flags |= BORDER_NEEDED; + break; + case DestroyNotify: + DestroyEntry((char *) clientData); + 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; + } } /* @@ -2195,8 +2715,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') { @@ -2213,7 +2734,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) { @@ -2227,7 +2750,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; @@ -2236,8 +2759,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, @@ -2554,7 +3079,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++; } @@ -2609,7 +3134,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); @@ -2648,11 +3175,11 @@ EntryBlinkProc(clientData) 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); } @@ -2800,13 +3327,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, @@ -3004,68 +3539,1034 @@ 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; /* Either NULL or pointer to option table. */ + 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; + + 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, sbOptSpec); + name = Tcl_GetString(objv[0]); + Tcl_GetCommandInfo(interp, name, &info); + info.objClientData = (ClientData) optionTable; + Tcl_SetCommandInfo(interp, name, &info); + } + + 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; + } + + /* + * 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; + entryPtr->string = (char *) ckalloc(1); + entryPtr->string[0] = '\0'; + 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; + + Tk_SetClass(entryPtr->tkwin, "Spinbox"); + TkSetClassProcs(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; + } + 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], sbCmdNames, + "option", 0, &cmdIndex); + if (result != TCL_OK) { + return result; + } + + 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 'i': /* index of insert/delete */ - sprintf(numStorage, "%d", index); - string = numStorage; + } + + 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 'P': /* 'Peeked' new value of the string */ - string = new; + } + + 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 's': /* Current string value of entry */ - string = entryPtr->string; + } + + 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 'S': /* string to be inserted/deleted, if any */ - string = change; + } + + case SB_CMD_GET: { + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL); + goto error; + } + Tcl_SetResult(interp, entryPtr->string, TCL_STATIC); + 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 'v': /* type of validation currently set */ - string = validateStrings[entryPtr->validate]; + } + + 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_SetResult(interp, selElementNames[elem], TCL_VOLATILE); + } 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_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 'W': /* widget name */ - string = Tk_PathName(entryPtr->tkwin); + } + + 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; - default: - length = Tcl_UniCharToUtf(ch, numStorage); - numStorage[length] = '\0'; - string = numStorage; + } + + 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; } - 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_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_SetResult(interp, + selElementNames[sbPtr->selElement], + TCL_VOLATILE); + } 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_SetResult(interp, entryPtr->string, TCL_STATIC); + 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; + } + } else { + return SEL_ENTRY; } + 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; } |