1/*
2 * tkEntry.c --
3 *
4 *	This module implements entry and spinbox widgets for the Tk toolkit.
5 *	An entry displays a string and allows the string to be edited. A
6 *	spinbox expands on the entry by adding up/down buttons that control
7 *	the value of the entry widget.
8 *
9 * Copyright (c) 1990-1994 The Regents of the University of California.
10 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
11 * Copyright (c) 2000 Ajuba Solutions.
12 * Copyright (c) 2002 ActiveState Corporation.
13 *
14 * See the file "license.terms" for information on usage and redistribution of
15 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 *
17 * RCS: @(#) $Id$
18 */
19
20#include "tkInt.h"
21#include "default.h"
22#include "tkEntry.h"
23
24/*
25 * The following macro defines how many extra pixels to leave on each side of
26 * the text in the entry.
27 */
28
29#define XPAD 1
30#define YPAD 1
31
32/*
33 * A comparison function for double values. For Spinboxes.
34 */
35
36#define MIN_DBL_VAL		1E-9
37#define DOUBLES_EQ(d1, d2)	(fabs((d1) - (d2)) < MIN_DBL_VAL)
38
39
40static char *stateStrings[] = {
41    "disabled", "normal", "readonly", NULL
42};
43
44/*
45 * Definitions for -validate option values:
46 */
47
48static char *validateStrings[] = {
49    "all", "key", "focus", "focusin", "focusout", "none", NULL
50};
51enum validateType {
52    VALIDATE_ALL, VALIDATE_KEY, VALIDATE_FOCUS,
53    VALIDATE_FOCUSIN, VALIDATE_FOCUSOUT, VALIDATE_NONE,
54    /*
55     * These extra enums are for use with EntryValidateChange
56     */
57    VALIDATE_FORCED, VALIDATE_DELETE, VALIDATE_INSERT, VALIDATE_BUTTON
58};
59#define DEF_ENTRY_VALIDATE	"none"
60#define DEF_ENTRY_INVALIDCMD	""
61
62/*
63 * Information used for Entry objv parsing.
64 */
65
66static const Tk_OptionSpec entryOptSpec[] = {
67    {TK_OPTION_BORDER, "-background", "background", "Background",
68	DEF_ENTRY_BG_COLOR, -1, Tk_Offset(Entry, normalBorder),
69	0, (ClientData) DEF_ENTRY_BG_MONO, 0},
70    {TK_OPTION_SYNONYM, "-bd", NULL, NULL,
71	NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
72    {TK_OPTION_SYNONYM, "-bg", NULL, NULL,
73	NULL, 0, -1, 0, (ClientData) "-background", 0},
74    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
75	DEF_ENTRY_BORDER_WIDTH, -1, Tk_Offset(Entry, borderWidth), 0, 0, 0},
76    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
77	DEF_ENTRY_CURSOR, -1, Tk_Offset(Entry, cursor),
78	TK_OPTION_NULL_OK, 0, 0},
79    {TK_OPTION_BORDER, "-disabledbackground", "disabledBackground",
80	"DisabledBackground", DEF_ENTRY_DISABLED_BG_COLOR, -1,
81	Tk_Offset(Entry, disabledBorder), TK_OPTION_NULL_OK,
82	(ClientData) DEF_ENTRY_DISABLED_BG_MONO, 0},
83    {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
84	"DisabledForeground", DEF_ENTRY_DISABLED_FG, -1,
85	Tk_Offset(Entry, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0},
86    {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
87	"ExportSelection", DEF_ENTRY_EXPORT_SELECTION, -1,
88	Tk_Offset(Entry, exportSelection), 0, 0, 0},
89    {TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
90	NULL, 0, -1, 0, (ClientData) "-foreground", 0},
91    {TK_OPTION_FONT, "-font", "font", "Font",
92	DEF_ENTRY_FONT, -1, Tk_Offset(Entry, tkfont), 0, 0, 0},
93    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
94	DEF_ENTRY_FG, -1, Tk_Offset(Entry, fgColorPtr), 0, 0, 0},
95    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
96	"HighlightBackground", DEF_ENTRY_HIGHLIGHT_BG,
97	-1, Tk_Offset(Entry, highlightBgColorPtr), 0, 0, 0},
98    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
99	DEF_ENTRY_HIGHLIGHT, -1, Tk_Offset(Entry, highlightColorPtr), 0, 0, 0},
100    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
101	"HighlightThickness", DEF_ENTRY_HIGHLIGHT_WIDTH, -1,
102	Tk_Offset(Entry, highlightWidth), 0, 0, 0},
103    {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
104	DEF_ENTRY_INSERT_BG, -1, Tk_Offset(Entry, insertBorder), 0, 0, 0},
105    {TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth",
106	"BorderWidth", DEF_ENTRY_INSERT_BD_COLOR, -1,
107	Tk_Offset(Entry, insertBorderWidth), 0,
108	(ClientData) DEF_ENTRY_INSERT_BD_MONO, 0},
109    {TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime",
110	DEF_ENTRY_INSERT_OFF_TIME, -1, Tk_Offset(Entry, insertOffTime),
111	0, 0, 0},
112    {TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime",
113	DEF_ENTRY_INSERT_ON_TIME, -1, Tk_Offset(Entry, insertOnTime), 0, 0, 0},
114    {TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
115	DEF_ENTRY_INSERT_WIDTH, -1, Tk_Offset(Entry, insertWidth), 0, 0, 0},
116    {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand",
117	DEF_ENTRY_INVALIDCMD, -1, Tk_Offset(Entry, invalidCmd),
118	TK_OPTION_NULL_OK, 0, 0},
119    {TK_OPTION_SYNONYM, "-invcmd", NULL, NULL,
120	NULL, 0, -1, 0, (ClientData) "-invalidcommand", 0},
121    {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
122	DEF_ENTRY_JUSTIFY, -1, Tk_Offset(Entry, justify), 0, 0, 0},
123    {TK_OPTION_BORDER, "-readonlybackground", "readonlyBackground",
124	"ReadonlyBackground", DEF_ENTRY_READONLY_BG_COLOR, -1,
125	Tk_Offset(Entry, readonlyBorder), TK_OPTION_NULL_OK,
126	(ClientData) DEF_ENTRY_READONLY_BG_MONO, 0},
127    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
128	DEF_ENTRY_RELIEF, -1, Tk_Offset(Entry, relief), 0, 0, 0},
129    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
130	DEF_ENTRY_SELECT_COLOR, -1, Tk_Offset(Entry, selBorder),
131	0, (ClientData) DEF_ENTRY_SELECT_MONO, 0},
132    {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth",
133	"BorderWidth", DEF_ENTRY_SELECT_BD_COLOR, -1,
134	Tk_Offset(Entry, selBorderWidth),
135	0, (ClientData) DEF_ENTRY_SELECT_BD_MONO, 0},
136    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
137	DEF_ENTRY_SELECT_FG_COLOR, -1, Tk_Offset(Entry, selFgColorPtr),
138	TK_CONFIG_NULL_OK, (ClientData) DEF_ENTRY_SELECT_FG_MONO, 0},
139    {TK_OPTION_STRING, "-show", "show", "Show",
140	DEF_ENTRY_SHOW, -1, Tk_Offset(Entry, showChar),
141	TK_OPTION_NULL_OK, 0, 0},
142    {TK_OPTION_STRING_TABLE, "-state", "state", "State",
143	DEF_ENTRY_STATE, -1, Tk_Offset(Entry, state),
144	0, (ClientData) stateStrings, 0},
145    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
146	DEF_ENTRY_TAKE_FOCUS, -1, Tk_Offset(Entry, takeFocus),
147	TK_OPTION_NULL_OK, 0, 0},
148    {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
149	DEF_ENTRY_TEXT_VARIABLE, -1, Tk_Offset(Entry, textVarName),
150	TK_OPTION_NULL_OK, 0, 0},
151    {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate",
152	DEF_ENTRY_VALIDATE, -1, Tk_Offset(Entry, validate),
153	0, (ClientData) validateStrings, 0},
154    {TK_OPTION_STRING, "-validatecommand", "validateCommand","ValidateCommand",
155	NULL, -1, Tk_Offset(Entry, validateCmd), TK_OPTION_NULL_OK, 0, 0},
156    {TK_OPTION_SYNONYM, "-vcmd", NULL, NULL,
157	NULL, 0, -1, 0, (ClientData) "-validatecommand", 0},
158    {TK_OPTION_INT, "-width", "width", "Width",
159	DEF_ENTRY_WIDTH, -1, Tk_Offset(Entry, prefWidth), 0, 0, 0},
160    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
161	DEF_ENTRY_SCROLL_COMMAND, -1, Tk_Offset(Entry, scrollCmd),
162	TK_OPTION_NULL_OK, 0, 0},
163    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0}
164};
165
166/*
167 * Information used for Spinbox objv parsing.
168 */
169
170#define DEF_SPINBOX_REPEAT_DELAY	"400"
171#define DEF_SPINBOX_REPEAT_INTERVAL	"100"
172
173#define DEF_SPINBOX_CMD			""
174
175#define DEF_SPINBOX_FROM		"0"
176#define DEF_SPINBOX_TO			"0"
177#define DEF_SPINBOX_INCREMENT		"1"
178#define DEF_SPINBOX_FORMAT		""
179
180#define DEF_SPINBOX_VALUES		""
181#define DEF_SPINBOX_WRAP		"0"
182
183static const Tk_OptionSpec sbOptSpec[] = {
184    {TK_OPTION_BORDER, "-activebackground", "activeBackground", "Background",
185	DEF_BUTTON_ACTIVE_BG_COLOR, -1, Tk_Offset(Spinbox, activeBorder),
186	0, (ClientData) DEF_BUTTON_ACTIVE_BG_MONO, 0},
187    {TK_OPTION_BORDER, "-background", "background", "Background",
188	DEF_ENTRY_BG_COLOR, -1, Tk_Offset(Entry, normalBorder),
189	0, (ClientData) DEF_ENTRY_BG_MONO, 0},
190    {TK_OPTION_SYNONYM, "-bd", NULL, NULL,
191	NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
192    {TK_OPTION_SYNONYM, "-bg", NULL, NULL,
193	NULL, 0, -1, 0, (ClientData) "-background", 0},
194    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
195	DEF_ENTRY_BORDER_WIDTH, -1, Tk_Offset(Entry, borderWidth), 0, 0, 0},
196    {TK_OPTION_BORDER, "-buttonbackground", "Button.background", "Background",
197	DEF_BUTTON_BG_COLOR, -1, Tk_Offset(Spinbox, buttonBorder),
198	0, (ClientData) DEF_BUTTON_BG_MONO, 0},
199    {TK_OPTION_CURSOR, "-buttoncursor", "Button.cursor", "Cursor",
200	DEF_BUTTON_CURSOR, -1, Tk_Offset(Spinbox, bCursor),
201	TK_OPTION_NULL_OK, 0, 0},
202    {TK_OPTION_RELIEF, "-buttondownrelief", "Button.relief", "Relief",
203	DEF_BUTTON_RELIEF, -1, Tk_Offset(Spinbox, bdRelief), 0, 0, 0},
204    {TK_OPTION_RELIEF, "-buttonuprelief", "Button.relief", "Relief",
205	DEF_BUTTON_RELIEF, -1, Tk_Offset(Spinbox, buRelief), 0, 0, 0},
206    {TK_OPTION_STRING, "-command", "command", "Command",
207	DEF_SPINBOX_CMD, -1, Tk_Offset(Spinbox, command),
208	TK_OPTION_NULL_OK, 0, 0},
209    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
210	DEF_ENTRY_CURSOR, -1, Tk_Offset(Entry, cursor),
211	TK_OPTION_NULL_OK, 0, 0},
212    {TK_OPTION_BORDER, "-disabledbackground", "disabledBackground",
213	"DisabledBackground", DEF_ENTRY_DISABLED_BG_COLOR, -1,
214	Tk_Offset(Entry, disabledBorder), TK_OPTION_NULL_OK,
215	(ClientData) DEF_ENTRY_DISABLED_BG_MONO, 0},
216    {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
217	"DisabledForeground", DEF_ENTRY_DISABLED_FG, -1,
218	Tk_Offset(Entry, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0},
219    {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
220	"ExportSelection", DEF_ENTRY_EXPORT_SELECTION, -1,
221	Tk_Offset(Entry, exportSelection), 0, 0, 0},
222    {TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
223	NULL, 0, -1, 0, (ClientData) "-foreground", 0},
224    {TK_OPTION_FONT, "-font", "font", "Font",
225	DEF_ENTRY_FONT, -1, Tk_Offset(Entry, tkfont), 0, 0, 0},
226    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
227	DEF_ENTRY_FG, -1, Tk_Offset(Entry, fgColorPtr), 0, 0, 0},
228    {TK_OPTION_STRING, "-format", "format", "Format",
229	DEF_SPINBOX_FORMAT, -1, Tk_Offset(Spinbox, reqFormat),
230	TK_OPTION_NULL_OK, 0, 0},
231    {TK_OPTION_DOUBLE, "-from", "from", "From",
232	DEF_SPINBOX_FROM, -1, Tk_Offset(Spinbox, fromValue), 0, 0, 0},
233    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
234	"HighlightBackground", DEF_ENTRY_HIGHLIGHT_BG,
235	-1, Tk_Offset(Entry, highlightBgColorPtr), 0, 0, 0},
236    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
237	DEF_ENTRY_HIGHLIGHT, -1, Tk_Offset(Entry, highlightColorPtr), 0, 0, 0},
238    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
239	"HighlightThickness", DEF_ENTRY_HIGHLIGHT_WIDTH, -1,
240	Tk_Offset(Entry, highlightWidth), 0, 0, 0},
241    {TK_OPTION_DOUBLE, "-increment", "increment", "Increment",
242	DEF_SPINBOX_INCREMENT, -1, Tk_Offset(Spinbox, increment), 0, 0, 0},
243    {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
244	DEF_ENTRY_INSERT_BG, -1, Tk_Offset(Entry, insertBorder), 0, 0, 0},
245    {TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth",
246	"BorderWidth", DEF_ENTRY_INSERT_BD_COLOR, -1,
247	Tk_Offset(Entry, insertBorderWidth), 0,
248	(ClientData) DEF_ENTRY_INSERT_BD_MONO, 0},
249    {TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime",
250	DEF_ENTRY_INSERT_OFF_TIME, -1, Tk_Offset(Entry, insertOffTime),
251	0, 0, 0},
252    {TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime",
253	DEF_ENTRY_INSERT_ON_TIME, -1, Tk_Offset(Entry, insertOnTime), 0, 0, 0},
254    {TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
255	DEF_ENTRY_INSERT_WIDTH, -1, Tk_Offset(Entry, insertWidth), 0, 0, 0},
256    {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand",
257	DEF_ENTRY_INVALIDCMD, -1, Tk_Offset(Entry, invalidCmd),
258	TK_OPTION_NULL_OK, 0, 0},
259    {TK_OPTION_SYNONYM, "-invcmd", NULL, NULL,
260	NULL, 0, -1, 0, (ClientData) "-invalidcommand", 0},
261    {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
262	DEF_ENTRY_JUSTIFY, -1, Tk_Offset(Entry, justify), 0, 0, 0},
263    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
264	DEF_ENTRY_RELIEF, -1, Tk_Offset(Entry, relief), 0, 0, 0},
265    {TK_OPTION_BORDER, "-readonlybackground", "readonlyBackground",
266	"ReadonlyBackground", DEF_ENTRY_READONLY_BG_COLOR, -1,
267	Tk_Offset(Entry, readonlyBorder), TK_OPTION_NULL_OK,
268	(ClientData) DEF_ENTRY_READONLY_BG_MONO, 0},
269    {TK_OPTION_INT, "-repeatdelay", "repeatDelay", "RepeatDelay",
270	DEF_SPINBOX_REPEAT_DELAY, -1, Tk_Offset(Spinbox, repeatDelay),
271	0, 0, 0},
272    {TK_OPTION_INT, "-repeatinterval", "repeatInterval", "RepeatInterval",
273	DEF_SPINBOX_REPEAT_INTERVAL, -1, Tk_Offset(Spinbox, repeatInterval),
274	0, 0, 0},
275    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
276	DEF_ENTRY_SELECT_COLOR, -1, Tk_Offset(Entry, selBorder),
277	0, (ClientData) DEF_ENTRY_SELECT_MONO, 0},
278    {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth",
279	"BorderWidth", DEF_ENTRY_SELECT_BD_COLOR, -1,
280	Tk_Offset(Entry, selBorderWidth),
281	0, (ClientData) DEF_ENTRY_SELECT_BD_MONO, 0},
282    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
283	DEF_ENTRY_SELECT_FG_COLOR, -1, Tk_Offset(Entry, selFgColorPtr),
284	TK_CONFIG_NULL_OK, (ClientData) DEF_ENTRY_SELECT_FG_MONO, 0},
285    {TK_OPTION_STRING_TABLE, "-state", "state", "State",
286	DEF_ENTRY_STATE, -1, Tk_Offset(Entry, state),
287	0, (ClientData) stateStrings, 0},
288    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
289	DEF_ENTRY_TAKE_FOCUS, -1, Tk_Offset(Entry, takeFocus),
290	TK_CONFIG_NULL_OK, 0, 0},
291    {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
292	DEF_ENTRY_TEXT_VARIABLE, -1, Tk_Offset(Entry, textVarName),
293	TK_CONFIG_NULL_OK, 0, 0},
294    {TK_OPTION_DOUBLE, "-to", "to", "To",
295	DEF_SPINBOX_TO, -1, Tk_Offset(Spinbox, toValue), 0, 0, 0},
296    {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate",
297	DEF_ENTRY_VALIDATE, -1, Tk_Offset(Entry, validate),
298	0, (ClientData) validateStrings, 0},
299    {TK_OPTION_STRING, "-validatecommand", "validateCommand","ValidateCommand",
300	NULL, -1, Tk_Offset(Entry, validateCmd), TK_CONFIG_NULL_OK, 0, 0},
301    {TK_OPTION_STRING, "-values", "values", "Values",
302	DEF_SPINBOX_VALUES, -1, Tk_Offset(Spinbox, valueStr),
303	TK_OPTION_NULL_OK, 0, 0},
304    {TK_OPTION_SYNONYM, "-vcmd", NULL, NULL,
305	NULL, 0, -1, 0, (ClientData) "-validatecommand", 0},
306    {TK_OPTION_INT, "-width", "width", "Width",
307	DEF_ENTRY_WIDTH, -1, Tk_Offset(Entry, prefWidth), 0, 0, 0},
308    {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap",
309	DEF_SPINBOX_WRAP, -1, Tk_Offset(Spinbox, wrap), 0, 0, 0},
310    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
311	DEF_ENTRY_SCROLL_COMMAND, -1, Tk_Offset(Entry, scrollCmd),
312	TK_CONFIG_NULL_OK, 0, 0},
313    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0}
314};
315
316/*
317 * The following tables define the entry widget commands (and sub-commands)
318 * and map the indexes into the string tables into enumerated types used to
319 * dispatch the entry widget command.
320 */
321
322static CONST char *entryCmdNames[] = {
323    "bbox", "cget", "configure", "delete", "get", "icursor", "index",
324    "insert", "scan", "selection", "validate", "xview", NULL
325};
326
327enum entryCmd {
328    COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_DELETE,
329    COMMAND_GET, COMMAND_ICURSOR, COMMAND_INDEX, COMMAND_INSERT,
330    COMMAND_SCAN, COMMAND_SELECTION, COMMAND_VALIDATE, COMMAND_XVIEW
331};
332
333static CONST char *selCmdNames[] = {
334    "adjust", "clear", "from", "present", "range", "to", NULL
335};
336
337enum selCmd {
338    SELECTION_ADJUST, SELECTION_CLEAR, SELECTION_FROM,
339    SELECTION_PRESENT, SELECTION_RANGE, SELECTION_TO
340};
341
342/*
343 * The following tables define the spinbox widget commands (and sub-commands)
344 * and map the indexes into the string tables into enumerated types used to
345 * dispatch the spinbox widget command.
346 */
347
348static CONST char *sbCmdNames[] = {
349    "bbox", "cget", "configure", "delete", "get", "icursor", "identify",
350    "index", "insert", "invoke", "scan", "selection", "set",
351    "validate", "xview", NULL
352};
353
354enum sbCmd {
355    SB_CMD_BBOX, SB_CMD_CGET, SB_CMD_CONFIGURE, SB_CMD_DELETE,
356    SB_CMD_GET, SB_CMD_ICURSOR, SB_CMD_IDENTIFY, SB_CMD_INDEX,
357    SB_CMD_INSERT, SB_CMD_INVOKE, SB_CMD_SCAN, SB_CMD_SELECTION,
358    SB_CMD_SET, SB_CMD_VALIDATE, SB_CMD_XVIEW
359};
360
361static CONST char *sbSelCmdNames[] = {
362    "adjust", "clear", "element", "from", "present", "range", "to", NULL
363};
364
365enum sbselCmd {
366    SB_SEL_ADJUST, SB_SEL_CLEAR, SB_SEL_ELEMENT, SB_SEL_FROM,
367    SB_SEL_PRESENT, SB_SEL_RANGE, SB_SEL_TO
368};
369
370/*
371 * Extra for selection of elements
372 */
373
374/*
375 * This is the string array corresponding to the enum in selelement. If you
376 * modify them, you must modify the strings here.
377 */
378
379static CONST char *selElementNames[] = {
380    "none", "buttondown", "buttonup", NULL, "entry"
381};
382
383/*
384 * Flags for GetEntryIndex function:
385 */
386
387#define ZERO_OK			1
388#define LAST_PLUS_ONE_OK	2
389
390/*
391 * Forward declarations for functions defined later in this file:
392 */
393
394static int		ConfigureEntry(Tcl_Interp *interp, Entry *entryPtr,
395			    int objc, Tcl_Obj *CONST objv[], int flags);
396static void		DeleteChars(Entry *entryPtr, int index, int count);
397static void		DestroyEntry(char *memPtr);
398static void		DisplayEntry(ClientData clientData);
399static void		EntryBlinkProc(ClientData clientData);
400static void		EntryCmdDeletedProc(ClientData clientData);
401static void		EntryComputeGeometry(Entry *entryPtr);
402static void		EntryEventProc(ClientData clientData,
403			    XEvent *eventPtr);
404static void		EntryFocusProc(Entry *entryPtr, int gotFocus);
405static int		EntryFetchSelection(ClientData clientData, int offset,
406			    char *buffer, int maxBytes);
407static void		EntryLostSelection(ClientData clientData);
408static void		EventuallyRedraw(Entry *entryPtr);
409static void		EntryScanTo(Entry *entryPtr, int y);
410static void		EntrySetValue(Entry *entryPtr, CONST char *value);
411static void		EntrySelectTo(Entry *entryPtr, int index);
412static char *		EntryTextVarProc(ClientData clientData,
413			    Tcl_Interp *interp, CONST char *name1,
414			    CONST char *name2, int flags);
415static void		EntryUpdateScrollbar(Entry *entryPtr);
416static int		EntryValidate(Entry *entryPtr, char *cmd);
417static int		EntryValidateChange(Entry *entryPtr, char *change,
418			    CONST char *newStr, int index, int type);
419static void		ExpandPercents(Entry *entryPtr, CONST char *before,
420			    CONST char *change, CONST char *newStr, int index,
421			    int type, Tcl_DString *dsPtr);
422static void		EntryValueChanged(Entry *entryPtr,
423			    CONST char *newValue);
424static void		EntryVisibleRange(Entry *entryPtr,
425			    double *firstPtr, double *lastPtr);
426static int		EntryWidgetObjCmd(ClientData clientData,
427			    Tcl_Interp *interp, int objc,
428			    Tcl_Obj *CONST objv[]);
429static void		EntryWorldChanged(ClientData instanceData);
430static int		GetEntryIndex(Tcl_Interp *interp, Entry *entryPtr,
431			    char *string, int *indexPtr);
432static void		InsertChars(Entry *entryPtr, int index, char *string);
433
434/*
435 * These forward declarations are the spinbox specific ones:
436 */
437
438static int		SpinboxWidgetObjCmd(ClientData clientData,
439			    Tcl_Interp *interp, int objc,
440			    Tcl_Obj *CONST objv[]);
441static int		GetSpinboxElement(Spinbox *sbPtr, int x, int y);
442static int		SpinboxInvoke(Tcl_Interp *interp, Spinbox *sbPtr,
443			    int element);
444static int		ComputeFormat(Spinbox *sbPtr);
445
446/*
447 * The structure below defines widget class behavior by means of functions
448 * that can be invoked from generic window code.
449 */
450
451static Tk_ClassProcs entryClass = {
452    sizeof(Tk_ClassProcs),	/* size */
453    EntryWorldChanged,		/* worldChangedProc */
454};
455
456
457/*
458 *--------------------------------------------------------------
459 *
460 * Tk_EntryObjCmd --
461 *
462 *	This function is invoked to process the "entry" Tcl command. See the
463 *	user documentation for details on what it does.
464 *
465 * Results:
466 *	A standard Tcl result.
467 *
468 * Side effects:
469 *	See the user documentation.
470 *
471 *--------------------------------------------------------------
472 */
473
474int
475Tk_EntryObjCmd(
476    ClientData clientData,	/* NULL. */
477    Tcl_Interp *interp,		/* Current interpreter. */
478    int objc,			/* Number of arguments. */
479    Tcl_Obj *CONST objv[])	/* Argument objects. */
480{
481    register Entry *entryPtr;
482    Tk_OptionTable optionTable;
483    Tk_Window tkwin;
484    char *tmp;
485
486    if (objc < 2) {
487	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
488	return TCL_ERROR;
489    }
490
491    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
492	    Tcl_GetString(objv[1]), NULL);
493    if (tkwin == NULL) {
494	return TCL_ERROR;
495    }
496
497    /*
498     * Create the option table for this widget class. If it has already been
499     * created, Tk will return the cached value.
500     */
501
502    optionTable = Tk_CreateOptionTable(interp, entryOptSpec);
503
504    /*
505     * Initialize the fields of the structure that won't be initialized by
506     * ConfigureEntry, or that ConfigureEntry requires to be initialized
507     * already (e.g. resource pointers). Only the non-NULL/0 data must be
508     * initialized as memset covers the rest.
509     */
510
511    entryPtr			= (Entry *) ckalloc(sizeof(Entry));
512    memset(entryPtr, 0, sizeof(Entry));
513
514    entryPtr->tkwin		= tkwin;
515    entryPtr->display		= Tk_Display(tkwin);
516    entryPtr->interp		= interp;
517    entryPtr->widgetCmd		= Tcl_CreateObjCommand(interp,
518	    Tk_PathName(entryPtr->tkwin), EntryWidgetObjCmd,
519	    (ClientData) entryPtr, EntryCmdDeletedProc);
520    entryPtr->optionTable	= optionTable;
521    entryPtr->type		= TK_ENTRY;
522    tmp				= (char *) ckalloc(1);
523    tmp[0]			= '\0';
524    entryPtr->string		= tmp;
525    entryPtr->selectFirst	= -1;
526    entryPtr->selectLast	= -1;
527
528    entryPtr->cursor		= None;
529    entryPtr->exportSelection	= 1;
530    entryPtr->justify		= TK_JUSTIFY_LEFT;
531    entryPtr->relief		= TK_RELIEF_FLAT;
532    entryPtr->state		= STATE_NORMAL;
533    entryPtr->displayString	= entryPtr->string;
534    entryPtr->inset		= XPAD;
535    entryPtr->textGC		= None;
536    entryPtr->selTextGC		= None;
537    entryPtr->highlightGC	= None;
538    entryPtr->avgWidth		= 1;
539    entryPtr->validate		= VALIDATE_NONE;
540
541    /*
542     * Keep a hold of the associated tkwin until we destroy the entry,
543     * otherwise Tk might free it while we still need it.
544     */
545
546    Tcl_Preserve((ClientData) entryPtr->tkwin);
547
548    Tk_SetClass(entryPtr->tkwin, "Entry");
549    Tk_SetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr);
550    Tk_CreateEventHandler(entryPtr->tkwin,
551	    ExposureMask|StructureNotifyMask|FocusChangeMask,
552	    EntryEventProc, (ClientData) entryPtr);
553    Tk_CreateSelHandler(entryPtr->tkwin, XA_PRIMARY, XA_STRING,
554	    EntryFetchSelection, (ClientData) entryPtr, XA_STRING);
555
556    if ((Tk_InitOptions(interp, (char *) entryPtr, optionTable, tkwin)
557	    != TCL_OK) ||
558	    (ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0) != TCL_OK)) {
559	Tk_DestroyWindow(entryPtr->tkwin);
560	return TCL_ERROR;
561    }
562
563    Tcl_SetResult(interp, Tk_PathName(entryPtr->tkwin), TCL_STATIC);
564    return TCL_OK;
565}
566
567/*
568 *--------------------------------------------------------------
569 *
570 * EntryWidgetObjCmd --
571 *
572 *	This function is invoked to process the Tcl command that corresponds
573 *	to a widget managed by this module. See the user documentation for
574 *	details on what it does.
575 *
576 * Results:
577 *	A standard Tcl result.
578 *
579 * Side effects:
580 *	See the user documentation.
581 *
582 *--------------------------------------------------------------
583 */
584
585static int
586EntryWidgetObjCmd(
587    ClientData clientData,	/* Information about entry widget. */
588    Tcl_Interp *interp,		/* Current interpreter. */
589    int objc,			/* Number of arguments. */
590    Tcl_Obj *CONST objv[])	/* Argument objects. */
591{
592    Entry *entryPtr = (Entry *) clientData;
593    int cmdIndex, selIndex, result;
594    Tcl_Obj *objPtr;
595
596    if (objc < 2) {
597	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
598	return TCL_ERROR;
599    }
600
601    /*
602     * Parse the widget command by looking up the second token in the list of
603     * valid command names.
604     */
605
606    result = Tcl_GetIndexFromObj(interp, objv[1], entryCmdNames,
607	    "option", 0, &cmdIndex);
608    if (result != TCL_OK) {
609	return result;
610    }
611
612    Tcl_Preserve((ClientData) entryPtr);
613    switch ((enum entryCmd) cmdIndex) {
614    case COMMAND_BBOX: {
615	int index, x, y, width, height;
616	char buf[TCL_INTEGER_SPACE * 4];
617
618	if (objc != 3) {
619	    Tcl_WrongNumArgs(interp, 2, objv, "index");
620	    goto error;
621	}
622	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
623		&index) != TCL_OK) {
624	    goto error;
625	}
626	if ((index == entryPtr->numChars) && (index > 0)) {
627	    index--;
628	}
629	Tk_CharBbox(entryPtr->textLayout, index, &x, &y, &width, &height);
630	sprintf(buf, "%d %d %d %d", x + entryPtr->layoutX,
631		y + entryPtr->layoutY, width, height);
632	Tcl_SetResult(interp, buf, TCL_VOLATILE);
633	break;
634    }
635
636    case COMMAND_CGET:
637	if (objc != 3) {
638	    Tcl_WrongNumArgs(interp, 2, objv, "option");
639	    goto error;
640	}
641
642	objPtr = Tk_GetOptionValue(interp, (char *) entryPtr,
643		entryPtr->optionTable, objv[2], entryPtr->tkwin);
644	if (objPtr == NULL) {
645	    goto error;
646	} else {
647	    Tcl_SetObjResult(interp, objPtr);
648	}
649	break;
650
651    case COMMAND_CONFIGURE:
652	if (objc <= 3) {
653	    objPtr = Tk_GetOptionInfo(interp, (char *) entryPtr,
654		    entryPtr->optionTable,
655		    (objc == 3) ? objv[2] : NULL,
656		    entryPtr->tkwin);
657	    if (objPtr == NULL) {
658		goto error;
659	    } else {
660		Tcl_SetObjResult(interp, objPtr);
661	    }
662	} else {
663	    result = ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0);
664	}
665	break;
666
667    case COMMAND_DELETE: {
668	int first, last;
669
670	if ((objc < 3) || (objc > 4)) {
671	    Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
672	    goto error;
673	}
674	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
675		&first) != TCL_OK) {
676	    goto error;
677	}
678	if (objc == 3) {
679	    last = first + 1;
680	} else if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[3]),
681		&last) != TCL_OK) {
682	    goto error;
683	}
684	if ((last >= first) && (entryPtr->state == STATE_NORMAL)) {
685	    DeleteChars(entryPtr, first, last - first);
686	}
687	break;
688    }
689
690    case COMMAND_GET:
691	if (objc != 2) {
692	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
693	    goto error;
694	}
695	Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1);
696	break;
697
698    case COMMAND_ICURSOR:
699	if (objc != 3) {
700	    Tcl_WrongNumArgs(interp, 2, objv, "pos");
701	    goto error;
702	}
703	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
704		&entryPtr->insertPos) != TCL_OK) {
705	    goto error;
706	}
707	EventuallyRedraw(entryPtr);
708	break;
709
710    case COMMAND_INDEX: {
711	int index;
712
713	if (objc != 3) {
714	    Tcl_WrongNumArgs(interp, 2, objv, "string");
715	    goto error;
716	}
717	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
718		&index) != TCL_OK) {
719	    goto error;
720	}
721	Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
722	break;
723    }
724
725    case COMMAND_INSERT: {
726	int index;
727
728	if (objc != 4) {
729	    Tcl_WrongNumArgs(interp, 2, objv, "index text");
730	    goto error;
731	}
732	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
733		&index) != TCL_OK) {
734	    goto error;
735	}
736	if (entryPtr->state == STATE_NORMAL) {
737	    InsertChars(entryPtr, index, Tcl_GetString(objv[3]));
738	}
739	break;
740    }
741
742    case COMMAND_SCAN: {
743	int x;
744	char *minorCmd;
745
746	if (objc != 4) {
747	    Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x");
748	    goto error;
749	}
750	if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
751	    goto error;
752	}
753
754	minorCmd = Tcl_GetString(objv[2]);
755	if (minorCmd[0] == 'm'
756		&& (strncmp(minorCmd, "mark", strlen(minorCmd)) == 0)) {
757	    entryPtr->scanMarkX = x;
758	    entryPtr->scanMarkIndex = entryPtr->leftIndex;
759	} else if ((minorCmd[0] == 'd')
760		&& (strncmp(minorCmd, "dragto", strlen(minorCmd)) == 0)) {
761	    EntryScanTo(entryPtr, x);
762	} else {
763	    Tcl_AppendResult(interp, "bad scan option \"",
764		    Tcl_GetString(objv[2]), "\": must be mark or dragto",
765		    NULL);
766	    goto error;
767	}
768	break;
769    }
770
771    case COMMAND_SELECTION: {
772	int index, index2;
773
774	if (objc < 3) {
775	    Tcl_WrongNumArgs(interp, 2, objv, "option ?index?");
776	    goto error;
777	}
778
779	/*
780	 * Parse the selection sub-command, using the command table
781	 * "selCmdNames" defined above.
782	 */
783
784	result = Tcl_GetIndexFromObj(interp, objv[2], selCmdNames,
785		"selection option", 0, &selIndex);
786	if (result != TCL_OK) {
787	    goto error;
788	}
789
790	/*
791	 * Disabled entries don't allow the selection to be modified, but
792	 * 'selection present' must return a boolean.
793	 */
794
795	if ((entryPtr->state == STATE_DISABLED)
796		&& (selIndex != SELECTION_PRESENT)) {
797	    goto done;
798	}
799
800	switch (selIndex) {
801	case SELECTION_ADJUST:
802	    if (objc != 4) {
803		Tcl_WrongNumArgs(interp, 3, objv, "index");
804		goto error;
805	    }
806	    if (GetEntryIndex(interp, entryPtr,
807		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
808		goto error;
809	    }
810	    if (entryPtr->selectFirst >= 0) {
811		int half1, half2;
812
813		half1 = (entryPtr->selectFirst + entryPtr->selectLast)/2;
814		half2 = (entryPtr->selectFirst + entryPtr->selectLast + 1)/2;
815		if (index < half1) {
816		    entryPtr->selectAnchor = entryPtr->selectLast;
817		} else if (index > half2) {
818		    entryPtr->selectAnchor = entryPtr->selectFirst;
819		} else {
820		    /*
821		     * We're at about the halfway point in the selection; just
822		     * keep the existing anchor.
823		     */
824		}
825	    }
826	    EntrySelectTo(entryPtr, index);
827	    break;
828
829	case SELECTION_CLEAR:
830	    if (objc != 3) {
831		Tcl_WrongNumArgs(interp, 3, objv, NULL);
832		goto error;
833	    }
834	    if (entryPtr->selectFirst >= 0) {
835		entryPtr->selectFirst = -1;
836		entryPtr->selectLast = -1;
837		EventuallyRedraw(entryPtr);
838	    }
839	    goto done;
840
841	case SELECTION_FROM:
842	    if (objc != 4) {
843		Tcl_WrongNumArgs(interp, 3, objv, "index");
844		goto error;
845	    }
846	    if (GetEntryIndex(interp, entryPtr,
847		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
848		goto error;
849	    }
850	    entryPtr->selectAnchor = index;
851	    break;
852
853	case SELECTION_PRESENT:
854	    if (objc != 3) {
855		Tcl_WrongNumArgs(interp, 3, objv, NULL);
856		goto error;
857	    }
858	    Tcl_SetObjResult(interp,
859		    Tcl_NewBooleanObj((entryPtr->selectFirst >= 0)));
860	    goto done;
861
862	case SELECTION_RANGE:
863	    if (objc != 5) {
864		Tcl_WrongNumArgs(interp, 3, objv, "start end");
865		goto error;
866	    }
867	    if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[3]),
868		    &index) != TCL_OK) {
869		goto error;
870	    }
871	    if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[4]),
872		    &index2) != TCL_OK) {
873		goto error;
874	    }
875	    if (index >= index2) {
876		entryPtr->selectFirst = -1;
877		entryPtr->selectLast = -1;
878	    } else {
879		entryPtr->selectFirst = index;
880		entryPtr->selectLast = index2;
881	    }
882	    if (!(entryPtr->flags & GOT_SELECTION)
883		    && (entryPtr->exportSelection)) {
884		Tk_OwnSelection(entryPtr->tkwin, XA_PRIMARY,
885			EntryLostSelection, (ClientData) entryPtr);
886		entryPtr->flags |= GOT_SELECTION;
887	    }
888	    EventuallyRedraw(entryPtr);
889	    break;
890
891	case SELECTION_TO:
892	    if (objc != 4) {
893		Tcl_WrongNumArgs(interp, 3, objv, "index");
894		goto error;
895	    }
896	    if (GetEntryIndex(interp, entryPtr,
897		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
898		goto error;
899	    }
900	    EntrySelectTo(entryPtr, index);
901	    break;
902	}
903	break;
904    }
905
906    case COMMAND_VALIDATE: {
907	int code;
908
909	if (objc != 2) {
910	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
911	    goto error;
912	}
913	selIndex = entryPtr->validate;
914	entryPtr->validate = VALIDATE_ALL;
915	code = EntryValidateChange(entryPtr, NULL, entryPtr->string,
916		-1, VALIDATE_FORCED);
917	if (entryPtr->validate != VALIDATE_NONE) {
918	    entryPtr->validate = selIndex;
919	}
920	Tcl_SetObjResult(interp, Tcl_NewBooleanObj((code == TCL_OK)));
921	break;
922    }
923
924    case COMMAND_XVIEW: {
925	int index;
926
927	if (objc == 2) {
928	    double first, last;
929	    char buf[TCL_DOUBLE_SPACE];
930
931	    EntryVisibleRange(entryPtr, &first, &last);
932	    Tcl_PrintDouble(NULL, first, buf);
933	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
934	    Tcl_PrintDouble(NULL, last, buf);
935	    Tcl_AppendResult(interp, " ", buf, NULL);
936	    goto done;
937	} else if (objc == 3) {
938	    if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
939		    &index) != TCL_OK) {
940		goto error;
941	    }
942	} else {
943	    double fraction;
944	    int count;
945
946	    index = entryPtr->leftIndex;
947	    switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction,
948		    &count)) {
949	    case TK_SCROLL_ERROR:
950		goto error;
951	    case TK_SCROLL_MOVETO:
952		index = (int) ((fraction * entryPtr->numChars) + 0.5);
953		break;
954	    case TK_SCROLL_PAGES: {
955		int charsPerPage;
956
957		charsPerPage = ((Tk_Width(entryPtr->tkwin)
958			- 2 * entryPtr->inset) / entryPtr->avgWidth) - 2;
959		if (charsPerPage < 1) {
960		    charsPerPage = 1;
961		}
962		index += count * charsPerPage;
963		break;
964	    }
965	    case TK_SCROLL_UNITS:
966		index += count;
967		break;
968	    }
969	}
970	if (index >= entryPtr->numChars) {
971	    index = entryPtr->numChars - 1;
972	}
973	if (index < 0) {
974	    index = 0;
975	}
976	entryPtr->leftIndex = index;
977	entryPtr->flags |= UPDATE_SCROLLBAR;
978	EntryComputeGeometry(entryPtr);
979	EventuallyRedraw(entryPtr);
980	break;
981    }
982    }
983
984  done:
985    Tcl_Release((ClientData) entryPtr);
986    return result;
987
988  error:
989    Tcl_Release((ClientData) entryPtr);
990    return TCL_ERROR;
991}
992
993/*
994 *----------------------------------------------------------------------
995 *
996 * DestroyEntry --
997 *
998 *	This function is invoked by Tcl_EventuallyFree or Tcl_Release to clean
999 *	up the internal structure of an entry at a safe time (when no-one is
1000 *	using it anymore).
1001 *
1002 * Results:
1003 *	None.
1004 *
1005 * Side effects:
1006 *	Everything associated with the entry is freed up.
1007 *
1008 *----------------------------------------------------------------------
1009 */
1010
1011static void
1012DestroyEntry(
1013    char *memPtr)		/* Info about entry widget. */
1014{
1015    Entry *entryPtr = (Entry *) memPtr;
1016
1017    /*
1018     * Free up all the stuff that requires special handling, then let
1019     * Tk_FreeOptions handle all the standard option-related stuff.
1020     */
1021
1022    ckfree((char *)entryPtr->string);
1023    if (entryPtr->textVarName != NULL) {
1024	Tcl_UntraceVar(entryPtr->interp, entryPtr->textVarName,
1025		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1026		EntryTextVarProc, (ClientData) entryPtr);
1027	entryPtr->flags &= ~ENTRY_VAR_TRACED;
1028    }
1029    if (entryPtr->textGC != None) {
1030	Tk_FreeGC(entryPtr->display, entryPtr->textGC);
1031    }
1032    if (entryPtr->selTextGC != None) {
1033	Tk_FreeGC(entryPtr->display, entryPtr->selTextGC);
1034    }
1035    Tcl_DeleteTimerHandler(entryPtr->insertBlinkHandler);
1036    if (entryPtr->displayString != entryPtr->string) {
1037	ckfree((char *)entryPtr->displayString);
1038    }
1039    if (entryPtr->type == TK_SPINBOX) {
1040	Spinbox *sbPtr = (Spinbox *) entryPtr;
1041
1042	if (sbPtr->listObj != NULL) {
1043	    Tcl_DecrRefCount(sbPtr->listObj);
1044	    sbPtr->listObj = NULL;
1045	}
1046	if (sbPtr->formatBuf) {
1047	    ckfree(sbPtr->formatBuf);
1048	}
1049    }
1050    Tk_FreeTextLayout(entryPtr->textLayout);
1051    Tk_FreeConfigOptions((char *) entryPtr, entryPtr->optionTable,
1052	    entryPtr->tkwin);
1053    Tcl_Release((ClientData) entryPtr->tkwin);
1054    entryPtr->tkwin = NULL;
1055
1056    ckfree((char *) entryPtr);
1057}
1058
1059/*
1060 *----------------------------------------------------------------------
1061 *
1062 * ConfigureEntry --
1063 *
1064 *	This function is called to process an argv/argc list, plus the Tk
1065 *	option database, in order to configure (or reconfigure) an entry
1066 *	widget.
1067 *
1068 * Results:
1069 *	The return value is a standard Tcl result. If TCL_ERROR is returned,
1070 *	then the interp's result contains an error message.
1071 *
1072 * Side effects:
1073 *	Configuration information, such as colors, border width, etc. get set
1074 *	for entryPtr; old resources get freed, if there were any.
1075 *
1076 *----------------------------------------------------------------------
1077 */
1078
1079static int
1080ConfigureEntry(
1081    Tcl_Interp *interp,		/* Used for error reporting. */
1082    Entry *entryPtr,		/* Information about widget; may or may not
1083				 * already have values for some fields. */
1084    int objc,			/* Number of valid entries in argv. */
1085    Tcl_Obj *CONST objv[],	/* Argument objects. */
1086    int flags)			/* Flags to pass to Tk_ConfigureWidget. */
1087{
1088    Tk_SavedOptions savedOptions;
1089    Tk_3DBorder border;
1090    Tcl_Obj *errorResult = NULL;
1091    Spinbox *sbPtr = (Spinbox *) entryPtr;
1092				/* Only used when this widget is of type
1093				 * TK_SPINBOX */
1094    char *oldValues = NULL;	/* lint initialization */
1095    char *oldFormat = NULL;	/* lint initialization */
1096    int error;
1097    int oldExport = 0;		/* lint initialization */
1098    int valuesChanged = 0;	/* lint initialization */
1099    double oldFrom = 0.0;	/* lint initialization */
1100    double oldTo = 0.0;		/* lint initialization */
1101
1102    /*
1103     * Eliminate any existing trace on a variable monitored by the entry.
1104     */
1105
1106    if ((entryPtr->textVarName != NULL)
1107	    && (entryPtr->flags & ENTRY_VAR_TRACED)) {
1108	Tcl_UntraceVar(interp, entryPtr->textVarName,
1109		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1110		EntryTextVarProc, (ClientData) entryPtr);
1111	entryPtr->flags &= ~ENTRY_VAR_TRACED;
1112    }
1113
1114    /*
1115     * Store old values that we need to effect certain behavior if they change
1116     * value.
1117     */
1118
1119    oldExport		= entryPtr->exportSelection;
1120    if (entryPtr->type == TK_SPINBOX) {
1121	oldValues	= sbPtr->valueStr;
1122	oldFormat	= sbPtr->reqFormat;
1123	oldFrom		= sbPtr->fromValue;
1124	oldTo		= sbPtr->toValue;
1125    }
1126
1127    for (error = 0; error <= 1; error++) {
1128	if (!error) {
1129	    /*
1130	     * First pass: set options to new values.
1131	     */
1132
1133	    if (Tk_SetOptions(interp, (char *) entryPtr,
1134		    entryPtr->optionTable, objc, objv,
1135		    entryPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
1136		continue;
1137	    }
1138	} else {
1139	    /*
1140	     * Second pass: restore options to old values.
1141	     */
1142
1143	    errorResult = Tcl_GetObjResult(interp);
1144	    Tcl_IncrRefCount(errorResult);
1145	    Tk_RestoreSavedOptions(&savedOptions);
1146	}
1147
1148	/*
1149	 * A few other options also need special processing, such as parsing
1150	 * the geometry and setting the background from a 3-D border.
1151	 */
1152
1153	if ((entryPtr->state == STATE_DISABLED) &&
1154		(entryPtr->disabledBorder != NULL)) {
1155	    border = entryPtr->disabledBorder;
1156	} else if ((entryPtr->state == STATE_READONLY) &&
1157		(entryPtr->readonlyBorder != NULL)) {
1158	    border = entryPtr->readonlyBorder;
1159	} else {
1160	    border = entryPtr->normalBorder;
1161	}
1162	Tk_SetBackgroundFromBorder(entryPtr->tkwin, border);
1163
1164	if (entryPtr->insertWidth <= 0) {
1165	    entryPtr->insertWidth = 2;
1166	}
1167	if (entryPtr->insertBorderWidth > entryPtr->insertWidth/2) {
1168	    entryPtr->insertBorderWidth = entryPtr->insertWidth/2;
1169	}
1170
1171	if (entryPtr->type == TK_SPINBOX) {
1172	    if (sbPtr->fromValue > sbPtr->toValue) {
1173		Tcl_SetResult(interp,
1174			"-to value must be greater than -from value",
1175			TCL_VOLATILE);
1176		continue;
1177	    }
1178
1179	    if (sbPtr->reqFormat && (oldFormat != sbPtr->reqFormat)) {
1180		/*
1181		 * Make sure that the given format is somewhat correct, and
1182		 * calculate the minimum space we'll need for the values as
1183		 * strings.
1184		 */
1185
1186		int min, max;
1187		size_t formatLen, formatSpace = TCL_DOUBLE_SPACE;
1188		char fbuf[4], *fmt = sbPtr->reqFormat;
1189
1190		formatLen = strlen(fmt);
1191		if ((fmt[0] != '%') || (fmt[formatLen-1] != 'f')) {
1192		    badFormatOpt:
1193		    Tcl_AppendResult(interp, "bad spinbox format specifier \"",
1194			    sbPtr->reqFormat, "\"", NULL);
1195		    continue;
1196		}
1197		if ((sscanf(fmt, "%%%d.%d%[f]", &min, &max, fbuf) == 3)
1198			&& (max >= 0)) {
1199		    formatSpace = min + max + 1;
1200		} else if (((sscanf(fmt, "%%.%d%[f]", &min, fbuf) == 2)
1201			|| (sscanf(fmt, "%%%d%[f]", &min, fbuf) == 2)
1202			|| (sscanf(fmt, "%%%d.%[f]", &min, fbuf) == 2))
1203			&& (min >= 0)) {
1204		    formatSpace = min + 1;
1205		} else {
1206		    goto badFormatOpt;
1207		}
1208		if (formatSpace < TCL_DOUBLE_SPACE) {
1209		    formatSpace = TCL_DOUBLE_SPACE;
1210		}
1211		sbPtr->formatBuf = ckrealloc(sbPtr->formatBuf, formatSpace);
1212
1213		/*
1214		 * We perturb the value of oldFrom to allow us to go into the
1215		 * branch below that will reformat the displayed value.
1216		 */
1217
1218		oldFrom = sbPtr->fromValue - 1;
1219	    }
1220
1221	    /*
1222	     * See if we have to rearrange our listObj data.
1223	     */
1224
1225	    if (oldValues != sbPtr->valueStr) {
1226		if (sbPtr->listObj != NULL) {
1227		    Tcl_DecrRefCount(sbPtr->listObj);
1228		}
1229		sbPtr->listObj = NULL;
1230		if (sbPtr->valueStr != NULL) {
1231		    Tcl_Obj *newObjPtr;
1232		    int nelems;
1233
1234		    newObjPtr = Tcl_NewStringObj(sbPtr->valueStr, -1);
1235		    if (Tcl_ListObjLength(interp, newObjPtr, &nelems)
1236			    != TCL_OK) {
1237			valuesChanged = -1;
1238			continue;
1239		    }
1240		    sbPtr->listObj = newObjPtr;
1241		    Tcl_IncrRefCount(sbPtr->listObj);
1242		    sbPtr->nElements = nelems;
1243		    sbPtr->eIndex = 0;
1244		    valuesChanged++;
1245		}
1246	    }
1247	}
1248
1249	/*
1250	 * Restart the cursor timing sequence in case the on-time or off-time
1251	 * just changed. Set validate temporarily to none, so the configure
1252	 * doesn't cause it to be triggered.
1253	 */
1254
1255	if (entryPtr->flags & GOT_FOCUS) {
1256	    int validate = entryPtr->validate;
1257
1258	    entryPtr->validate = VALIDATE_NONE;
1259	    EntryFocusProc(entryPtr, 1);
1260	    entryPtr->validate = validate;
1261	}
1262
1263	/*
1264	 * Claim the selection if we've suddenly started exporting it.
1265	 */
1266
1267	if (entryPtr->exportSelection && (!oldExport)
1268		&& (entryPtr->selectFirst != -1)
1269		&& !(entryPtr->flags & GOT_SELECTION)) {
1270	    Tk_OwnSelection(entryPtr->tkwin, XA_PRIMARY, EntryLostSelection,
1271		    (ClientData) entryPtr);
1272	    entryPtr->flags |= GOT_SELECTION;
1273	}
1274
1275	/*
1276	 * Recompute the window's geometry and arrange for it to be
1277	 * redisplayed.
1278	 */
1279
1280	Tk_SetInternalBorder(entryPtr->tkwin,
1281		entryPtr->borderWidth + entryPtr->highlightWidth);
1282	if (entryPtr->highlightWidth <= 0) {
1283	    entryPtr->highlightWidth = 0;
1284	}
1285	entryPtr->inset = entryPtr->highlightWidth
1286		+ entryPtr->borderWidth + XPAD;
1287	break;
1288    }
1289    if (!error) {
1290	Tk_FreeSavedOptions(&savedOptions);
1291    }
1292
1293    /*
1294     * If the entry is tied to the value of a variable, create the variable if
1295     * it doesn't exist, and set the entry's value from the variable's value.
1296     */
1297
1298    if (entryPtr->textVarName != NULL) {
1299	CONST char *value;
1300
1301	value = Tcl_GetVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY);
1302	if (value == NULL) {
1303	    EntryValueChanged(entryPtr, NULL);
1304	} else {
1305	    EntrySetValue(entryPtr, value);
1306	}
1307    }
1308
1309    if (entryPtr->type == TK_SPINBOX) {
1310	ComputeFormat(sbPtr);
1311
1312	if (valuesChanged > 0) {
1313	    Tcl_Obj *objPtr;
1314
1315	    /*
1316	     * No check for error return, because there shouldn't be one given
1317	     * the check for valid list above.
1318	     */
1319
1320	    Tcl_ListObjIndex(interp, sbPtr->listObj, 0, &objPtr);
1321	    EntryValueChanged(entryPtr, Tcl_GetString(objPtr));
1322	} else if ((sbPtr->valueStr == NULL)
1323		&& !DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue)
1324		&& (!DOUBLES_EQ(sbPtr->fromValue, oldFrom)
1325			|| !DOUBLES_EQ(sbPtr->toValue, oldTo))) {
1326	    /*
1327	     * If the valueStr is empty and -from && -to are specified, check
1328	     * to see if the current string is within the range. If not, it
1329	     * will be constrained to the nearest edge. If the current string
1330	     * isn't a double value, we set it to -from.
1331	     */
1332
1333	    int code;
1334	    double dvalue;
1335
1336	    code = Tcl_GetDouble(NULL, entryPtr->string, &dvalue);
1337	    if (code != TCL_OK) {
1338		dvalue = sbPtr->fromValue;
1339	    } else {
1340		if (dvalue > sbPtr->toValue) {
1341		    dvalue = sbPtr->toValue;
1342		} else if (dvalue < sbPtr->fromValue) {
1343		    dvalue = sbPtr->fromValue;
1344		}
1345	    }
1346	    sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue);
1347	    EntryValueChanged(entryPtr, sbPtr->formatBuf);
1348	}
1349    }
1350
1351    /*
1352     * Set up a trace on the variable's value after we've possibly constrained
1353     * the value according to new -from/-to values.
1354     */
1355
1356    if ((entryPtr->textVarName != NULL)
1357	    && !(entryPtr->flags & ENTRY_VAR_TRACED)) {
1358	Tcl_TraceVar(interp, entryPtr->textVarName,
1359		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
1360		EntryTextVarProc, (ClientData) entryPtr);
1361	entryPtr->flags |= ENTRY_VAR_TRACED;
1362    }
1363
1364    EntryWorldChanged((ClientData) entryPtr);
1365    if (error) {
1366	Tcl_SetObjResult(interp, errorResult);
1367	Tcl_DecrRefCount(errorResult);
1368	return TCL_ERROR;
1369    } else {
1370	return TCL_OK;
1371    }
1372}
1373
1374/*
1375 *---------------------------------------------------------------------------
1376 *
1377 * EntryWorldChanged --
1378 *
1379 *	This function is called when the world has changed in some way and the
1380 *	widget needs to recompute all its graphics contexts and determine its
1381 *	new geometry.
1382 *
1383 * Results:
1384 *	None.
1385 *
1386 * Side effects:
1387 *	Entry will be relayed out and redisplayed.
1388 *
1389 *---------------------------------------------------------------------------
1390 */
1391
1392static void
1393EntryWorldChanged(
1394    ClientData instanceData)	/* Information about widget. */
1395{
1396    XGCValues gcValues;
1397    GC gc = None;
1398    unsigned long mask;
1399    Tk_3DBorder border;
1400    XColor *colorPtr;
1401    Entry *entryPtr = (Entry *) instanceData;
1402
1403    entryPtr->avgWidth = Tk_TextWidth(entryPtr->tkfont, "0", 1);
1404    if (entryPtr->avgWidth == 0) {
1405	entryPtr->avgWidth = 1;
1406    }
1407
1408    if (entryPtr->type == TK_SPINBOX) {
1409	/*
1410	 * Compute the button width for a spinbox
1411	 */
1412
1413	entryPtr->xWidth = entryPtr->avgWidth + 2 * (1+XPAD);
1414	if (entryPtr->xWidth < 11) {
1415	    entryPtr->xWidth = 11; /* we want a min visible size */
1416	}
1417    }
1418
1419    /*
1420     * Default background and foreground are from the normal state. In a
1421     * disabled state, both of those may be overridden; in the readonly state,
1422     * the background may be overridden.
1423     */
1424
1425    border	= entryPtr->normalBorder;
1426    colorPtr	= entryPtr->fgColorPtr;
1427    switch (entryPtr->state) {
1428    case STATE_DISABLED:
1429	if (entryPtr->disabledBorder != NULL) {
1430	    border = entryPtr->disabledBorder;
1431	}
1432	if (entryPtr->dfgColorPtr != NULL) {
1433	    colorPtr = entryPtr->dfgColorPtr;
1434	}
1435	break;
1436    case STATE_READONLY:
1437	if (entryPtr->readonlyBorder != NULL) {
1438	    border = entryPtr->readonlyBorder;
1439	}
1440	break;
1441    }
1442
1443    Tk_SetBackgroundFromBorder(entryPtr->tkwin, border);
1444    gcValues.foreground = colorPtr->pixel;
1445    gcValues.font = Tk_FontId(entryPtr->tkfont);
1446    gcValues.graphics_exposures = False;
1447    mask = GCForeground | GCFont | GCGraphicsExposures;
1448    gc = Tk_GetGC(entryPtr->tkwin, mask, &gcValues);
1449    if (entryPtr->textGC != None) {
1450	Tk_FreeGC(entryPtr->display, entryPtr->textGC);
1451    }
1452    entryPtr->textGC = gc;
1453
1454    if (entryPtr->selFgColorPtr != NULL) {
1455	gcValues.foreground = entryPtr->selFgColorPtr->pixel;
1456    }
1457    gcValues.font = Tk_FontId(entryPtr->tkfont);
1458    mask = GCForeground | GCFont;
1459    gc = Tk_GetGC(entryPtr->tkwin, mask, &gcValues);
1460    if (entryPtr->selTextGC != None) {
1461	Tk_FreeGC(entryPtr->display, entryPtr->selTextGC);
1462    }
1463    entryPtr->selTextGC = gc;
1464
1465    /*
1466     * Recompute the window's geometry and arrange for it to be redisplayed.
1467     */
1468
1469    EntryComputeGeometry(entryPtr);
1470    entryPtr->flags |= UPDATE_SCROLLBAR;
1471    EventuallyRedraw(entryPtr);
1472}
1473
1474#ifndef MAC_OSX_TK
1475/*
1476 *--------------------------------------------------------------
1477 *
1478 * TkpDrawEntryBorderAndFocus --
1479 *
1480 *	This function redraws the border of an entry widget. It overrides the
1481 *	generic border drawing code if the entry widget parameters are such
1482 *	that the native widget drawing is a good fit. This version just
1483 *	returns 0, so platforms that don't do special native drawing don't
1484 *	have to implement it.
1485 *
1486 * Results:
1487 *	1 if it has drawn the border, 0 if not.
1488 *
1489 * Side effects:
1490 *	May draw the entry border into pixmap.
1491 *
1492 *--------------------------------------------------------------
1493 */
1494
1495int
1496TkpDrawEntryBorderAndFocus(
1497    Entry *entryPtr,
1498    Drawable pixmap,
1499    int isSpinbox)
1500{
1501    return 0;
1502}
1503
1504/*
1505 *--------------------------------------------------------------
1506 *
1507 * TkpDrawSpinboxButtons --
1508 *
1509 *	This function redraws the buttons of an spinbox widget. It overrides
1510 *	the generic button drawing code if the spinbox widget parameters are
1511 *	such that the native widget drawing is a good fit. This version just
1512 *	returns 0, so platforms that don't do special native drawing don't
1513 *	have to implement it.
1514 *
1515 * Results:
1516 *	1 if it has drawn the border, 0 if not.
1517 *
1518 * Side effects:
1519 *	May draw the entry border into pixmap.
1520 *
1521 *--------------------------------------------------------------
1522 */
1523
1524int
1525TkpDrawSpinboxButtons(
1526    Spinbox *sbPtr,
1527    Pixmap pixmap)
1528{
1529    return 0;
1530}
1531#endif /* Not MAC_OSX_TK */
1532
1533/*
1534 *--------------------------------------------------------------
1535 *
1536 * DisplayEntry --
1537 *
1538 *	This function redraws the contents of an entry window.
1539 *
1540 * Results:
1541 *	None.
1542 *
1543 * Side effects:
1544 *	Information appears on the screen.
1545 *
1546 *--------------------------------------------------------------
1547 */
1548
1549static void
1550DisplayEntry(
1551    ClientData clientData)	/* Information about window. */
1552{
1553    Entry *entryPtr = (Entry *) clientData;
1554    Tk_Window tkwin = entryPtr->tkwin;
1555    int baseY, selStartX, selEndX, cursorX;
1556    int showSelection, xBound;
1557    Tk_FontMetrics fm;
1558    Pixmap pixmap;
1559    Tk_3DBorder border;
1560
1561    entryPtr->flags &= ~REDRAW_PENDING;
1562    if ((entryPtr->flags & ENTRY_DELETED) || !Tk_IsMapped(tkwin)) {
1563	return;
1564    }
1565
1566    Tk_GetFontMetrics(entryPtr->tkfont, &fm);
1567
1568    /*
1569     * Update the scrollbar if that's needed.
1570     */
1571
1572    if (entryPtr->flags & UPDATE_SCROLLBAR) {
1573	entryPtr->flags &= ~UPDATE_SCROLLBAR;
1574
1575	/*
1576	 * Preserve/Release because updating the scrollbar can have the
1577	 * side-effect of destroying or unmapping the entry widget.
1578	 */
1579
1580	Tcl_Preserve((ClientData) entryPtr);
1581	EntryUpdateScrollbar(entryPtr);
1582
1583	if ((entryPtr->flags & ENTRY_DELETED) || !Tk_IsMapped(tkwin)) {
1584	    Tcl_Release((ClientData) entryPtr);
1585	    return;
1586	}
1587	Tcl_Release((ClientData) entryPtr);
1588    }
1589
1590#ifndef TK_NO_DOUBLE_BUFFERING
1591    /*
1592     * In order to avoid screen flashes, this function redraws the textual
1593     * area of the entry into off-screen memory, then copies it back on-screen
1594     * in a single operation. This means there's no point in time where the
1595     * on-screen image has been cleared.
1596     */
1597
1598    pixmap = Tk_GetPixmap(entryPtr->display, Tk_WindowId(tkwin),
1599	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
1600#else
1601    pixmap = Tk_WindowId(tkwin);
1602#endif /* TK_NO_DOUBLE_BUFFERING */
1603
1604    /*
1605     * Compute x-coordinate of the pixel just after last visible one, plus
1606     * vertical position of baseline of text.
1607     */
1608
1609    xBound = Tk_Width(tkwin) - entryPtr->inset - entryPtr->xWidth;
1610    baseY = (Tk_Height(tkwin) + fm.ascent - fm.descent) / 2;
1611
1612    /*
1613     * Hide the selection whenever we don't have the focus, unless we
1614     * always want to show selection.
1615     */
1616    if (TkpAlwaysShowSelection(entryPtr->tkwin)) {
1617	showSelection = 1;
1618    } else {
1619	showSelection = (entryPtr->flags & GOT_FOCUS);
1620    }
1621
1622    /*
1623     * Draw the background in three layers. From bottom to top the layers are:
1624     * normal background, selection background, and insertion cursor
1625     * background.
1626     */
1627
1628    if ((entryPtr->state == STATE_DISABLED) &&
1629	    (entryPtr->disabledBorder != NULL)) {
1630	border = entryPtr->disabledBorder;
1631    } else if ((entryPtr->state == STATE_READONLY) &&
1632	    (entryPtr->readonlyBorder != NULL)) {
1633	border = entryPtr->readonlyBorder;
1634    } else {
1635	border = entryPtr->normalBorder;
1636    }
1637    Tk_Fill3DRectangle(tkwin, pixmap, border,
1638	    0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
1639
1640    if (showSelection && (entryPtr->state != STATE_DISABLED)
1641	    && (entryPtr->selectLast > entryPtr->leftIndex)) {
1642	if (entryPtr->selectFirst <= entryPtr->leftIndex) {
1643	    selStartX = entryPtr->leftX;
1644	} else {
1645	    Tk_CharBbox(entryPtr->textLayout, entryPtr->selectFirst,
1646		    &selStartX, NULL, NULL, NULL);
1647	    selStartX += entryPtr->layoutX;
1648	}
1649	if ((selStartX - entryPtr->selBorderWidth) < xBound) {
1650	    Tk_CharBbox(entryPtr->textLayout, entryPtr->selectLast,
1651		    &selEndX, NULL, NULL, NULL);
1652	    selEndX += entryPtr->layoutX;
1653	    Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->selBorder,
1654		    selStartX - entryPtr->selBorderWidth,
1655		    baseY - fm.ascent - entryPtr->selBorderWidth,
1656		    (selEndX - selStartX) + 2*entryPtr->selBorderWidth,
1657		    (fm.ascent + fm.descent) + 2*entryPtr->selBorderWidth,
1658		    entryPtr->selBorderWidth,
1659#ifndef MAC_OSX_TK
1660		    TK_RELIEF_RAISED
1661#else
1662		    MAC_OSX_ENTRY_SELECT_RELIEF
1663#endif
1664		    );
1665	}
1666    }
1667
1668    /*
1669     * Draw a special background for the insertion cursor, overriding even the
1670     * selection background. As a special hack to keep the cursor visible when
1671     * the insertion cursor color is the same as the color for selected text
1672     * (e.g., on mono displays), write background in the cursor area (instead
1673     * of nothing) when the cursor isn't on. Otherwise the selection would
1674     * hide the cursor.
1675     */
1676
1677    if ((entryPtr->state == STATE_NORMAL) && (entryPtr->flags & GOT_FOCUS)) {
1678	Tk_CharBbox(entryPtr->textLayout, entryPtr->insertPos, &cursorX, NULL,
1679		NULL, NULL);
1680	cursorX += entryPtr->layoutX;
1681	cursorX -= (entryPtr->insertWidth)/2;
1682	Tk_SetCaretPos(entryPtr->tkwin, cursorX, baseY - fm.ascent,
1683		fm.ascent + fm.descent);
1684	if (entryPtr->insertPos >= entryPtr->leftIndex && cursorX < xBound) {
1685	    if (entryPtr->flags & CURSOR_ON) {
1686		Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->insertBorder,
1687			cursorX, baseY - fm.ascent, entryPtr->insertWidth,
1688			fm.ascent + fm.descent, entryPtr->insertBorderWidth,
1689			TK_RELIEF_RAISED);
1690	    } else if (entryPtr->insertBorder == entryPtr->selBorder) {
1691		Tk_Fill3DRectangle(tkwin, pixmap, border, cursorX,
1692			baseY - fm.ascent, entryPtr->insertWidth,
1693			fm.ascent + fm.descent, 0, TK_RELIEF_FLAT);
1694	    }
1695	}
1696    }
1697
1698    /*
1699     * Draw the text in two pieces: first the unselected portion, then the
1700     * selected portion on top of it.
1701     */
1702
1703    Tk_DrawTextLayout(entryPtr->display, pixmap, entryPtr->textGC,
1704	    entryPtr->textLayout, entryPtr->layoutX, entryPtr->layoutY,
1705	    entryPtr->leftIndex, entryPtr->numChars);
1706
1707    if (showSelection && (entryPtr->state != STATE_DISABLED)
1708	    && (entryPtr->selTextGC != entryPtr->textGC)
1709	    && (entryPtr->selectFirst < entryPtr->selectLast)) {
1710	int selFirst;
1711
1712	if (entryPtr->selectFirst < entryPtr->leftIndex) {
1713	    selFirst = entryPtr->leftIndex;
1714	} else {
1715	    selFirst = entryPtr->selectFirst;
1716	}
1717	Tk_DrawTextLayout(entryPtr->display, pixmap, entryPtr->selTextGC,
1718		entryPtr->textLayout, entryPtr->layoutX, entryPtr->layoutY,
1719		selFirst, entryPtr->selectLast);
1720    }
1721
1722    if (entryPtr->type == TK_SPINBOX) {
1723	int startx, height, inset, pad, tHeight, xWidth;
1724	Spinbox *sbPtr = (Spinbox *) entryPtr;
1725
1726	/*
1727	 * Draw the spin button controls.
1728	 */
1729
1730	if (TkpDrawSpinboxButtons(sbPtr, pixmap) == 0) {
1731	    xWidth = entryPtr->xWidth;
1732	    pad = XPAD + 1;
1733	    inset = entryPtr->inset - XPAD;
1734	    startx = Tk_Width(tkwin) - (xWidth + inset);
1735	    height = (Tk_Height(tkwin) - 2*inset)/2;
1736#if 0
1737	    Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
1738		    startx, inset, xWidth, height, 1, sbPtr->buRelief);
1739	    Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
1740		    startx, inset+height, xWidth, height, 1, sbPtr->bdRelief);
1741#else
1742	    Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
1743		    startx, inset, xWidth, height, 1,
1744		    (sbPtr->selElement == SEL_BUTTONUP) ?
1745		    TK_RELIEF_SUNKEN : TK_RELIEF_RAISED);
1746	    Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder,
1747		    startx, inset+height, xWidth, height, 1,
1748		    (sbPtr->selElement == SEL_BUTTONDOWN) ?
1749		    TK_RELIEF_SUNKEN : TK_RELIEF_RAISED);
1750#endif
1751
1752	    xWidth -= 2*pad;
1753
1754	    /*
1755	     * Only draw the triangles if we have enough display space
1756	     */
1757
1758	    if ((xWidth > 1)) {
1759		XPoint points[3];
1760		int starty, space, offset;
1761
1762		space = height - 2*pad;
1763
1764		/*
1765		 * Ensure width of triangle is odd to guarantee a sharp tip
1766		 */
1767
1768		if (!(xWidth % 2)) {
1769		    xWidth++;
1770		}
1771		tHeight = (xWidth + 1) / 2;
1772		if (tHeight > space) {
1773		    tHeight = space;
1774		}
1775		space	= (space - tHeight) / 2;
1776		startx += pad;
1777		starty	= inset + height - pad - space;
1778		offset	= (sbPtr->selElement == SEL_BUTTONUP);
1779
1780		/*
1781		 * The points are slightly different for the up and down
1782		 * arrows because (for *.x), we need to account for a bug in
1783		 * the way XFillPolygon draws triangles, and we want to shift
1784		 * the arrows differently when allowing for depressed
1785		 * behavior.
1786		 */
1787
1788		points[0].x = startx + offset;
1789		points[0].y = starty + (offset ? 0 : -1);
1790		points[1].x = startx + xWidth/2 + offset;
1791		points[1].y = starty - tHeight + (offset ? 0 : -1);
1792		points[2].x = startx + xWidth + offset;
1793		points[2].y = points[0].y;
1794		XFillPolygon(entryPtr->display, pixmap, entryPtr->textGC,
1795			points, 3, Convex, CoordModeOrigin);
1796
1797		starty = inset + height + pad + space;
1798		offset = (sbPtr->selElement == SEL_BUTTONDOWN);
1799		points[0].x = startx + 1 + offset;
1800		points[0].y = starty + (offset ? 1 : 0);
1801		points[1].x = startx + xWidth/2 + offset;
1802		points[1].y = starty + tHeight + (offset ? 0 : -1);
1803		points[2].x = startx - 1 + xWidth + offset;
1804		points[2].y = points[0].y;
1805		XFillPolygon(entryPtr->display, pixmap, entryPtr->textGC,
1806			points, 3, Convex, CoordModeOrigin);
1807	    }
1808	}
1809    }
1810
1811    /*
1812     * Draw the border and focus highlight last, so they will overwrite any
1813     * text that extends past the viewable part of the window.
1814     */
1815
1816    if (!TkpDrawEntryBorderAndFocus(entryPtr, pixmap,
1817	    (entryPtr->type == TK_SPINBOX))) {
1818	xBound = entryPtr->highlightWidth;
1819	if (entryPtr->relief != TK_RELIEF_FLAT) {
1820	    Tk_Draw3DRectangle(tkwin, pixmap, border, xBound, xBound,
1821		    Tk_Width(tkwin) - 2 * xBound,
1822		    Tk_Height(tkwin) - 2 * xBound,
1823		    entryPtr->borderWidth, entryPtr->relief);
1824	}
1825	if (xBound > 0) {
1826	    GC fgGC, bgGC;
1827
1828	    bgGC = Tk_GCForColor(entryPtr->highlightBgColorPtr, pixmap);
1829	    if (entryPtr->flags & GOT_FOCUS) {
1830		fgGC = Tk_GCForColor(entryPtr->highlightColorPtr, pixmap);
1831		TkpDrawHighlightBorder(tkwin, fgGC, bgGC, xBound, pixmap);
1832	    } else {
1833		TkpDrawHighlightBorder(tkwin, bgGC, bgGC, xBound, pixmap);
1834	    }
1835	}
1836    }
1837
1838#ifndef TK_NO_DOUBLE_BUFFERING
1839    /*
1840     * Everything's been redisplayed; now copy the pixmap onto the screen and
1841     * free up the pixmap.
1842     */
1843
1844    XCopyArea(entryPtr->display, pixmap, Tk_WindowId(tkwin), entryPtr->textGC,
1845	    0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
1846	    0, 0);
1847    Tk_FreePixmap(entryPtr->display, pixmap);
1848#endif /* TK_NO_DOUBLE_BUFFERING */
1849    entryPtr->flags &= ~BORDER_NEEDED;
1850}
1851
1852/*
1853 *----------------------------------------------------------------------
1854 *
1855 * EntryComputeGeometry --
1856 *
1857 *	This function is invoked to recompute information about where in its
1858 *	window an entry's string will be displayed. It also computes the
1859 *	requested size for the window.
1860 *
1861 * Results:
1862 *	None.
1863 *
1864 * Side effects:
1865 *	The leftX and tabOrigin fields are recomputed for entryPtr, and
1866 *	leftIndex may be adjusted. Tk_GeometryRequest is called to register
1867 *	the desired dimensions for the window.
1868 *
1869 *----------------------------------------------------------------------
1870 */
1871
1872static void
1873EntryComputeGeometry(
1874    Entry *entryPtr)		/* Widget record for entry. */
1875{
1876    int totalLength, overflow, maxOffScreen, rightX;
1877    int height, width, i;
1878    Tk_FontMetrics fm;
1879    char *p;
1880
1881    if (entryPtr->displayString != entryPtr->string) {
1882	ckfree((char *)entryPtr->displayString);
1883	entryPtr->displayString = entryPtr->string;
1884	entryPtr->numDisplayBytes = entryPtr->numBytes;
1885    }
1886
1887    /*
1888     * If we're displaying a special character instead of the value of the
1889     * entry, recompute the displayString.
1890     */
1891
1892    if (entryPtr->showChar != NULL) {
1893	Tcl_UniChar ch;
1894	char buf[TCL_UTF_MAX];
1895	int size;
1896
1897	/*
1898	 * Normalize the special character so we can safely duplicate it in
1899	 * the display string. If we didn't do this, then two malformed
1900	 * characters might end up looking like one valid UTF character in the
1901	 * resulting string.
1902	 */
1903
1904	Tcl_UtfToUniChar(entryPtr->showChar, &ch);
1905	size = Tcl_UniCharToUtf(ch, buf);
1906
1907	entryPtr->numDisplayBytes = entryPtr->numChars * size;
1908	p = (char *) ckalloc((unsigned) (entryPtr->numDisplayBytes + 1));
1909	entryPtr->displayString = p;
1910
1911	for (i = entryPtr->numChars; --i >= 0; ) {
1912	    p += Tcl_UniCharToUtf(ch, p);
1913	}
1914	*p = '\0';
1915    }
1916
1917    Tk_FreeTextLayout(entryPtr->textLayout);
1918    entryPtr->textLayout = Tk_ComputeTextLayout(entryPtr->tkfont,
1919	    entryPtr->displayString, entryPtr->numChars, 0,
1920	    entryPtr->justify, TK_IGNORE_NEWLINES, &totalLength, &height);
1921
1922    entryPtr->layoutY = (Tk_Height(entryPtr->tkwin) - height) / 2;
1923
1924    /*
1925     * Recompute where the leftmost character on the display will be drawn
1926     * (entryPtr->leftX) and adjust leftIndex if necessary so that we don't
1927     * let characters hang off the edge of the window unless the entire window
1928     * is full.
1929     */
1930
1931    overflow = totalLength -
1932	    (Tk_Width(entryPtr->tkwin) - 2*entryPtr->inset - entryPtr->xWidth);
1933    if (overflow <= 0) {
1934	entryPtr->leftIndex = 0;
1935	if (entryPtr->justify == TK_JUSTIFY_LEFT) {
1936	    entryPtr->leftX = entryPtr->inset;
1937	} else if (entryPtr->justify == TK_JUSTIFY_RIGHT) {
1938	    entryPtr->leftX = Tk_Width(entryPtr->tkwin) - entryPtr->inset
1939		    - entryPtr->xWidth - totalLength;
1940	} else {
1941	    entryPtr->leftX = (Tk_Width(entryPtr->tkwin)
1942		    - entryPtr->xWidth - totalLength)/2;
1943	}
1944	entryPtr->layoutX = entryPtr->leftX;
1945    } else {
1946	/*
1947	 * The whole string can't fit in the window. Compute the maximum
1948	 * number of characters that may be off-screen to the left without
1949	 * leaving empty space on the right of the window, then don't let
1950	 * leftIndex be any greater than that.
1951	 */
1952
1953	maxOffScreen = Tk_PointToChar(entryPtr->textLayout, overflow, 0);
1954	Tk_CharBbox(entryPtr->textLayout, maxOffScreen,
1955		&rightX, NULL, NULL, NULL);
1956	if (rightX < overflow) {
1957	    maxOffScreen++;
1958	}
1959	if (entryPtr->leftIndex > maxOffScreen) {
1960	    entryPtr->leftIndex = maxOffScreen;
1961	}
1962	Tk_CharBbox(entryPtr->textLayout, entryPtr->leftIndex, &rightX,
1963		NULL, NULL, NULL);
1964	entryPtr->leftX = entryPtr->inset;
1965	entryPtr->layoutX = entryPtr->leftX - rightX;
1966    }
1967
1968    Tk_GetFontMetrics(entryPtr->tkfont, &fm);
1969    height = fm.linespace + 2*entryPtr->inset + 2*(YPAD-XPAD);
1970    if (entryPtr->prefWidth > 0) {
1971	width = entryPtr->prefWidth*entryPtr->avgWidth + 2*entryPtr->inset;
1972    } else {
1973	if (totalLength == 0) {
1974	    width = entryPtr->avgWidth + 2*entryPtr->inset;
1975	} else {
1976	    width = totalLength + 2*entryPtr->inset;
1977	}
1978    }
1979
1980    /*
1981     * Add one extra length for the spin buttons
1982     */
1983    width += entryPtr->xWidth;
1984
1985    Tk_GeometryRequest(entryPtr->tkwin, width, height);
1986}
1987
1988/*
1989 *----------------------------------------------------------------------
1990 *
1991 * InsertChars --
1992 *
1993 *	Add new characters to an entry widget.
1994 *
1995 * Results:
1996 *	None.
1997 *
1998 * Side effects:
1999 *	New information gets added to entryPtr; it will be redisplayed soon,
2000 *	but not necessarily immediately.
2001 *
2002 *----------------------------------------------------------------------
2003 */
2004
2005static void
2006InsertChars(
2007    Entry *entryPtr,		/* Entry that is to get the new elements. */
2008    int index,			/* Add the new elements before this character
2009				 * index. */
2010    char *value)		/* New characters to add (NULL-terminated
2011				 * string). */
2012{
2013    ptrdiff_t byteIndex;
2014    size_t byteCount, newByteCount;
2015    int oldChars, charsAdded;
2016    CONST char *string;
2017    char *newStr;
2018
2019    string = entryPtr->string;
2020    byteIndex = Tcl_UtfAtIndex(string, index) - string;
2021    byteCount = strlen(value);
2022    if (byteCount == 0) {
2023	return;
2024    }
2025
2026    newByteCount = entryPtr->numBytes + byteCount + 1;
2027    newStr = (char *) ckalloc((unsigned) newByteCount);
2028    memcpy(newStr, string, byteIndex);
2029    strcpy(newStr + byteIndex, value);
2030    strcpy(newStr + byteIndex + byteCount, string + byteIndex);
2031
2032    if ((entryPtr->validate == VALIDATE_KEY ||
2033	    entryPtr->validate == VALIDATE_ALL) &&
2034	    EntryValidateChange(entryPtr, value, newStr, index,
2035		    VALIDATE_INSERT) != TCL_OK) {
2036	ckfree(newStr);
2037	return;
2038    }
2039
2040    ckfree((char *)string);
2041    entryPtr->string = newStr;
2042
2043    /*
2044     * The following construction is used because inserting improperly formed
2045     * UTF-8 sequences between other improperly formed UTF-8 sequences could
2046     * result in actually forming valid UTF-8 sequences; the number of
2047     * characters added may not be Tcl_NumUtfChars(string, -1), because of
2048     * context. The actual number of characters added is how many characters
2049     * are in the string now minus the number that used to be there.
2050     */
2051
2052    oldChars = entryPtr->numChars;
2053    entryPtr->numChars = Tcl_NumUtfChars(newStr, -1);
2054    charsAdded = entryPtr->numChars - oldChars;
2055    entryPtr->numBytes += byteCount;
2056
2057    if (entryPtr->displayString == string) {
2058	entryPtr->displayString = newStr;
2059	entryPtr->numDisplayBytes = entryPtr->numBytes;
2060    }
2061
2062    /*
2063     * Inserting characters invalidates all indexes into the string. Touch up
2064     * the indexes so that they still refer to the same characters (at new
2065     * positions). When updating the selection end-points, don't include the
2066     * new text in the selection unless it was completely surrounded by the
2067     * selection.
2068     */
2069
2070    if (entryPtr->selectFirst >= index) {
2071	entryPtr->selectFirst += charsAdded;
2072    }
2073    if (entryPtr->selectLast > index) {
2074	entryPtr->selectLast += charsAdded;
2075    }
2076    if ((entryPtr->selectAnchor > index) || (entryPtr->selectFirst >= index)) {
2077	entryPtr->selectAnchor += charsAdded;
2078    }
2079    if (entryPtr->leftIndex > index) {
2080	entryPtr->leftIndex += charsAdded;
2081    }
2082    if (entryPtr->insertPos >= index) {
2083	entryPtr->insertPos += charsAdded;
2084    }
2085    EntryValueChanged(entryPtr, NULL);
2086}
2087
2088/*
2089 *----------------------------------------------------------------------
2090 *
2091 * DeleteChars --
2092 *
2093 *	Remove one or more characters from an entry widget.
2094 *
2095 * Results:
2096 *	None.
2097 *
2098 * Side effects:
2099 *	Memory gets freed, the entry gets modified and (eventually)
2100 *	redisplayed.
2101 *
2102 *----------------------------------------------------------------------
2103 */
2104
2105static void
2106DeleteChars(
2107    Entry *entryPtr,		/* Entry widget to modify. */
2108    int index,			/* Index of first character to delete. */
2109    int count)			/* How many characters to delete. */
2110{
2111    int byteIndex, byteCount, newByteCount;
2112    CONST char *string;
2113    char *newStr, *toDelete;
2114
2115    if ((index + count) > entryPtr->numChars) {
2116	count = entryPtr->numChars - index;
2117    }
2118    if (count <= 0) {
2119	return;
2120    }
2121
2122    string = entryPtr->string;
2123    byteIndex = Tcl_UtfAtIndex(string, index) - string;
2124    byteCount = Tcl_UtfAtIndex(string + byteIndex, count) - (string+byteIndex);
2125
2126    newByteCount = entryPtr->numBytes + 1 - byteCount;
2127    newStr = (char *) ckalloc((unsigned) newByteCount);
2128    memcpy(newStr, string, (size_t) byteIndex);
2129    strcpy(newStr + byteIndex, string + byteIndex + byteCount);
2130
2131    toDelete = (char *) ckalloc((unsigned) (byteCount + 1));
2132    memcpy(toDelete, string + byteIndex, (size_t) byteCount);
2133    toDelete[byteCount] = '\0';
2134
2135    if ((entryPtr->validate == VALIDATE_KEY ||
2136	    entryPtr->validate == VALIDATE_ALL) &&
2137	    EntryValidateChange(entryPtr, toDelete, newStr, index,
2138		    VALIDATE_DELETE) != TCL_OK) {
2139	ckfree(newStr);
2140	ckfree(toDelete);
2141	return;
2142    }
2143
2144    ckfree(toDelete);
2145    ckfree((char *)entryPtr->string);
2146    entryPtr->string = newStr;
2147    entryPtr->numChars -= count;
2148    entryPtr->numBytes -= byteCount;
2149
2150    if (entryPtr->displayString == string) {
2151	entryPtr->displayString = newStr;
2152	entryPtr->numDisplayBytes = entryPtr->numBytes;
2153    }
2154
2155    /*
2156     * Deleting characters results in the remaining characters being
2157     * renumbered. Update the various indexes into the string to reflect this
2158     * change.
2159     */
2160
2161    if (entryPtr->selectFirst >= index) {
2162	if (entryPtr->selectFirst >= (index + count)) {
2163	    entryPtr->selectFirst -= count;
2164	} else {
2165	    entryPtr->selectFirst = index;
2166	}
2167    }
2168    if (entryPtr->selectLast >= index) {
2169	if (entryPtr->selectLast >= (index + count)) {
2170	    entryPtr->selectLast -= count;
2171	} else {
2172	    entryPtr->selectLast = index;
2173	}
2174    }
2175    if (entryPtr->selectLast <= entryPtr->selectFirst) {
2176	entryPtr->selectFirst = -1;
2177	entryPtr->selectLast = -1;
2178    }
2179    if (entryPtr->selectAnchor >= index) {
2180	if (entryPtr->selectAnchor >= (index+count)) {
2181	    entryPtr->selectAnchor -= count;
2182	} else {
2183	    entryPtr->selectAnchor = index;
2184	}
2185    }
2186    if (entryPtr->leftIndex > index) {
2187	if (entryPtr->leftIndex >= (index + count)) {
2188	    entryPtr->leftIndex -= count;
2189	} else {
2190	    entryPtr->leftIndex = index;
2191	}
2192    }
2193    if (entryPtr->insertPos >= index) {
2194	if (entryPtr->insertPos >= (index + count)) {
2195	    entryPtr->insertPos -= count;
2196	} else {
2197	    entryPtr->insertPos = index;
2198	}
2199    }
2200    EntryValueChanged(entryPtr, NULL);
2201}
2202
2203/*
2204 *----------------------------------------------------------------------
2205 *
2206 * EntryValueChanged --
2207 *
2208 *	This function is invoked when characters are inserted into an entry or
2209 *	deleted from it. It updates the entry's associated variable, if there
2210 *	is one, and does other bookkeeping such as arranging for redisplay.
2211 *
2212 * Results:
2213 *	None.
2214 *
2215 * Side effects:
2216 *	None.
2217 *
2218 *----------------------------------------------------------------------
2219 */
2220
2221static void
2222EntryValueChanged(
2223    Entry *entryPtr,		/* Entry whose value just changed. */
2224    CONST char *newValue)	/* If this value is not NULL, we first force
2225				 * the value of the entry to this. */
2226{
2227    if (newValue != NULL) {
2228	EntrySetValue(entryPtr, newValue);
2229    }
2230
2231    if (entryPtr->textVarName == NULL) {
2232	newValue = NULL;
2233    } else {
2234	newValue = Tcl_SetVar(entryPtr->interp, entryPtr->textVarName,
2235		entryPtr->string, TCL_GLOBAL_ONLY);
2236    }
2237
2238    if ((newValue != NULL) && (strcmp(newValue, entryPtr->string) != 0)) {
2239	/*
2240	 * The value of the variable is different than what we asked for.
2241	 * This means that a trace on the variable modified it. In this case
2242	 * our trace function wasn't invoked since the modification came while
2243	 * a trace was already active on the variable. So, update our value to
2244	 * reflect the variable's latest value.
2245	 */
2246
2247	EntrySetValue(entryPtr, newValue);
2248    } else {
2249	/*
2250	 * Arrange for redisplay.
2251	 */
2252
2253	entryPtr->flags |= UPDATE_SCROLLBAR;
2254	EntryComputeGeometry(entryPtr);
2255	EventuallyRedraw(entryPtr);
2256    }
2257}
2258
2259/*
2260 *----------------------------------------------------------------------
2261 *
2262 * EntrySetValue --
2263 *
2264 *	Replace the contents of a text entry with a given value. This function
2265 *	is invoked when updating the entry from the entry's associated
2266 *	variable.
2267 *
2268 * Results:
2269 *	None.
2270 *
2271 * Side effects:
2272 *	The string displayed in the entry will change. The selection,
2273 *	insertion point, and view may have to be adjusted to keep them within
2274 *	the bounds of the new string. Note: this function does *not* update
2275 *	the entry's associated variable, since that could result in an
2276 *	infinite loop.
2277 *
2278 *----------------------------------------------------------------------
2279 */
2280
2281static void
2282EntrySetValue(
2283    Entry *entryPtr,		/* Entry whose value is to be changed. */
2284    CONST char *value)		/* New text to display in entry. */
2285{
2286    CONST char *oldSource;
2287    int valueLen, malloced = 0;
2288
2289    if (strcmp(value, entryPtr->string) == 0) {
2290	return;
2291    }
2292    valueLen = strlen(value);
2293
2294    if (entryPtr->flags & VALIDATE_VAR) {
2295	entryPtr->flags |= VALIDATE_ABORT;
2296    } else {
2297	/*
2298	 * If we validate, we create a copy of the value, as it may point to
2299	 * volatile memory, like the value of the -textvar which may get freed
2300	 * during validation
2301	 */
2302
2303	char *tmp = (char *) ckalloc((unsigned) (valueLen + 1));
2304
2305	strcpy(tmp, value);
2306	value = tmp;
2307	malloced = 1;
2308
2309	entryPtr->flags |= VALIDATE_VAR;
2310	(void) EntryValidateChange(entryPtr, NULL, value, -1,
2311		VALIDATE_FORCED);
2312	entryPtr->flags &= ~VALIDATE_VAR;
2313
2314	/*
2315	 * If VALIDATE_ABORT has been set, then this operation should be
2316	 * aborted because the validatecommand did something else instead
2317	 */
2318
2319	if (entryPtr->flags & VALIDATE_ABORT) {
2320	    entryPtr->flags &= ~VALIDATE_ABORT;
2321	    ckfree((char *)value);
2322	    return;
2323	}
2324    }
2325
2326    oldSource = entryPtr->string;
2327    ckfree((char *)entryPtr->string);
2328
2329    if (malloced) {
2330	entryPtr->string = value;
2331    } else {
2332	char *tmp = (char *) ckalloc((unsigned) (valueLen + 1));
2333
2334	strcpy(tmp, value);
2335	entryPtr->string = tmp;
2336    }
2337    entryPtr->numBytes = valueLen;
2338    entryPtr->numChars = Tcl_NumUtfChars(value, valueLen);
2339
2340    if (entryPtr->displayString == oldSource) {
2341	entryPtr->displayString = entryPtr->string;
2342	entryPtr->numDisplayBytes = entryPtr->numBytes;
2343    }
2344
2345    if (entryPtr->selectFirst >= 0) {
2346	if (entryPtr->selectFirst >= entryPtr->numChars) {
2347	    entryPtr->selectFirst = -1;
2348	    entryPtr->selectLast = -1;
2349	} else if (entryPtr->selectLast > entryPtr->numChars) {
2350	    entryPtr->selectLast = entryPtr->numChars;
2351	}
2352    }
2353    if (entryPtr->leftIndex >= entryPtr->numChars) {
2354	if (entryPtr->numChars > 0) {
2355	    entryPtr->leftIndex = entryPtr->numChars - 1;
2356	} else {
2357	    entryPtr->leftIndex = 0;
2358	}
2359    }
2360    if (entryPtr->insertPos > entryPtr->numChars) {
2361	entryPtr->insertPos = entryPtr->numChars;
2362    }
2363
2364    entryPtr->flags |= UPDATE_SCROLLBAR;
2365    EntryComputeGeometry(entryPtr);
2366    EventuallyRedraw(entryPtr);
2367}
2368
2369/*
2370 *--------------------------------------------------------------
2371 *
2372 * EntryEventProc --
2373 *
2374 *	This function is invoked by the Tk dispatcher for various events on
2375 *	entries.
2376 *
2377 * Results:
2378 *	None.
2379 *
2380 * Side effects:
2381 *	When the window gets deleted, internal structures get cleaned up.
2382 *	When it gets exposed, it is redisplayed.
2383 *
2384 *--------------------------------------------------------------
2385 */
2386
2387static void
2388EntryEventProc(
2389    ClientData clientData,	/* Information about window. */
2390    XEvent *eventPtr)		/* Information about event. */
2391{
2392    Entry *entryPtr = (Entry *) clientData;
2393
2394    if ((entryPtr->type == TK_SPINBOX) && (eventPtr->type == MotionNotify)) {
2395	Spinbox *sbPtr = (Spinbox *) clientData;
2396	int elem;
2397
2398	elem = GetSpinboxElement(sbPtr, eventPtr->xmotion.x,
2399		eventPtr->xmotion.y);
2400	if (elem != sbPtr->curElement) {
2401	    Tk_Cursor cursor;
2402
2403	    sbPtr->curElement = elem;
2404	    if (elem == SEL_ENTRY) {
2405		cursor = entryPtr->cursor;
2406	    } else if ((elem == SEL_BUTTONDOWN) || (elem == SEL_BUTTONUP)) {
2407		cursor = sbPtr->bCursor;
2408	    } else {
2409		cursor = None;
2410	    }
2411	    if (cursor != None) {
2412		Tk_DefineCursor(entryPtr->tkwin, cursor);
2413	    } else {
2414		Tk_UndefineCursor(entryPtr->tkwin);
2415	    }
2416	}
2417	return;
2418    }
2419
2420    switch (eventPtr->type) {
2421    case Expose:
2422	EventuallyRedraw(entryPtr);
2423	entryPtr->flags |= BORDER_NEEDED;
2424	break;
2425    case DestroyNotify:
2426	if (!(entryPtr->flags & ENTRY_DELETED)) {
2427	    entryPtr->flags |= (ENTRY_DELETED | VALIDATE_ABORT);
2428	    Tcl_DeleteCommandFromToken(entryPtr->interp, entryPtr->widgetCmd);
2429	    if (entryPtr->flags & REDRAW_PENDING) {
2430		Tcl_CancelIdleCall(DisplayEntry, clientData);
2431	    }
2432	    Tcl_EventuallyFree(clientData, DestroyEntry);
2433	}
2434	break;
2435    case ConfigureNotify:
2436	Tcl_Preserve((ClientData) entryPtr);
2437	entryPtr->flags |= UPDATE_SCROLLBAR;
2438	EntryComputeGeometry(entryPtr);
2439	EventuallyRedraw(entryPtr);
2440	Tcl_Release((ClientData) entryPtr);
2441	break;
2442    case FocusIn:
2443    case FocusOut:
2444	if (eventPtr->xfocus.detail != NotifyInferior) {
2445	    EntryFocusProc(entryPtr, (eventPtr->type == FocusIn));
2446	}
2447	break;
2448    }
2449}
2450
2451/*
2452 *----------------------------------------------------------------------
2453 *
2454 * EntryCmdDeletedProc --
2455 *
2456 *	This function is invoked when a widget command is deleted. If the
2457 *	widget isn't already in the process of being destroyed, this command
2458 *	destroys it.
2459 *
2460 * Results:
2461 *	None.
2462 *
2463 * Side effects:
2464 *	The widget is destroyed.
2465 *
2466 *----------------------------------------------------------------------
2467 */
2468
2469static void
2470EntryCmdDeletedProc(
2471    ClientData clientData)	/* Pointer to widget record for widget. */
2472{
2473    Entry *entryPtr = (Entry *) clientData;
2474
2475    /*
2476     * This function could be invoked either because the window was destroyed
2477     * and the command was then deleted (in which case tkwin is NULL) or
2478     * because the command was deleted, and then this function destroys the
2479     * widget.
2480     */
2481
2482    if (!(entryPtr->flags & ENTRY_DELETED)) {
2483	Tk_DestroyWindow(entryPtr->tkwin);
2484    }
2485}
2486
2487/*
2488 *---------------------------------------------------------------------------
2489 *
2490 * GetEntryIndex --
2491 *
2492 *	Parse an index into an entry and return either its value or an error.
2493 *
2494 * Results:
2495 *	A standard Tcl result. If all went well, then *indexPtr is filled in
2496 *	with the character index (into entryPtr) corresponding to string. The
2497 *	index value is guaranteed to lie between 0 and the number of
2498 *	characters in the string, inclusive. If an error occurs then an error
2499 *	message is left in the interp's result.
2500 *
2501 * Side effects:
2502 *	None.
2503 *
2504 *---------------------------------------------------------------------------
2505 */
2506
2507static int
2508GetEntryIndex(
2509    Tcl_Interp *interp,		/* For error messages. */
2510    Entry *entryPtr,		/* Entry for which the index is being
2511				 * specified. */
2512    char *string,		/* Specifies character in entryPtr. */
2513    int *indexPtr)		/* Where to store converted character index */
2514{
2515    size_t length;
2516
2517    length = strlen(string);
2518
2519    if (string[0] == 'a') {
2520	if (strncmp(string, "anchor", length) == 0) {
2521	    *indexPtr = entryPtr->selectAnchor;
2522	} else {
2523	badIndex:
2524
2525	    /*
2526	     * Some of the paths here leave messages in the interp's result,
2527	     * so we have to clear it out before storing our own message.
2528	     */
2529
2530	    Tcl_SetResult(interp, NULL, TCL_STATIC);
2531	    Tcl_AppendResult(interp, "bad ",
2532		    (entryPtr->type == TK_ENTRY) ? "entry" : "spinbox",
2533		    " index \"", string, "\"", NULL);
2534	    return TCL_ERROR;
2535	}
2536    } else if (string[0] == 'e') {
2537	if (strncmp(string, "end", length) == 0) {
2538	    *indexPtr = entryPtr->numChars;
2539	} else {
2540	    goto badIndex;
2541	}
2542    } else if (string[0] == 'i') {
2543	if (strncmp(string, "insert", length) == 0) {
2544	    *indexPtr = entryPtr->insertPos;
2545	} else {
2546	    goto badIndex;
2547	}
2548    } else if (string[0] == 's') {
2549	if (entryPtr->selectFirst < 0) {
2550	    Tcl_SetResult(interp, NULL, TCL_STATIC);
2551	    Tcl_AppendResult(interp, "selection isn't in widget ",
2552		    Tk_PathName(entryPtr->tkwin), NULL);
2553	    return TCL_ERROR;
2554	}
2555	if (length < 5) {
2556	    goto badIndex;
2557	}
2558	if (strncmp(string, "sel.first", length) == 0) {
2559	    *indexPtr = entryPtr->selectFirst;
2560	} else if (strncmp(string, "sel.last", length) == 0) {
2561	    *indexPtr = entryPtr->selectLast;
2562	} else {
2563	    goto badIndex;
2564	}
2565    } else if (string[0] == '@') {
2566	int x, roundUp, maxWidth;
2567
2568	if (Tcl_GetInt(interp, string + 1, &x) != TCL_OK) {
2569	    goto badIndex;
2570	}
2571	if (x < entryPtr->inset) {
2572	    x = entryPtr->inset;
2573	}
2574	roundUp = 0;
2575	maxWidth = Tk_Width(entryPtr->tkwin) - entryPtr->inset
2576	    - entryPtr->xWidth - 1;
2577	if (x > maxWidth) {
2578	    x = maxWidth;
2579	    roundUp = 1;
2580	}
2581	*indexPtr = Tk_PointToChar(entryPtr->textLayout,
2582		x - entryPtr->layoutX, 0);
2583
2584	/*
2585	 * Special trick: if the x-position was off-screen to the right, round
2586	 * the index up to refer to the character just after the last visible
2587	 * one on the screen. This is needed to enable the last character to
2588	 * be selected, for example.
2589	 */
2590
2591	if (roundUp && (*indexPtr < entryPtr->numChars)) {
2592	    *indexPtr += 1;
2593	}
2594    } else {
2595	if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) {
2596	    goto badIndex;
2597	}
2598	if (*indexPtr < 0){
2599	    *indexPtr = 0;
2600	} else if (*indexPtr > entryPtr->numChars) {
2601	    *indexPtr = entryPtr->numChars;
2602	}
2603    }
2604    return TCL_OK;
2605}
2606
2607/*
2608 *----------------------------------------------------------------------
2609 *
2610 * EntryScanTo --
2611 *
2612 *	Given a y-coordinate (presumably of the curent mouse location) drag
2613 *	the view in the window to implement the scan operation.
2614 *
2615 * Results:
2616 *	None.
2617 *
2618 * Side effects:
2619 *	The view in the window may change.
2620 *
2621 *----------------------------------------------------------------------
2622 */
2623
2624static void
2625EntryScanTo(
2626    Entry *entryPtr,		/* Information about widget. */
2627    int x)			/* X-coordinate to use for scan operation. */
2628{
2629    int newLeftIndex;
2630
2631    /*
2632     * Compute new leftIndex for entry by amplifying the difference between
2633     * the current position and the place where the scan started (the "mark"
2634     * position). If we run off the left or right side of the entry, then
2635     * reset the mark point so that the current position continues to
2636     * correspond to the edge of the window. This means that the picture will
2637     * start dragging as soon as the mouse reverses direction (without this
2638     * reset, might have to slide mouse a long ways back before the picture
2639     * starts moving again).
2640     */
2641
2642    newLeftIndex = entryPtr->scanMarkIndex
2643	    - (10 * (x - entryPtr->scanMarkX)) / entryPtr->avgWidth;
2644    if (newLeftIndex >= entryPtr->numChars) {
2645	newLeftIndex = entryPtr->scanMarkIndex = entryPtr->numChars - 1;
2646	entryPtr->scanMarkX = x;
2647    }
2648    if (newLeftIndex < 0) {
2649	newLeftIndex = entryPtr->scanMarkIndex = 0;
2650	entryPtr->scanMarkX = x;
2651    }
2652
2653    if (newLeftIndex != entryPtr->leftIndex) {
2654	entryPtr->leftIndex = newLeftIndex;
2655	entryPtr->flags |= UPDATE_SCROLLBAR;
2656	EntryComputeGeometry(entryPtr);
2657	if (newLeftIndex != entryPtr->leftIndex) {
2658	    entryPtr->scanMarkIndex = entryPtr->leftIndex;
2659	    entryPtr->scanMarkX = x;
2660	}
2661	EventuallyRedraw(entryPtr);
2662    }
2663}
2664
2665/*
2666 *----------------------------------------------------------------------
2667 *
2668 * EntrySelectTo --
2669 *
2670 *	Modify the selection by moving its un-anchored end. This could make
2671 *	the selection either larger or smaller.
2672 *
2673 * Results:
2674 *	None.
2675 *
2676 * Side effects:
2677 *	The selection changes.
2678 *
2679 *----------------------------------------------------------------------
2680 */
2681
2682static void
2683EntrySelectTo(
2684    Entry *entryPtr,		/* Information about widget. */
2685    int index)			/* Character index of element that is to
2686				 * become the "other" end of the selection. */
2687{
2688    int newFirst, newLast;
2689
2690    /*
2691     * Grab the selection if we don't own it already.
2692     */
2693
2694    if (!(entryPtr->flags & GOT_SELECTION) && (entryPtr->exportSelection)) {
2695	Tk_OwnSelection(entryPtr->tkwin, XA_PRIMARY, EntryLostSelection,
2696		(ClientData) entryPtr);
2697	entryPtr->flags |= GOT_SELECTION;
2698    }
2699
2700    /*
2701     * Pick new starting and ending points for the selection.
2702     */
2703
2704    if (entryPtr->selectAnchor > entryPtr->numChars) {
2705	entryPtr->selectAnchor = entryPtr->numChars;
2706    }
2707    if (entryPtr->selectAnchor <= index) {
2708	newFirst = entryPtr->selectAnchor;
2709	newLast = index;
2710    } else {
2711	newFirst = index;
2712	newLast = entryPtr->selectAnchor;
2713	if (newLast < 0) {
2714	    newFirst = newLast = -1;
2715	}
2716    }
2717    if ((entryPtr->selectFirst == newFirst)
2718	    && (entryPtr->selectLast == newLast)) {
2719	return;
2720    }
2721    entryPtr->selectFirst = newFirst;
2722    entryPtr->selectLast = newLast;
2723    EventuallyRedraw(entryPtr);
2724}
2725
2726/*
2727 *----------------------------------------------------------------------
2728 *
2729 * EntryFetchSelection --
2730 *
2731 *	This function is called back by Tk when the selection is requested by
2732 *	someone. It returns part or all of the selection in a buffer provided
2733 *	by the caller.
2734 *
2735 * Results:
2736 *	The return value is the number of non-NULL bytes stored at buffer.
2737 *	Buffer is filled (or partially filled) with a NULL-terminated string
2738 *	containing part or all of the selection, as given by offset and
2739 *	maxBytes.
2740 *
2741 * Side effects:
2742 *	None.
2743 *
2744 *----------------------------------------------------------------------
2745 */
2746
2747static int
2748EntryFetchSelection(
2749    ClientData clientData,	/* Information about entry widget. */
2750    int offset,			/* Byte offset within selection of first
2751				 * character to be returned. */
2752    char *buffer,		/* Location in which to place selection. */
2753    int maxBytes)		/* Maximum number of bytes to place at buffer,
2754				 * not including terminating NUL character. */
2755{
2756    Entry *entryPtr = (Entry *) clientData;
2757    int byteCount;
2758    CONST char *string;
2759    CONST char *selStart, *selEnd;
2760
2761    if ((entryPtr->selectFirst < 0) || !(entryPtr->exportSelection)) {
2762	return -1;
2763    }
2764    string = entryPtr->displayString;
2765    selStart = Tcl_UtfAtIndex(string, entryPtr->selectFirst);
2766    selEnd = Tcl_UtfAtIndex(selStart,
2767	    entryPtr->selectLast - entryPtr->selectFirst);
2768    byteCount = selEnd - selStart - offset;
2769    if (byteCount > maxBytes) {
2770	byteCount = maxBytes;
2771    }
2772    if (byteCount <= 0) {
2773	return 0;
2774    }
2775    memcpy(buffer, selStart + offset, (size_t) byteCount);
2776    buffer[byteCount] = '\0';
2777    return byteCount;
2778}
2779
2780/*
2781 *----------------------------------------------------------------------
2782 *
2783 * EntryLostSelection --
2784 *
2785 *	This function is called back by Tk when the selection is grabbed away
2786 *	from an entry widget.
2787 *
2788 * Results:
2789 *	None.
2790 *
2791 * Side effects:
2792 *	The existing selection is unhighlighted, and the window is marked as
2793 *	not containing a selection.
2794 *
2795 *----------------------------------------------------------------------
2796 */
2797
2798static void
2799EntryLostSelection(
2800    ClientData clientData)	/* Information about entry widget. */
2801{
2802    Entry *entryPtr = (Entry *) clientData;
2803
2804    entryPtr->flags &= ~GOT_SELECTION;
2805
2806    /*
2807     * On Windows and Mac systems, we want to remember the selection for the
2808     * next time the focus enters the window. On Unix, we need to clear the
2809     * selection since it is always visible.
2810     * This is controlled by ::tk::AlwaysShowSelection.
2811     */
2812
2813    if (TkpAlwaysShowSelection(entryPtr->tkwin)
2814	    && (entryPtr->selectFirst >= 0) && entryPtr->exportSelection) {
2815	entryPtr->selectFirst = -1;
2816	entryPtr->selectLast = -1;
2817	EventuallyRedraw(entryPtr);
2818    }
2819}
2820
2821/*
2822 *----------------------------------------------------------------------
2823 *
2824 * EventuallyRedraw --
2825 *
2826 *	Ensure that an entry is eventually redrawn on the display.
2827 *
2828 * Results:
2829 *	None.
2830 *
2831 * Side effects:
2832 *	Information gets redisplayed. Right now we don't do selective
2833 *	redisplays: the whole window will be redrawn. This doesn't seem to
2834 *	hurt performance noticeably, but if it does then this could be
2835 *	changed.
2836 *
2837 *----------------------------------------------------------------------
2838 */
2839
2840static void
2841EventuallyRedraw(
2842    Entry *entryPtr)		/* Information about widget. */
2843{
2844    if ((entryPtr->flags & ENTRY_DELETED) || !Tk_IsMapped(entryPtr->tkwin)) {
2845	return;
2846    }
2847
2848    /*
2849     * Right now we don't do selective redisplays: the whole window will be
2850     * redrawn. This doesn't seem to hurt performance noticeably, but if it
2851     * does then this could be changed.
2852     */
2853
2854    if (!(entryPtr->flags & REDRAW_PENDING)) {
2855	entryPtr->flags |= REDRAW_PENDING;
2856	Tcl_DoWhenIdle(DisplayEntry, (ClientData) entryPtr);
2857    }
2858}
2859
2860/*
2861 *----------------------------------------------------------------------
2862 *
2863 * EntryVisibleRange --
2864 *
2865 *	Return information about the range of the entry that is currently
2866 *	visible.
2867 *
2868 * Results:
2869 *	*firstPtr and *lastPtr are modified to hold fractions between 0 and 1
2870 *	identifying the range of characters visible in the entry.
2871 *
2872 * Side effects:
2873 *	None.
2874 *
2875 *----------------------------------------------------------------------
2876 */
2877
2878static void
2879EntryVisibleRange(
2880    Entry *entryPtr,		/* Information about widget. */
2881    double *firstPtr,		/* Return position of first visible character
2882				 * in widget. */
2883    double *lastPtr)		/* Return position of char just after last
2884				 * visible one. */
2885{
2886    int charsInWindow;
2887
2888    if (entryPtr->numChars == 0) {
2889	*firstPtr = 0.0;
2890	*lastPtr = 1.0;
2891    } else {
2892	charsInWindow = Tk_PointToChar(entryPtr->textLayout,
2893		Tk_Width(entryPtr->tkwin) - entryPtr->inset
2894		- entryPtr->xWidth - entryPtr->layoutX - 1, 0);
2895	if (charsInWindow < entryPtr->numChars) {
2896	    charsInWindow++;
2897	}
2898	charsInWindow -= entryPtr->leftIndex;
2899	if (charsInWindow == 0) {
2900	    charsInWindow = 1;
2901	}
2902
2903	*firstPtr = (double) entryPtr->leftIndex / entryPtr->numChars;
2904	*lastPtr = (double) (entryPtr->leftIndex + charsInWindow)
2905		/ entryPtr->numChars;
2906    }
2907}
2908
2909/*
2910 *----------------------------------------------------------------------
2911 *
2912 * EntryUpdateScrollbar --
2913 *
2914 *	This function is invoked whenever information has changed in an entry
2915 *	in a way that would invalidate a scrollbar display. If there is an
2916 *	associated scrollbar, then this function updates it by invoking a Tcl
2917 *	command.
2918 *
2919 * Results:
2920 *	None.
2921 *
2922 * Side effects:
2923 *	A Tcl command is invoked, and an additional command may be
2924 *	invoked to process errors in the command.
2925 *
2926 *----------------------------------------------------------------------
2927 */
2928
2929static void
2930EntryUpdateScrollbar(
2931    Entry *entryPtr)			/* Information about widget. */
2932{
2933    char firstStr[TCL_DOUBLE_SPACE], lastStr[TCL_DOUBLE_SPACE];
2934    int code;
2935    double first, last;
2936    Tcl_Interp *interp;
2937
2938    if (entryPtr->scrollCmd == NULL) {
2939	return;
2940    }
2941
2942    interp = entryPtr->interp;
2943    Tcl_Preserve((ClientData) interp);
2944    EntryVisibleRange(entryPtr, &first, &last);
2945    Tcl_PrintDouble(NULL, first, firstStr);
2946    Tcl_PrintDouble(NULL, last, lastStr);
2947    code = Tcl_VarEval(interp, entryPtr->scrollCmd, " ", firstStr, " ",
2948	    lastStr, NULL);
2949    if (code != TCL_OK) {
2950	Tcl_AddErrorInfo(interp,
2951		"\n    (horizontal scrolling command executed by ");
2952	Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->tkwin));
2953	Tcl_AddErrorInfo(interp, ")");
2954	Tcl_BackgroundError(interp);
2955    }
2956    Tcl_SetResult(interp, NULL, TCL_STATIC);
2957    Tcl_Release((ClientData) interp);
2958}
2959
2960/*
2961 *----------------------------------------------------------------------
2962 *
2963 * EntryBlinkProc --
2964 *
2965 *	This function is called as a timer handler to blink the insertion
2966 *	cursor off and on.
2967 *
2968 * Results:
2969 *	None.
2970 *
2971 * Side effects:
2972 *	The cursor gets turned on or off, redisplay gets invoked, and this
2973 *	function reschedules itself.
2974 *
2975 *----------------------------------------------------------------------
2976 */
2977
2978static void
2979EntryBlinkProc(
2980    ClientData clientData)	/* Pointer to record describing entry. */
2981{
2982    Entry *entryPtr = (Entry *) clientData;
2983
2984    if ((entryPtr->state == STATE_DISABLED) ||
2985	    (entryPtr->state == STATE_READONLY) ||
2986	    !(entryPtr->flags & GOT_FOCUS) || (entryPtr->insertOffTime == 0)) {
2987	return;
2988    }
2989    if (entryPtr->flags & CURSOR_ON) {
2990	entryPtr->flags &= ~CURSOR_ON;
2991	entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
2992	    entryPtr->insertOffTime, EntryBlinkProc, (ClientData) entryPtr);
2993    } else {
2994	entryPtr->flags |= CURSOR_ON;
2995	entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
2996	    entryPtr->insertOnTime, EntryBlinkProc, (ClientData) entryPtr);
2997    }
2998    EventuallyRedraw(entryPtr);
2999}
3000
3001/*
3002 *----------------------------------------------------------------------
3003 *
3004 * EntryFocusProc --
3005 *
3006 *	This function is called whenever the entry gets or loses the input
3007 *	focus. It's also called whenever the window is reconfigured while it
3008 *	has the focus.
3009 *
3010 * Results:
3011 *	None.
3012 *
3013 * Side effects:
3014 *	The cursor gets turned on or off.
3015 *
3016 *----------------------------------------------------------------------
3017 */
3018
3019static void
3020EntryFocusProc(
3021    Entry *entryPtr,		/* Entry that got or lost focus. */
3022    int gotFocus)		/* 1 means window is getting focus, 0 means
3023				 * it's losing it. */
3024{
3025    Tcl_DeleteTimerHandler(entryPtr->insertBlinkHandler);
3026    if (gotFocus) {
3027	entryPtr->flags |= GOT_FOCUS | CURSOR_ON;
3028	if (entryPtr->insertOffTime != 0) {
3029	    entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
3030		    entryPtr->insertOnTime, EntryBlinkProc,
3031		    (ClientData) entryPtr);
3032	}
3033	if (entryPtr->validate == VALIDATE_ALL ||
3034	    entryPtr->validate == VALIDATE_FOCUS ||
3035	    entryPtr->validate == VALIDATE_FOCUSIN) {
3036	    EntryValidateChange(entryPtr, NULL,
3037				entryPtr->string, -1, VALIDATE_FOCUSIN);
3038	}
3039    } else {
3040	entryPtr->flags &= ~(GOT_FOCUS | CURSOR_ON);
3041	entryPtr->insertBlinkHandler = (Tcl_TimerToken) NULL;
3042	if (entryPtr->validate == VALIDATE_ALL ||
3043	    entryPtr->validate == VALIDATE_FOCUS ||
3044	    entryPtr->validate == VALIDATE_FOCUSOUT) {
3045	    EntryValidateChange(entryPtr, NULL,
3046				entryPtr->string, -1, VALIDATE_FOCUSOUT);
3047	}
3048    }
3049    EventuallyRedraw(entryPtr);
3050}
3051
3052/*
3053 *--------------------------------------------------------------
3054 *
3055 * EntryTextVarProc --
3056 *
3057 *	This function is invoked when someone changes the variable whose
3058 *	contents are to be displayed in an entry.
3059 *
3060 * Results:
3061 *	NULL is always returned.
3062 *
3063 * Side effects:
3064 *	The text displayed in the entry will change to match the variable.
3065 *
3066 *--------------------------------------------------------------
3067 */
3068
3069	/* ARGSUSED */
3070static char *
3071EntryTextVarProc(
3072    ClientData clientData,	/* Information about button. */
3073    Tcl_Interp *interp,		/* Interpreter containing variable. */
3074    CONST char *name1,		/* Not used. */
3075    CONST char *name2,		/* Not used. */
3076    int flags)			/* Information about what happened. */
3077{
3078    Entry *entryPtr = (Entry *) clientData;
3079    CONST char *value;
3080
3081    if (entryPtr->flags & ENTRY_DELETED) {
3082	/*
3083	 * Just abort early if we entered here while being deleted.
3084	 */
3085	return NULL;
3086    }
3087
3088    /*
3089     * If the variable is unset, then immediately recreate it unless the whole
3090     * interpreter is going away.
3091     */
3092
3093    if (flags & TCL_TRACE_UNSETS) {
3094	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
3095	    Tcl_SetVar(interp, entryPtr->textVarName, entryPtr->string,
3096		    TCL_GLOBAL_ONLY);
3097	    Tcl_TraceVar(interp, entryPtr->textVarName,
3098		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
3099		    EntryTextVarProc, clientData);
3100	    entryPtr->flags |= ENTRY_VAR_TRACED;
3101	}
3102	return NULL;
3103    }
3104
3105    /*
3106     * Update the entry's text with the value of the variable, unless the
3107     * entry already has that value (this happens when the variable changes
3108     * value because we changed it because someone typed in the entry).
3109     */
3110
3111    value = Tcl_GetVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY);
3112    if (value == NULL) {
3113	value = "";
3114    }
3115    EntrySetValue(entryPtr, value);
3116    return NULL;
3117}
3118
3119/*
3120 *--------------------------------------------------------------
3121 *
3122 * EntryValidate --
3123 *
3124 *	This function is invoked when any character is added or removed from
3125 *	the entry widget, or a focus has trigerred validation.
3126 *
3127 * Results:
3128
3129 *	TCL_OK if the validatecommand passes the new string. TCL_BREAK if the
3130 *	vcmd executed OK, but rejects the string. TCL_ERROR if an error
3131 *	occurred while executing the vcmd or a valid Tcl_Bool is not returned.
3132 *
3133 * Side effects:
3134 *	An error condition may arise
3135 *
3136 *--------------------------------------------------------------
3137 */
3138
3139static int
3140EntryValidate(
3141     register Entry *entryPtr,	/* Entry that needs validation. */
3142     register char *cmd)	/* Validation command (NULL-terminated
3143				 * string). */
3144{
3145    register Tcl_Interp *interp = entryPtr->interp;
3146    int code, bool;
3147
3148    code = Tcl_EvalEx(interp, cmd, -1, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
3149
3150    /*
3151     * We accept TCL_OK and TCL_RETURN as valid return codes from the command
3152     * callback.
3153     */
3154
3155    if (code != TCL_OK && code != TCL_RETURN) {
3156	Tcl_AddErrorInfo(interp, "\n\t(in validation command executed by ");
3157	Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->tkwin));
3158	Tcl_AddErrorInfo(interp, ")");
3159	Tcl_BackgroundError(interp);
3160	return TCL_ERROR;
3161    }
3162
3163    /*
3164     * The command callback should return an acceptable Tcl boolean.
3165     */
3166
3167    if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp),
3168	    &bool) != TCL_OK) {
3169	Tcl_AddErrorInfo(interp,
3170		 "\nvalid boolean not returned by validation command");
3171	Tcl_BackgroundError(interp);
3172	Tcl_SetResult(interp, NULL, 0);
3173	return TCL_ERROR;
3174    }
3175
3176    Tcl_SetResult(interp, NULL, 0);
3177    return (bool ? TCL_OK : TCL_BREAK);
3178}
3179
3180/*
3181 *--------------------------------------------------------------
3182 *
3183 * EntryValidateChange --
3184 *
3185 *	This function is invoked when any character is added or removed from
3186 *	the entry widget, or a focus has trigerred validation.
3187 *
3188 * Results:
3189 *	TCL_OK if the validatecommand accepts the new string, TCL_ERROR if any
3190 *	problems occured with validatecommand.
3191 *
3192 * Side effects:
3193 *	The insertion/deletion may be aborted, and the validatecommand might
3194 *	turn itself off (if an error or loop condition arises).
3195 *
3196 *--------------------------------------------------------------
3197 */
3198
3199static int
3200EntryValidateChange(
3201     register Entry *entryPtr,	/* Entry that needs validation. */
3202     char *change,		/* Characters to be added/deleted
3203				 * (NUL-terminated string). */
3204     CONST char *newValue,	/* Potential new value of entry string */
3205     int index,			/* index of insert/delete, -1 otherwise */
3206     int type)			/* forced, delete, insert, focusin or
3207				 * focusout */
3208{
3209    int code, varValidate = (entryPtr->flags & VALIDATE_VAR);
3210    char *p;
3211    Tcl_DString script;
3212
3213    if (entryPtr->validateCmd == NULL ||
3214	entryPtr->validate == VALIDATE_NONE) {
3215	return (varValidate ? TCL_ERROR : TCL_OK);
3216    }
3217
3218    /*
3219     * If we're already validating, then we're hitting a loop condition Return
3220     * and set validate to 0 to disallow further validations and prevent
3221     * current validation from finishing
3222     */
3223
3224    if (entryPtr->flags & VALIDATING) {
3225	entryPtr->validate = VALIDATE_NONE;
3226	return (varValidate ? TCL_ERROR : TCL_OK);
3227    }
3228
3229    entryPtr->flags |= VALIDATING;
3230
3231    /*
3232     * Now form command string and run through the -validatecommand
3233     */
3234
3235    Tcl_DStringInit(&script);
3236    ExpandPercents(entryPtr, entryPtr->validateCmd,
3237	    change, newValue, index, type, &script);
3238    Tcl_DStringAppend(&script, "", 1);
3239
3240    p = Tcl_DStringValue(&script);
3241    code = EntryValidate(entryPtr, p);
3242    Tcl_DStringFree(&script);
3243
3244    /*
3245     * If e->validate has become VALIDATE_NONE during the validation, or we
3246     * now have VALIDATE_VAR set (from EntrySetValue) and didn't before, it
3247     * means that a loop condition almost occured. Do not allow this
3248     * validation result to finish.
3249     */
3250
3251    if (entryPtr->validate == VALIDATE_NONE
3252	    || (!varValidate && (entryPtr->flags & VALIDATE_VAR))) {
3253	code = TCL_ERROR;
3254    }
3255
3256    /*
3257     * It's possible that the user deleted the entry during validation. In
3258     * that case, abort future validation and return an error.
3259     */
3260
3261    if (entryPtr->flags & ENTRY_DELETED) {
3262	return TCL_ERROR;
3263    }
3264
3265    /*
3266     * If validate will return ERROR, then disallow further validations
3267     * Otherwise, if it didn't accept the new string (returned TCL_BREAK) then
3268     * eval the invalidCmd (if it's set)
3269     */
3270
3271    if (code == TCL_ERROR) {
3272	entryPtr->validate = VALIDATE_NONE;
3273    } else if (code == TCL_BREAK) {
3274	/*
3275	 * If we were doing forced validation (like via a variable trace) and
3276	 * the command returned 0, the we turn off validation because we
3277	 * assume that textvariables have precedence in managing the value.
3278	 * We also don't call the invcmd, as it may want to do entry
3279	 * manipulation which the setting of the var will later wipe anyway.
3280	 */
3281
3282	if (varValidate) {
3283	    entryPtr->validate = VALIDATE_NONE;
3284	} else if (entryPtr->invalidCmd != NULL) {
3285	    Tcl_DStringInit(&script);
3286	    ExpandPercents(entryPtr, entryPtr->invalidCmd,
3287		    change, newValue, index, type, &script);
3288	    Tcl_DStringAppend(&script, "", 1);
3289	    p = Tcl_DStringValue(&script);
3290	    if (Tcl_EvalEx(entryPtr->interp, p, -1,
3291		    TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT) != TCL_OK) {
3292		Tcl_AddErrorInfo(entryPtr->interp,
3293			"\n\t(in invalidcommand executed by entry)");
3294		Tcl_BackgroundError(entryPtr->interp);
3295		code = TCL_ERROR;
3296		entryPtr->validate = VALIDATE_NONE;
3297	    }
3298	    Tcl_DStringFree(&script);
3299
3300	    /*
3301	     * It's possible that the user deleted the entry during
3302	     * validation. In that case, abort future validation and return an
3303	     * error.
3304	     */
3305
3306	    if (entryPtr->flags & ENTRY_DELETED) {
3307		return TCL_ERROR;
3308	    }
3309	}
3310    }
3311
3312    entryPtr->flags &= ~VALIDATING;
3313
3314    return code;
3315}
3316
3317/*
3318 *--------------------------------------------------------------
3319 *
3320 * ExpandPercents --
3321 *
3322 *	Given a command and an event, produce a new command by replacing %
3323 *	constructs in the original command with information from the X event.
3324 *
3325 * Results:
3326 *	The new expanded command is appended to the dynamic string given by
3327 *	dsPtr.
3328 *
3329 * Side effects:
3330 *	None.
3331 *
3332 *--------------------------------------------------------------
3333 */
3334
3335static void
3336ExpandPercents(
3337     register Entry *entryPtr,	/* Entry that needs validation. */
3338     register CONST char *before,
3339				/* Command containing percent expressions to
3340				 * be replaced. */
3341     CONST char *change,	/* Characters to added/deleted (NUL-terminated
3342				 * string). */
3343     CONST char *newValue,	/* Potential new value of entry string */
3344     int index,			/* index of insert/delete */
3345     int type,			/* INSERT or DELETE */
3346     Tcl_DString *dsPtr)	/* Dynamic string in which to append new
3347				 * command. */
3348{
3349    int spaceNeeded, cvtFlags;	/* Used to substitute string as proper Tcl
3350				 * list element. */
3351    int number, length;
3352    register CONST char *string;
3353    Tcl_UniChar ch;
3354    char numStorage[2*TCL_INTEGER_SPACE];
3355
3356    while (1) {
3357	if (*before == '\0') {
3358	    break;
3359	}
3360	/*
3361	 * Find everything up to the next % character and append it to the
3362	 * result string.
3363	 */
3364
3365	string = before;
3366
3367	/*
3368	 * No need to convert '%', as it is in ascii range.
3369	 */
3370
3371	string = Tcl_UtfFindFirst(before, '%');
3372	if (string == NULL) {
3373	    Tcl_DStringAppend(dsPtr, before, -1);
3374	    break;
3375	} else if (string != before) {
3376	    Tcl_DStringAppend(dsPtr, before, string-before);
3377	    before = string;
3378	}
3379
3380	/*
3381	 * There's a percent sequence here. Process it.
3382	 */
3383
3384	before++; /* skip over % */
3385	if (*before != '\0') {
3386	    before += Tcl_UtfToUniChar(before, &ch);
3387	} else {
3388	    ch = '%';
3389	}
3390	if (type == VALIDATE_BUTTON) {
3391	    /*
3392	     * -command %-substitution
3393	     */
3394
3395	    switch (ch) {
3396	    case 's':		/* Current string value of spinbox */
3397		string = entryPtr->string;
3398		break;
3399	    case 'd':		/* direction, up or down */
3400		string = change;
3401		break;
3402	    case 'W':		/* widget name */
3403		string = Tk_PathName(entryPtr->tkwin);
3404		break;
3405	    default:
3406		length = Tcl_UniCharToUtf(ch, numStorage);
3407		numStorage[length] = '\0';
3408		string = numStorage;
3409		break;
3410	    }
3411	} else {
3412	    /*
3413	     * -validatecommand / -invalidcommand %-substitution
3414	     */
3415
3416	    switch (ch) {
3417	    case 'd':		/* Type of call that caused validation */
3418		switch (type) {
3419		case VALIDATE_INSERT:
3420		    number = 1;
3421		    break;
3422		case VALIDATE_DELETE:
3423		    number = 0;
3424		    break;
3425		default:
3426		    number = -1;
3427		    break;
3428		}
3429		sprintf(numStorage, "%d", number);
3430		string = numStorage;
3431		break;
3432	    case 'i':		/* index of insert/delete */
3433		sprintf(numStorage, "%d", index);
3434		string = numStorage;
3435		break;
3436	    case 'P':		/* 'Peeked' new value of the string */
3437		string = newValue;
3438		break;
3439	    case 's':		/* Current string value of spinbox */
3440		string = entryPtr->string;
3441		break;
3442	    case 'S':		/* string to be inserted/deleted, if any */
3443		string = change;
3444		break;
3445	    case 'v':		/* type of validation currently set */
3446		string = validateStrings[entryPtr->validate];
3447		break;
3448	    case 'V':		/* type of validation in effect */
3449		switch (type) {
3450		case VALIDATE_INSERT:
3451		case VALIDATE_DELETE:
3452		    string = validateStrings[VALIDATE_KEY];
3453		    break;
3454		case VALIDATE_FORCED:
3455		    string = "forced";
3456		    break;
3457		default:
3458		    string = validateStrings[type];
3459		    break;
3460		}
3461		break;
3462	    case 'W': /* widget name */
3463		string = Tk_PathName(entryPtr->tkwin);
3464		break;
3465	    default:
3466		length = Tcl_UniCharToUtf(ch, numStorage);
3467		numStorage[length] = '\0';
3468		string = numStorage;
3469		break;
3470	    }
3471	}
3472
3473	spaceNeeded = Tcl_ScanCountedElement(string, -1, &cvtFlags);
3474	length = Tcl_DStringLength(dsPtr);
3475	Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
3476	spaceNeeded = Tcl_ConvertCountedElement(string, -1,
3477		Tcl_DStringValue(dsPtr) + length,
3478		cvtFlags | TCL_DONT_USE_BRACES);
3479	Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
3480    }
3481}
3482
3483/*
3484 *--------------------------------------------------------------
3485 *
3486 * Tk_SpinboxObjCmd --
3487 *
3488 *	This function is invoked to process the "spinbox" Tcl command. See the
3489 *	user documentation for details on what it does.
3490 *
3491 * Results:
3492 *	A standard Tcl result.
3493 *
3494 * Side effects:
3495 *	See the user documentation.
3496 *
3497 *--------------------------------------------------------------
3498 */
3499
3500int
3501Tk_SpinboxObjCmd(
3502    ClientData clientData,	/* NULL. */
3503    Tcl_Interp *interp,		/* Current interpreter. */
3504    int objc,			/* Number of arguments. */
3505    Tcl_Obj *CONST objv[])	/* Argument objects. */
3506{
3507    register Entry *entryPtr;
3508    register Spinbox *sbPtr;
3509    Tk_OptionTable optionTable;
3510    Tk_Window tkwin;
3511    char *tmp;
3512
3513    if (objc < 2) {
3514	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
3515	return TCL_ERROR;
3516    }
3517
3518    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
3519	    Tcl_GetString(objv[1]), NULL);
3520    if (tkwin == NULL) {
3521	return TCL_ERROR;
3522    }
3523
3524    /*
3525     * Create the option table for this widget class. If it has already been
3526     * created, Tk will return the cached value.
3527     */
3528
3529    optionTable = Tk_CreateOptionTable(interp, sbOptSpec);
3530
3531    /*
3532     * Initialize the fields of the structure that won't be initialized by
3533     * ConfigureEntry, or that ConfigureEntry requires to be initialized
3534     * already (e.g. resource pointers). Only the non-NULL/0 data must be
3535     * initialized as memset covers the rest.
3536     */
3537
3538    sbPtr			= (Spinbox *) ckalloc(sizeof(Spinbox));
3539    entryPtr			= (Entry *) sbPtr;
3540    memset(sbPtr, 0, sizeof(Spinbox));
3541
3542    entryPtr->tkwin		= tkwin;
3543    entryPtr->display		= Tk_Display(tkwin);
3544    entryPtr->interp		= interp;
3545    entryPtr->widgetCmd		= Tcl_CreateObjCommand(interp,
3546	    Tk_PathName(entryPtr->tkwin), SpinboxWidgetObjCmd,
3547	    (ClientData) sbPtr, EntryCmdDeletedProc);
3548    entryPtr->optionTable	= optionTable;
3549    entryPtr->type		= TK_SPINBOX;
3550    tmp				= (char *) ckalloc(1);
3551    tmp[0]			= '\0';
3552    entryPtr->string		= tmp;
3553    entryPtr->selectFirst	= -1;
3554    entryPtr->selectLast	= -1;
3555
3556    entryPtr->cursor		= None;
3557    entryPtr->exportSelection	= 1;
3558    entryPtr->justify		= TK_JUSTIFY_LEFT;
3559    entryPtr->relief		= TK_RELIEF_FLAT;
3560    entryPtr->state		= STATE_NORMAL;
3561    entryPtr->displayString	= entryPtr->string;
3562    entryPtr->inset		= XPAD;
3563    entryPtr->textGC		= None;
3564    entryPtr->selTextGC		= None;
3565    entryPtr->highlightGC	= None;
3566    entryPtr->avgWidth		= 1;
3567    entryPtr->validate		= VALIDATE_NONE;
3568
3569    sbPtr->selElement		= SEL_NONE;
3570    sbPtr->curElement		= SEL_NONE;
3571    sbPtr->bCursor		= None;
3572    sbPtr->repeatDelay		= 400;
3573    sbPtr->repeatInterval	= 100;
3574    sbPtr->fromValue		= 0.0;
3575    sbPtr->toValue		= 100.0;
3576    sbPtr->increment		= 1.0;
3577    sbPtr->formatBuf		= (char *) ckalloc(TCL_DOUBLE_SPACE);
3578    sbPtr->bdRelief		= TK_RELIEF_FLAT;
3579    sbPtr->buRelief		= TK_RELIEF_FLAT;
3580
3581    /*
3582     * Keep a hold of the associated tkwin until we destroy the spinbox,
3583     * otherwise Tk might free it while we still need it.
3584     */
3585
3586    Tcl_Preserve((ClientData) entryPtr->tkwin);
3587
3588    Tk_SetClass(entryPtr->tkwin, "Spinbox");
3589    Tk_SetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr);
3590    Tk_CreateEventHandler(entryPtr->tkwin,
3591	    PointerMotionMask|ExposureMask|StructureNotifyMask|FocusChangeMask,
3592	    EntryEventProc, (ClientData) entryPtr);
3593    Tk_CreateSelHandler(entryPtr->tkwin, XA_PRIMARY, XA_STRING,
3594	    EntryFetchSelection, (ClientData) entryPtr, XA_STRING);
3595
3596    if (Tk_InitOptions(interp, (char *) sbPtr, optionTable, tkwin)
3597	    != TCL_OK) {
3598	Tk_DestroyWindow(entryPtr->tkwin);
3599	return TCL_ERROR;
3600    }
3601    if (ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0) != TCL_OK) {
3602	goto error;
3603    }
3604
3605    Tcl_SetResult(interp, Tk_PathName(entryPtr->tkwin), TCL_STATIC);
3606    return TCL_OK;
3607
3608    error:
3609    Tk_DestroyWindow(entryPtr->tkwin);
3610    return TCL_ERROR;
3611}
3612
3613/*
3614 *--------------------------------------------------------------
3615 *
3616 * SpinboxWidgetObjCmd --
3617 *
3618 *	This function is invoked to process the Tcl command that corresponds
3619 *	to a widget managed by this module. See the user documentation for
3620 *	details on what it does.
3621 *
3622 * Results:
3623 *	A standard Tcl result.
3624 *
3625 * Side effects:
3626 *	See the user documentation.
3627 *
3628 *--------------------------------------------------------------
3629 */
3630
3631static int
3632SpinboxWidgetObjCmd(
3633    ClientData clientData,	/* Information about spinbox widget. */
3634    Tcl_Interp *interp,		/* Current interpreter. */
3635    int objc,			/* Number of arguments. */
3636    Tcl_Obj *CONST objv[])	/* Argument objects. */
3637{
3638    Entry *entryPtr = (Entry *) clientData;
3639    Spinbox *sbPtr = (Spinbox *) clientData;
3640    int cmdIndex, selIndex, result;
3641    Tcl_Obj *objPtr;
3642
3643    if (objc < 2) {
3644	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
3645	return TCL_ERROR;
3646    }
3647
3648    /*
3649     * Parse the widget command by looking up the second token in the list of
3650     * valid command names.
3651     */
3652
3653    result = Tcl_GetIndexFromObj(interp, objv[1], sbCmdNames,
3654	    "option", 0, &cmdIndex);
3655    if (result != TCL_OK) {
3656	return result;
3657    }
3658
3659    Tcl_Preserve((ClientData) entryPtr);
3660    switch ((enum sbCmd) cmdIndex) {
3661    case SB_CMD_BBOX: {
3662	int index, x, y, width, height;
3663	char buf[TCL_INTEGER_SPACE * 4];
3664
3665	if (objc != 3) {
3666	    Tcl_WrongNumArgs(interp, 2, objv, "index");
3667	    goto error;
3668	}
3669	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
3670		&index) != TCL_OK) {
3671	    goto error;
3672	}
3673	if ((index == entryPtr->numChars) && (index > 0)) {
3674	    index--;
3675	}
3676	Tk_CharBbox(entryPtr->textLayout, index, &x, &y,
3677		&width, &height);
3678	sprintf(buf, "%d %d %d %d", x + entryPtr->layoutX,
3679		y + entryPtr->layoutY, width, height);
3680	Tcl_SetResult(interp, buf, TCL_VOLATILE);
3681	break;
3682    }
3683
3684    case SB_CMD_CGET:
3685	if (objc != 3) {
3686	    Tcl_WrongNumArgs(interp, 2, objv, "option");
3687	    goto error;
3688	}
3689
3690	objPtr = Tk_GetOptionValue(interp, (char *) entryPtr,
3691		entryPtr->optionTable, objv[2], entryPtr->tkwin);
3692	if (objPtr == NULL) {
3693	    goto error;
3694	} else {
3695	    Tcl_SetObjResult(interp, objPtr);
3696	}
3697	break;
3698
3699    case SB_CMD_CONFIGURE:
3700	if (objc <= 3) {
3701	    objPtr = Tk_GetOptionInfo(interp, (char *) entryPtr,
3702		    entryPtr->optionTable,
3703		    (objc == 3) ? objv[2] : NULL,
3704		    entryPtr->tkwin);
3705	    if (objPtr == NULL) {
3706		goto error;
3707	    } else {
3708		Tcl_SetObjResult(interp, objPtr);
3709	    }
3710	} else {
3711	    result = ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0);
3712	}
3713	break;
3714
3715    case SB_CMD_DELETE: {
3716	int first, last;
3717
3718	if ((objc < 3) || (objc > 4)) {
3719	    Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
3720	    goto error;
3721	}
3722	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
3723		&first) != TCL_OK) {
3724	    goto error;
3725	}
3726	if (objc == 3) {
3727	    last = first + 1;
3728	} else {
3729	    if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[3]),
3730		    &last) != TCL_OK) {
3731		goto error;
3732	    }
3733	}
3734	if ((last >= first) && (entryPtr->state == STATE_NORMAL)) {
3735	    DeleteChars(entryPtr, first, last - first);
3736	}
3737	break;
3738    }
3739
3740    case SB_CMD_GET:
3741	if (objc != 2) {
3742	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
3743	    goto error;
3744	}
3745	Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1);
3746	break;
3747
3748    case SB_CMD_ICURSOR:
3749	if (objc != 3) {
3750	    Tcl_WrongNumArgs(interp, 2, objv, "pos");
3751	    goto error;
3752	}
3753	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
3754		&entryPtr->insertPos) != TCL_OK) {
3755	    goto error;
3756	}
3757	EventuallyRedraw(entryPtr);
3758	break;
3759
3760    case SB_CMD_IDENTIFY: {
3761	int x, y, elem;
3762
3763	if (objc != 4) {
3764	    Tcl_WrongNumArgs(interp, 2, objv, "x y");
3765	    goto error;
3766	}
3767	if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) ||
3768		(Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
3769	    goto error;
3770	}
3771	elem = GetSpinboxElement(sbPtr, x, y);
3772	if (elem != SEL_NONE) {
3773	    Tcl_SetStringObj(Tcl_GetObjResult(interp),
3774		    selElementNames[elem], -1);
3775	}
3776	break;
3777    }
3778
3779    case SB_CMD_INDEX: {
3780	int index;
3781
3782	if (objc != 3) {
3783	    Tcl_WrongNumArgs(interp, 2, objv, "string");
3784	    goto error;
3785	}
3786	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
3787		&index) != TCL_OK) {
3788	    goto error;
3789	}
3790	Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
3791	break;
3792    }
3793
3794    case SB_CMD_INSERT: {
3795	int index;
3796
3797	if (objc != 4) {
3798	    Tcl_WrongNumArgs(interp, 2, objv, "index text");
3799	    goto error;
3800	}
3801	if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
3802		&index) != TCL_OK) {
3803	    goto error;
3804	}
3805	if (entryPtr->state == STATE_NORMAL) {
3806	    InsertChars(entryPtr, index, Tcl_GetString(objv[3]));
3807	}
3808	break;
3809    }
3810
3811    case SB_CMD_INVOKE:
3812	if (objc != 3) {
3813	    Tcl_WrongNumArgs(interp, 2, objv, "elemName");
3814	    goto error;
3815	}
3816	result = Tcl_GetIndexFromObj(interp, objv[2],
3817		selElementNames, "element", 0, &cmdIndex);
3818	if (result != TCL_OK) {
3819	    goto error;
3820	}
3821	if (entryPtr->state != STATE_DISABLED) {
3822	    if (SpinboxInvoke(interp, sbPtr, cmdIndex) != TCL_OK) {
3823		goto error;
3824	    }
3825	}
3826	break;
3827
3828    case SB_CMD_SCAN: {
3829	int x;
3830	char *minorCmd;
3831
3832	if (objc != 4) {
3833	    Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x");
3834	    goto error;
3835	}
3836	if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
3837	    goto error;
3838	}
3839
3840	minorCmd = Tcl_GetString(objv[2]);
3841	if (minorCmd[0] == 'm'
3842		&& (strncmp(minorCmd, "mark", strlen(minorCmd)) == 0)) {
3843	    entryPtr->scanMarkX = x;
3844	    entryPtr->scanMarkIndex = entryPtr->leftIndex;
3845	} else if ((minorCmd[0] == 'd')
3846		&& (strncmp(minorCmd, "dragto", strlen(minorCmd)) == 0)) {
3847	    EntryScanTo(entryPtr, x);
3848	} else {
3849	    Tcl_AppendResult(interp, "bad scan option \"",
3850		    Tcl_GetString(objv[2]), "\": must be mark or dragto",
3851		    NULL);
3852	    goto error;
3853	}
3854	break;
3855    }
3856
3857    case SB_CMD_SELECTION: {
3858	int index, index2;
3859
3860	if (objc < 3) {
3861	    Tcl_WrongNumArgs(interp, 2, objv, "option ?index?");
3862	    goto error;
3863	}
3864
3865	/*
3866	 * Parse the selection sub-command, using the command table
3867	 * "sbSelCmdNames" defined above.
3868	 */
3869
3870	result = Tcl_GetIndexFromObj(interp, objv[2], sbSelCmdNames,
3871		"selection option", 0, &selIndex);
3872	if (result != TCL_OK) {
3873	    goto error;
3874	}
3875
3876	/*
3877	 * Disabled entries don't allow the selection to be modified, but
3878	 * 'selection present' must return a boolean.
3879	 */
3880
3881	if ((entryPtr->state == STATE_DISABLED)
3882		&& (selIndex != SB_SEL_PRESENT)) {
3883	    goto done;
3884	}
3885
3886	switch (selIndex) {
3887	case SB_SEL_ADJUST:
3888	    if (objc != 4) {
3889		Tcl_WrongNumArgs(interp, 3, objv, "index");
3890		goto error;
3891	    }
3892	    if (GetEntryIndex(interp, entryPtr,
3893		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
3894		goto error;
3895	    }
3896	    if (entryPtr->selectFirst >= 0) {
3897		int half1, half2;
3898
3899		half1 = (entryPtr->selectFirst + entryPtr->selectLast)/2;
3900		half2 = (entryPtr->selectFirst + entryPtr->selectLast + 1)/2;
3901		if (index < half1) {
3902		    entryPtr->selectAnchor = entryPtr->selectLast;
3903		} else if (index > half2) {
3904		    entryPtr->selectAnchor = entryPtr->selectFirst;
3905		} else {
3906		    /*
3907		     * We're at about the halfway point in the selection; just
3908		     * keep the existing anchor.
3909		     */
3910		}
3911	    }
3912	    EntrySelectTo(entryPtr, index);
3913	    break;
3914
3915	case SB_SEL_CLEAR:
3916	    if (objc != 3) {
3917		Tcl_WrongNumArgs(interp, 3, objv, NULL);
3918		goto error;
3919	    }
3920	    if (entryPtr->selectFirst >= 0) {
3921		entryPtr->selectFirst = -1;
3922		entryPtr->selectLast = -1;
3923		EventuallyRedraw(entryPtr);
3924	    }
3925	    goto done;
3926
3927	case SB_SEL_FROM:
3928	    if (objc != 4) {
3929		Tcl_WrongNumArgs(interp, 3, objv, "index");
3930		goto error;
3931	    }
3932	    if (GetEntryIndex(interp, entryPtr,
3933		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
3934		goto error;
3935	    }
3936	    entryPtr->selectAnchor = index;
3937	    break;
3938
3939	case SB_SEL_PRESENT:
3940	    if (objc != 3) {
3941		Tcl_WrongNumArgs(interp, 3, objv, NULL);
3942		goto error;
3943	    }
3944	    Tcl_SetObjResult(interp,
3945		    Tcl_NewBooleanObj((entryPtr->selectFirst >= 0)));
3946	    goto done;
3947
3948	case SB_SEL_RANGE:
3949	    if (objc != 5) {
3950		Tcl_WrongNumArgs(interp, 3, objv, "start end");
3951		goto error;
3952	    }
3953	    if (GetEntryIndex(interp, entryPtr,
3954		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
3955		goto error;
3956	    }
3957	    if (GetEntryIndex(interp, entryPtr,
3958		    Tcl_GetString(objv[4]),& index2) != TCL_OK) {
3959		goto error;
3960	    }
3961	    if (index >= index2) {
3962		entryPtr->selectFirst = -1;
3963		entryPtr->selectLast = -1;
3964	    } else {
3965		entryPtr->selectFirst = index;
3966		entryPtr->selectLast = index2;
3967	    }
3968	    if (!(entryPtr->flags & GOT_SELECTION)
3969		    && (entryPtr->exportSelection)) {
3970		Tk_OwnSelection(entryPtr->tkwin, XA_PRIMARY,
3971			EntryLostSelection, (ClientData) entryPtr);
3972		entryPtr->flags |= GOT_SELECTION;
3973	    }
3974	    EventuallyRedraw(entryPtr);
3975	    break;
3976
3977	case SB_SEL_TO:
3978	    if (objc != 4) {
3979		Tcl_WrongNumArgs(interp, 3, objv, "index");
3980		goto error;
3981	    }
3982	    if (GetEntryIndex(interp, entryPtr,
3983		    Tcl_GetString(objv[3]), &index) != TCL_OK) {
3984		goto error;
3985	    }
3986	    EntrySelectTo(entryPtr, index);
3987	    break;
3988
3989	case SB_SEL_ELEMENT:
3990	    if ((objc < 3) || (objc > 4)) {
3991		Tcl_WrongNumArgs(interp, 3, objv, "?elemName?");
3992		goto error;
3993	    }
3994	    if (objc == 3) {
3995		Tcl_SetStringObj(Tcl_GetObjResult(interp),
3996			selElementNames[sbPtr->selElement], -1);
3997	    } else {
3998		int lastElement = sbPtr->selElement;
3999
4000		result = Tcl_GetIndexFromObj(interp, objv[3], selElementNames,
4001			"selection element", 0, &(sbPtr->selElement));
4002		if (result != TCL_OK) {
4003		    goto error;
4004		}
4005		if (lastElement != sbPtr->selElement) {
4006		    EventuallyRedraw(entryPtr);
4007		}
4008	    }
4009	    break;
4010	}
4011	break;
4012    }
4013
4014    case SB_CMD_SET:
4015	if (objc > 3) {
4016	    Tcl_WrongNumArgs(interp, 2, objv, "?string?");
4017	    goto error;
4018	}
4019	if (objc == 3) {
4020	    EntryValueChanged(entryPtr, Tcl_GetString(objv[2]));
4021	}
4022	Tcl_SetStringObj(Tcl_GetObjResult(interp), entryPtr->string, -1);
4023	break;
4024
4025    case SB_CMD_VALIDATE: {
4026	int code;
4027
4028	if (objc != 2) {
4029	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
4030	    goto error;
4031	}
4032	selIndex = entryPtr->validate;
4033	entryPtr->validate = VALIDATE_ALL;
4034	code = EntryValidateChange(entryPtr, NULL, entryPtr->string,
4035		-1, VALIDATE_FORCED);
4036	if (entryPtr->validate != VALIDATE_NONE) {
4037	    entryPtr->validate = selIndex;
4038	}
4039	Tcl_SetObjResult(interp, Tcl_NewBooleanObj((code == TCL_OK)));
4040	break;
4041    }
4042
4043    case SB_CMD_XVIEW: {
4044	int index;
4045
4046	if (objc == 2) {
4047	    double first, last;
4048	    char buf[TCL_DOUBLE_SPACE];
4049
4050	    EntryVisibleRange(entryPtr, &first, &last);
4051	    Tcl_PrintDouble(NULL, first, buf);
4052	    Tcl_SetResult(interp, buf, TCL_VOLATILE);
4053	    Tcl_PrintDouble(NULL, last, buf);
4054	    Tcl_AppendResult(interp, " ", buf, NULL);
4055	    goto done;
4056	} else if (objc == 3) {
4057	    if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]),
4058		    &index) != TCL_OK) {
4059		goto error;
4060	    }
4061	} else {
4062	    double fraction;
4063	    int count;
4064
4065	    index = entryPtr->leftIndex;
4066	    switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction,
4067		    &count)) {
4068	    case TK_SCROLL_ERROR:
4069		goto error;
4070	    case TK_SCROLL_MOVETO:
4071		index = (int) ((fraction * entryPtr->numChars) + 0.5);
4072		break;
4073	    case TK_SCROLL_PAGES: {
4074		int charsPerPage;
4075
4076		charsPerPage = ((Tk_Width(entryPtr->tkwin)
4077			- 2 * entryPtr->inset - entryPtr->xWidth)
4078			/ entryPtr->avgWidth) - 2;
4079		if (charsPerPage < 1) {
4080		    charsPerPage = 1;
4081		}
4082		index += count * charsPerPage;
4083		break;
4084	    }
4085	    case TK_SCROLL_UNITS:
4086		index += count;
4087		break;
4088	    }
4089	}
4090	if (index >= entryPtr->numChars) {
4091	    index = entryPtr->numChars - 1;
4092	}
4093	if (index < 0) {
4094	    index = 0;
4095	}
4096	entryPtr->leftIndex = index;
4097	entryPtr->flags |= UPDATE_SCROLLBAR;
4098	EntryComputeGeometry(entryPtr);
4099	EventuallyRedraw(entryPtr);
4100	break;
4101    }
4102    }
4103
4104  done:
4105    Tcl_Release((ClientData) entryPtr);
4106    return result;
4107
4108  error:
4109    Tcl_Release((ClientData) entryPtr);
4110    return TCL_ERROR;
4111}
4112
4113/*
4114 *---------------------------------------------------------------------------
4115 *
4116 * GetSpinboxElement --
4117 *
4118 *	Return the element associated with an x,y coord.
4119 *
4120 * Results:
4121 *	Element type as enum selelement.
4122 *
4123 * Side effects:
4124 *	None.
4125 *
4126 *---------------------------------------------------------------------------
4127 */
4128
4129static int
4130GetSpinboxElement(
4131    Spinbox *sbPtr,		/* Spinbox for which the index is being
4132				 * specified. */
4133    int x, int y)		/* Widget-relative coordinates. */
4134{
4135    Entry *entryPtr = (Entry *) sbPtr;
4136
4137    if ((x < 0) || (y < 0) || (y > Tk_Height(entryPtr->tkwin))
4138	    || (x > Tk_Width(entryPtr->tkwin))) {
4139	return SEL_NONE;
4140    }
4141
4142    if (x > (Tk_Width(entryPtr->tkwin) - entryPtr->inset - entryPtr->xWidth)) {
4143	if (y > (Tk_Height(entryPtr->tkwin) / 2)) {
4144	    return SEL_BUTTONDOWN;
4145	} else {
4146	    return SEL_BUTTONUP;
4147	}
4148    }
4149    return SEL_ENTRY;
4150}
4151
4152/*
4153 *--------------------------------------------------------------
4154 *
4155 * SpinboxInvoke --
4156 *
4157 *	This function is invoked when the invoke method for the widget is
4158 *	called.
4159 *
4160 * Results:
4161 *	TCL_OK.
4162 *
4163 * Side effects:
4164 *	An background error condition may arise when invoking the callback.
4165 *	The widget value may change.
4166 *
4167 *--------------------------------------------------------------
4168 */
4169
4170static int
4171SpinboxInvoke(
4172    register Tcl_Interp *interp,/* Current interpreter. */
4173    register Spinbox *sbPtr,	/* Spinbox to invoke. */
4174    int element)		/* Element to invoke, either the "up" or
4175				 * "down" button. */
4176{
4177    Entry *entryPtr = (Entry *) sbPtr;
4178    char *type;
4179    int code, up;
4180    Tcl_DString script;
4181
4182    switch (element) {
4183    case SEL_BUTTONUP:
4184	type = "up";
4185	up = 1;
4186	break;
4187    case SEL_BUTTONDOWN:
4188	type = "down";
4189	up = 0;
4190	break;
4191    default:
4192	return TCL_OK;
4193    }
4194
4195    if (fabs(sbPtr->increment) > MIN_DBL_VAL) {
4196	if (sbPtr->listObj != NULL) {
4197	    Tcl_Obj *objPtr;
4198
4199	    Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr);
4200	    if (strcmp(Tcl_GetString(objPtr), entryPtr->string)) {
4201		/*
4202		 * Somehow the string changed from what we expected, so let's
4203		 * do a search on the list to see if the current value is
4204		 * there. If not, move to the first element of the list.
4205		 */
4206
4207		int i, listc, elemLen, length = entryPtr->numChars;
4208		char *bytes;
4209		Tcl_Obj **listv;
4210
4211		Tcl_ListObjGetElements(interp, sbPtr->listObj, &listc, &listv);
4212		for (i = 0; i < listc; i++) {
4213		    bytes = Tcl_GetStringFromObj(listv[i], &elemLen);
4214		    if ((length == elemLen) &&
4215			    (memcmp(bytes, entryPtr->string,
4216				    (size_t) length) == 0)) {
4217			sbPtr->eIndex = i;
4218			break;
4219		    }
4220		}
4221	    }
4222	    if (up) {
4223		if (++sbPtr->eIndex >= sbPtr->nElements) {
4224		    if (sbPtr->wrap) {
4225			sbPtr->eIndex = 0;
4226		    } else {
4227			sbPtr->eIndex = sbPtr->nElements-1;
4228		    }
4229		}
4230	    } else {
4231		if (--sbPtr->eIndex < 0) {
4232		    if (sbPtr->wrap) {
4233			sbPtr->eIndex = sbPtr->nElements-1;
4234		    } else {
4235			sbPtr->eIndex = 0;
4236		    }
4237		}
4238	    }
4239	    Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr);
4240	    EntryValueChanged(entryPtr, Tcl_GetString(objPtr));
4241	} else if (!DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue)) {
4242	    double dvalue;
4243
4244	    if (Tcl_GetDouble(NULL, entryPtr->string, &dvalue) != TCL_OK) {
4245		/*
4246		 * If the string is empty, or isn't a valid double value, just
4247		 * use the -from value
4248		 */
4249
4250		dvalue = sbPtr->fromValue;
4251	    } else if (up) {
4252		dvalue += sbPtr->increment;
4253		if (dvalue > sbPtr->toValue) {
4254		    if (sbPtr->wrap) {
4255			dvalue = sbPtr->fromValue;
4256		    } else {
4257			dvalue = sbPtr->toValue;
4258		    }
4259		} else if (dvalue < sbPtr->fromValue) {
4260		    /*
4261		     * It's possible that when pressing up, we are still less
4262		     * than the fromValue, because the user may have
4263		     * manipulated the value by hand.
4264		     */
4265
4266		    dvalue = sbPtr->fromValue;
4267		}
4268	    } else {
4269		dvalue -= sbPtr->increment;
4270		if (dvalue < sbPtr->fromValue) {
4271		    if (sbPtr->wrap) {
4272			dvalue = sbPtr->toValue;
4273		    } else {
4274			dvalue = sbPtr->fromValue;
4275		    }
4276		} else if (dvalue > sbPtr->toValue) {
4277		    /*
4278		     * It's possible that when pressing down, we are still
4279		     * greater than the toValue, because the user may have
4280		     * manipulated the value by hand.
4281		     */
4282
4283		    dvalue = sbPtr->toValue;
4284		}
4285	    }
4286	    sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue);
4287	    EntryValueChanged(entryPtr, sbPtr->formatBuf);
4288	}
4289    }
4290
4291    if (sbPtr->command != NULL) {
4292	Tcl_DStringInit(&script);
4293	ExpandPercents(entryPtr, sbPtr->command, type, "", 0,
4294		VALIDATE_BUTTON, &script);
4295	Tcl_DStringAppend(&script, "", 1);
4296
4297	code = Tcl_EvalEx(interp, Tcl_DStringValue(&script), -1,
4298		TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
4299	Tcl_DStringFree(&script);
4300
4301	if (code != TCL_OK) {
4302	    Tcl_AddErrorInfo(interp, "\n\t(in command executed by spinbox)");
4303	    Tcl_BackgroundError(interp);
4304
4305	    /*
4306	     * Yes, it's an error, but a bg one, so we return OK
4307	     */
4308
4309	    return TCL_OK;
4310	}
4311
4312	Tcl_SetResult(interp, NULL, 0);
4313    }
4314
4315    return TCL_OK;
4316}
4317
4318/*
4319 *----------------------------------------------------------------------
4320 *
4321 * ComputeFormat --
4322 *
4323 *	This function is invoked to recompute the "format" fields of a
4324 *	spinbox's widget record, which determines how the value of the dial is
4325 *	converted to a string.
4326 *
4327 * Results:
4328 *	Tcl result code.
4329 *
4330 * Side effects:
4331 *	The format fields of the spinbox are modified.
4332 *
4333 *----------------------------------------------------------------------
4334 */
4335
4336static int
4337ComputeFormat(
4338     Spinbox *sbPtr)		/* Information about dial widget. */
4339{
4340    double maxValue, x;
4341    int mostSigDigit, numDigits, leastSigDigit, afterDecimal;
4342    int eDigits, fDigits;
4343
4344    /*
4345     * Compute the displacement from the decimal of the most significant digit
4346     * required for any number in the dial's range.
4347     */
4348
4349    if (sbPtr->reqFormat) {
4350	sbPtr->valueFormat = sbPtr->reqFormat;
4351	return TCL_OK;
4352    }
4353
4354    maxValue = fabs(sbPtr->fromValue);
4355    x = fabs(sbPtr->toValue);
4356    if (x > maxValue) {
4357	maxValue = x;
4358    }
4359    if (maxValue == 0) {
4360	maxValue = 1;
4361    }
4362    mostSigDigit = (int) floor(log10(maxValue));
4363
4364    if (fabs(sbPtr->increment) > MIN_DBL_VAL) {
4365	/*
4366	 * A increment was specified, so use it.
4367	 */
4368
4369	leastSigDigit = (int) floor(log10(sbPtr->increment));
4370    } else {
4371	leastSigDigit = 0;
4372    }
4373    numDigits = mostSigDigit - leastSigDigit + 1;
4374    if (numDigits < 1) {
4375	numDigits = 1;
4376    }
4377
4378    /*
4379     * Compute the number of characters required using "e" format and "f"
4380     * format, and then choose whichever one takes fewer characters.
4381     */
4382
4383    eDigits = numDigits + 4;
4384    if (numDigits > 1) {
4385	eDigits++;		/* Decimal point. */
4386    }
4387    afterDecimal = numDigits - mostSigDigit - 1;
4388    if (afterDecimal < 0) {
4389	afterDecimal = 0;
4390    }
4391    fDigits = (mostSigDigit >= 0) ? mostSigDigit + afterDecimal : afterDecimal;
4392    if (afterDecimal > 0) {
4393	fDigits++;		/* Decimal point. */
4394    }
4395    if (mostSigDigit < 0) {
4396	fDigits++;		/* Zero to left of decimal point. */
4397    }
4398    if (fDigits <= eDigits) {
4399	sprintf(sbPtr->digitFormat, "%%.%df", afterDecimal);
4400    } else {
4401	sprintf(sbPtr->digitFormat, "%%.%de", numDigits-1);
4402    }
4403    sbPtr->valueFormat = sbPtr->digitFormat;
4404    return TCL_OK;
4405}
4406
4407/*
4408 * Local Variables:
4409 * mode: c
4410 * c-basic-offset: 4
4411 * fill-column: 78
4412 * End:
4413 */
4414