1/*
2 * $Id$
3 *
4 * DERIVED FROM: tk/generic/tkEntry.c r1.35.
5 *
6 * Copyright (c) 1990-1994 The Regents of the University of California.
7 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
8 * Copyright (c) 2000 Ajuba Solutions.
9 * Copyright (c) 2002 ActiveState Corporation.
10 * Copyright (c) 2004 Joe English
11 */
12
13#include <string.h>
14#include <stdio.h>
15#include <tk.h>
16#include <X11/Xatom.h>
17
18#include "ttkTheme.h"
19#include "ttkWidget.h"
20
21/*
22 * Extra bits for core.flags:
23 */
24#define GOT_SELECTION		(WIDGET_USER_FLAG<<1)
25#define SYNCING_VARIABLE	(WIDGET_USER_FLAG<<2)
26#define VALIDATING		(WIDGET_USER_FLAG<<3)
27#define VALIDATION_SET_VALUE	(WIDGET_USER_FLAG<<4)
28
29/*
30 * Definitions for -validate option values:
31 */
32typedef enum validateMode {
33    VMODE_ALL, VMODE_KEY, VMODE_FOCUS, VMODE_FOCUSIN, VMODE_FOCUSOUT, VMODE_NONE
34} VMODE;
35
36static const char *const validateStrings[] = {
37    "all", "key", "focus", "focusin", "focusout", "none", NULL
38};
39
40/*
41 * Validation reasons:
42 */
43typedef enum validateReason {
44    VALIDATE_INSERT, VALIDATE_DELETE,
45    VALIDATE_FOCUSIN, VALIDATE_FOCUSOUT,
46    VALIDATE_FORCED
47} VREASON;
48
49static const char *const validateReasonStrings[] = {
50    "key", "key", "focusin", "focusout", "forced", NULL
51};
52
53/*------------------------------------------------------------------------
54 * +++ Entry widget record.
55 *
56 * Dependencies:
57 *
58 * textVariableTrace	: textVariableObj
59 *
60 * numBytes,numChars	: string
61 * displayString	: numChars, showChar
62 * layoutHeight,
63 * layoutWidth,
64 * textLayout		: fontObj, displayString
65 * layoutX, layoutY	: textLayout, justify, xscroll.first
66 *
67 * Invariants:
68 *
69 * 0 <= insertPos <= numChars
70 * 0 <= selectFirst < selectLast <= numChars || selectFirst == selectLast == -1
71 * displayString points to string if showChar == NULL,
72 * or to malloc'ed storage if showChar != NULL.
73 */
74
75/* Style parameters:
76 */
77typedef struct {
78    Tcl_Obj *foregroundObj;	/* Foreground color for normal text */
79    Tcl_Obj *backgroundObj;	/* Entry widget background color */
80    Tcl_Obj *selBorderObj;	/* Border and background for selection */
81    Tcl_Obj *selBorderWidthObj;	/* Width of selection border */
82    Tcl_Obj *selForegroundObj;	/* Foreground color for selected text */
83    Tcl_Obj *insertColorObj;	/* Color of insertion cursor */
84    Tcl_Obj *insertWidthObj;	/* Insert cursor width */
85} EntryStyleData;
86
87typedef struct {
88    /*
89     * Internal state:
90     */
91    char *string;		/* Storage for string (malloced) */
92    int numBytes;		/* Length of string in bytes. */
93    int numChars;		/* Length of string in characters. */
94
95    int insertPos;		/* Insert index */
96    int selectFirst;		/* Index of start of selection, or -1 */
97    int selectLast;		/* Index of end of selection, or -1 */
98
99    Scrollable xscroll;		/* Current scroll position */
100    ScrollHandle xscrollHandle;
101
102    /*
103     * Options managed by Tk_SetOptions:
104     */
105    Tcl_Obj *textVariableObj;	/* Name of linked variable */
106    int exportSelection;	/* Tie internal selection to X selection? */
107
108    VMODE validate;		/* Validation mode */
109    char *validateCmd;		/* Validation script template */
110    char *invalidCmd;		/* Invalid callback script template */
111
112    char *showChar;		/* Used to derive displayString */
113
114    Tcl_Obj *fontObj;		/* Text font to use */
115    Tcl_Obj *widthObj;		/* Desired width of window (in avgchars) */
116    Tk_Justify justify;		/* Text justification */
117
118    EntryStyleData styleData;	/* Display style data (widget options) */
119    EntryStyleData styleDefaults;/* Style defaults (fallback values) */
120
121    Tcl_Obj *stateObj;		/* Compatibility option -- see CheckStateObj */
122
123    /*
124     * Derived resources:
125     */
126    Ttk_TraceHandle *textVariableTrace;
127
128    char *displayString;	/* String to use when displaying */
129    Tk_TextLayout textLayout;	/* Cached text layout information. */
130    int layoutWidth;		/* textLayout width */
131    int layoutHeight;		/* textLayout height */
132
133    int layoutX, layoutY;	/* Origin for text layout. */
134
135} EntryPart;
136
137typedef struct {
138    WidgetCore	core;
139    EntryPart	entry;
140} Entry;
141
142/*
143 * Extra mask bits for Tk_SetOptions()
144 */
145#define STATE_CHANGED	 	(0x100)	/* -state option changed */
146#define TEXTVAR_CHANGED	 	(0x200)	/* -textvariable option changed */
147#define SCROLLCMD_CHANGED	(0x400)	/* -xscrollcommand option changed */
148
149/*
150 * Default option values:
151 */
152#define DEF_SELECT_BG	"#000000"
153#define DEF_SELECT_FG	"#ffffff"
154#define DEF_INSERT_BG	"black"
155#define DEF_ENTRY_WIDTH	"20"
156#define DEF_ENTRY_FONT	"TkTextFont"
157#define DEF_LIST_HEIGHT	"10"
158
159static Tk_OptionSpec EntryOptionSpecs[] = {
160    WIDGET_TAKES_FOCUS,
161
162    {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
163        "ExportSelection", "1", -1, Tk_Offset(Entry, entry.exportSelection),
164	0,0,0 },
165    {TK_OPTION_FONT, "-font", "font", "Font",
166	DEF_ENTRY_FONT, Tk_Offset(Entry, entry.fontObj),-1,
167	0,0,GEOMETRY_CHANGED},
168    {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand",
169	NULL, -1, Tk_Offset(Entry, entry.invalidCmd),
170	TK_OPTION_NULL_OK, 0, 0},
171    {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify",
172	"left", -1, Tk_Offset(Entry, entry.justify),
173	0, 0, GEOMETRY_CHANGED},
174    {TK_OPTION_STRING, "-show", "show", "Show",
175        NULL, -1, Tk_Offset(Entry, entry.showChar),
176	TK_OPTION_NULL_OK, 0, 0},
177    {TK_OPTION_STRING, "-state", "state", "State",
178	"normal", Tk_Offset(Entry, entry.stateObj), -1,
179        0,0,STATE_CHANGED},
180    {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable",
181	NULL, Tk_Offset(Entry, entry.textVariableObj), -1,
182	TK_OPTION_NULL_OK,0,TEXTVAR_CHANGED},
183    {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate",
184	"none", -1, Tk_Offset(Entry, entry.validate),
185	0, (ClientData) validateStrings, 0},
186    {TK_OPTION_STRING, "-validatecommand", "validateCommand", "ValidateCommand",
187	NULL, -1, Tk_Offset(Entry, entry.validateCmd),
188	TK_OPTION_NULL_OK, 0, 0},
189    {TK_OPTION_INT, "-width", "width", "Width",
190	DEF_ENTRY_WIDTH, Tk_Offset(Entry, entry.widthObj), -1,
191	0,0,GEOMETRY_CHANGED},
192    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
193	NULL, -1, Tk_Offset(Entry, entry.xscroll.scrollCmd),
194	TK_OPTION_NULL_OK, 0, SCROLLCMD_CHANGED},
195
196    /* EntryStyleData options:
197     */
198    {TK_OPTION_COLOR, "-foreground", "textColor", "TextColor",
199	NULL, Tk_Offset(Entry, entry.styleData.foregroundObj), -1,
200	TK_OPTION_NULL_OK,0,0},
201    {TK_OPTION_COLOR, "-background", "windowColor", "WindowColor",
202	NULL, Tk_Offset(Entry, entry.styleData.backgroundObj), -1,
203	TK_OPTION_NULL_OK,0,0},
204
205    WIDGET_INHERIT_OPTIONS(ttkCoreOptionSpecs)
206};
207
208/*------------------------------------------------------------------------
209 * +++ EntryStyleData management.
210 * 	This is still more awkward than it should be;
211 * 	it should be able to use the Element API instead.
212 */
213
214/* EntryInitStyleDefaults --
215 * 	Initialize EntryStyleData record to fallback values.
216 */
217static void EntryInitStyleDefaults(EntryStyleData *es)
218{
219#define INIT(member, value) \
220	es->member = Tcl_NewStringObj(value, -1); \
221	Tcl_IncrRefCount(es->member);
222    INIT(foregroundObj, DEFAULT_FOREGROUND)
223    INIT(selBorderObj, DEF_SELECT_BG)
224    INIT(selForegroundObj, DEF_SELECT_FG)
225    INIT(insertColorObj, DEFAULT_FOREGROUND)
226    INIT(selBorderWidthObj, "0")
227    INIT(insertWidthObj, "1")
228#undef INIT
229}
230
231static void EntryFreeStyleDefaults(EntryStyleData *es)
232{
233    Tcl_DecrRefCount(es->foregroundObj);
234    Tcl_DecrRefCount(es->selBorderObj);
235    Tcl_DecrRefCount(es->selForegroundObj);
236    Tcl_DecrRefCount(es->insertColorObj);
237    Tcl_DecrRefCount(es->selBorderWidthObj);
238    Tcl_DecrRefCount(es->insertWidthObj);
239}
240
241/*
242 * EntryInitStyleData --
243 * 	Look up style-specific data for an entry widget.
244 */
245static void EntryInitStyleData(Entry *entryPtr, EntryStyleData *es)
246{
247    Ttk_State state = entryPtr->core.state;
248    Ttk_ResourceCache cache = Ttk_GetResourceCache(entryPtr->core.interp);
249    Tk_Window tkwin = entryPtr->core.tkwin;
250    Tcl_Obj *tmp;
251
252    /* Initialize to fallback values:
253     */
254    *es = entryPtr->entry.styleDefaults;
255
256#   define INIT(member, name) \
257    if ((tmp=Ttk_QueryOption(entryPtr->core.layout,name,state))) \
258    	es->member=tmp;
259    INIT(foregroundObj, "-foreground");
260    INIT(selBorderObj, "-selectbackground")
261    INIT(selBorderWidthObj, "-selectborderwidth")
262    INIT(selForegroundObj, "-selectforeground")
263    INIT(insertColorObj, "-insertcolor")
264    INIT(insertWidthObj, "-insertwidth")
265#undef INIT
266
267    /* Reacquire color & border resources from resource cache.
268     */
269    es->foregroundObj = Ttk_UseColor(cache, tkwin, es->foregroundObj);
270    es->selForegroundObj = Ttk_UseColor(cache, tkwin, es->selForegroundObj);
271    es->insertColorObj = Ttk_UseColor(cache, tkwin, es->insertColorObj);
272    es->selBorderObj = Ttk_UseBorder(cache, tkwin, es->selBorderObj);
273}
274
275/*------------------------------------------------------------------------
276 * +++ Resource management.
277 */
278
279/* EntryDisplayString --
280 * 	Return a malloc'ed string consisting of 'numChars' copies
281 * 	of (the first character in the string) 'showChar'.
282 * 	Used to compute the displayString if -show is non-NULL.
283 */
284static char *EntryDisplayString(const char *showChar, int numChars)
285{
286    char *displayString, *p;
287    int size;
288    Tcl_UniChar ch;
289    char buf[TCL_UTF_MAX];
290
291    Tcl_UtfToUniChar(showChar, &ch);
292    size = Tcl_UniCharToUtf(ch, buf);
293    p = displayString = ckalloc(numChars * size + 1);
294
295    while (numChars--) {
296	p += Tcl_UniCharToUtf(ch, p);
297    }
298    *p = '\0';
299
300    return displayString;
301}
302
303/* EntryUpdateTextLayout --
304 * 	Recompute textLayout, layoutWidth, and layoutHeight
305 * 	from displayString and fontObj.
306 */
307static void EntryUpdateTextLayout(Entry *entryPtr)
308{
309    Tk_FreeTextLayout(entryPtr->entry.textLayout);
310    entryPtr->entry.textLayout = Tk_ComputeTextLayout(
311	    Tk_GetFontFromObj(entryPtr->core.tkwin, entryPtr->entry.fontObj),
312	    entryPtr->entry.displayString, entryPtr->entry.numChars,
313	    0/*wraplength*/, entryPtr->entry.justify, TK_IGNORE_NEWLINES,
314	    &entryPtr->entry.layoutWidth, &entryPtr->entry.layoutHeight);
315}
316
317/* EntryEditable --
318 * 	Returns 1 if the entry widget accepts user changes, 0 otherwise
319 */
320static int
321EntryEditable(Entry *entryPtr)
322{
323    return !(entryPtr->core.state & (TTK_STATE_DISABLED|TTK_STATE_READONLY));
324}
325
326/*------------------------------------------------------------------------
327 * +++ Selection management.
328 */
329
330/* EntryFetchSelection --
331 *	Selection handler for entry widgets.
332 */
333static int
334EntryFetchSelection(
335    ClientData clientData, int offset, char *buffer, int maxBytes)
336{
337    Entry *entryPtr = (Entry *) clientData;
338    size_t byteCount;
339    const char *string;
340    const char *selStart, *selEnd;
341
342    if (entryPtr->entry.selectFirst < 0 || !entryPtr->entry.exportSelection) {
343	return -1;
344    }
345    string = entryPtr->entry.displayString;
346
347    selStart = Tcl_UtfAtIndex(string, entryPtr->entry.selectFirst);
348    selEnd = Tcl_UtfAtIndex(selStart,
349	    entryPtr->entry.selectLast - entryPtr->entry.selectFirst);
350    byteCount = selEnd - selStart - offset;
351    if (byteCount > (size_t)maxBytes) {
352    /* @@@POSSIBLE BUG: Can transfer partial UTF-8 sequences.  Is this OK? */
353	byteCount = maxBytes;
354    }
355    if (byteCount <= 0) {
356	return 0;
357    }
358    memcpy(buffer, selStart + offset, byteCount);
359    buffer[byteCount] = '\0';
360    return byteCount;
361}
362
363/* EntryLostSelection --
364 *	Tk_LostSelProc for Entry widgets; called when an entry
365 *	loses ownership of the selection.
366 */
367static void EntryLostSelection(ClientData clientData)
368{
369    Entry *entryPtr = (Entry *) clientData;
370    entryPtr->core.flags &= ~GOT_SELECTION;
371    entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1;
372    TtkRedisplayWidget(&entryPtr->core);
373}
374
375/* EntryOwnSelection --
376 * 	Assert ownership of the PRIMARY selection,
377 * 	if -exportselection set and selection is present.
378 */
379static void EntryOwnSelection(Entry *entryPtr)
380{
381    if (entryPtr->entry.exportSelection
382	&& !(entryPtr->core.flags & GOT_SELECTION)) {
383	Tk_OwnSelection(entryPtr->core.tkwin, XA_PRIMARY, EntryLostSelection,
384		(ClientData) entryPtr);
385	entryPtr->core.flags |= GOT_SELECTION;
386    }
387}
388
389/*------------------------------------------------------------------------
390 * +++ Validation.
391 */
392
393/* ExpandPercents --
394 *	Expand an entry validation script template (-validatecommand
395 *	or -invalidcommand).
396 */
397static void
398ExpandPercents(
399     Entry *entryPtr,		/* Entry that needs validation. */
400     const char *template, 	/* Script template */
401     const char *new,		/* Potential new value of entry string */
402     int index,			/* index of insert/delete */
403     int count,			/* #changed characters */
404     VREASON reason,		/* Reason for change */
405     Tcl_DString *dsPtr)	/* Result of %-substitutions */
406{
407    int spaceNeeded, cvtFlags;
408    int number, length;
409    const char *string;
410    int stringLength;
411    Tcl_UniChar ch;
412    char numStorage[2*TCL_INTEGER_SPACE];
413
414    while (*template) {
415	/* Find everything up to the next % character and append it
416	 * to the result string.
417	 */
418	string = Tcl_UtfFindFirst(template, '%');
419	if (string == NULL) {
420	    /* No more %-sequences to expand.
421	     * Copy the rest of the template.
422	     */
423	    Tcl_DStringAppend(dsPtr, template, -1);
424	    return;
425	}
426	if (string != template) {
427	    Tcl_DStringAppend(dsPtr, template, string - template);
428	    template = string;
429	}
430
431	/* There's a percent sequence here.  Process it.
432	 */
433	++template; /* skip over % */
434	if (*template != '\0') {
435	    template += Tcl_UtfToUniChar(template, &ch);
436	} else {
437	    ch = '%';
438	}
439
440	stringLength = -1;
441	switch (ch) {
442	    case 'd': /* Type of call that caused validation */
443		if (reason == VALIDATE_INSERT) {
444		    number = 1;
445		} else if (reason == VALIDATE_DELETE) {
446		    number = 0;
447		} else {
448		    number = -1;
449		}
450		sprintf(numStorage, "%d", number);
451		string = numStorage;
452		break;
453	    case 'i': /* index of insert/delete */
454		sprintf(numStorage, "%d", index);
455		string = numStorage;
456		break;
457	    case 'P': /* 'Peeked' new value of the string */
458		string = new;
459		break;
460	    case 's': /* Current string value */
461		string = entryPtr->entry.string;
462		break;
463	    case 'S': /* string to be inserted/deleted, if any */
464		if (reason == VALIDATE_INSERT) {
465		    string = Tcl_UtfAtIndex(new, index);
466		    stringLength = Tcl_UtfAtIndex(string, count) - string;
467		} else if (reason == VALIDATE_DELETE) {
468		    string = Tcl_UtfAtIndex(entryPtr->entry.string, index);
469		    stringLength = Tcl_UtfAtIndex(string, count) - string;
470		} else {
471		    string = "";
472		    stringLength = 0;
473		}
474		break;
475	    case 'v': /* type of validation currently set */
476		string = validateStrings[entryPtr->entry.validate];
477		break;
478	    case 'V': /* type of validation in effect */
479		string = validateReasonStrings[reason];
480		break;
481	    case 'W': /* widget name */
482		string = Tk_PathName(entryPtr->core.tkwin);
483		break;
484	    default:
485		length = Tcl_UniCharToUtf(ch, numStorage);
486		numStorage[length] = '\0';
487		string = numStorage;
488		break;
489	}
490
491	spaceNeeded = Tcl_ScanCountedElement(string, stringLength, &cvtFlags);
492	length = Tcl_DStringLength(dsPtr);
493	Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
494	spaceNeeded = Tcl_ConvertCountedElement(string, stringLength,
495		Tcl_DStringValue(dsPtr) + length,
496		cvtFlags | TCL_DONT_USE_BRACES);
497	Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
498    }
499}
500
501/* RunValidationScript --
502 * 	Build and evaluate an entry validation script.
503 * 	If the script raises an error, disable validation
504 * 	by setting '-validate none'
505 */
506static int RunValidationScript(
507    Tcl_Interp *interp, 	/* Interpreter to use */
508    Entry *entryPtr,		/* Entry being validated */
509    const char *template,	/* Script template */
510    const char *optionName,	/* "-validatecommand", "-invalidcommand" */
511    const char *new,		/* Potential new value of entry string */
512    int index,			/* index of insert/delete */
513    int count,			/* #changed characters */
514    VREASON reason)		/* Reason for change */
515{
516    Tcl_DString script;
517    int code;
518
519    Tcl_DStringInit(&script);
520    ExpandPercents(entryPtr, template, new, index, count, reason, &script);
521    code = Tcl_EvalEx(interp,
522		Tcl_DStringValue(&script), Tcl_DStringLength(&script),
523		TCL_EVAL_GLOBAL);
524    Tcl_DStringFree(&script);
525    if (WidgetDestroyed(&entryPtr->core))
526	return TCL_ERROR;
527
528    if (code != TCL_OK && code != TCL_RETURN) {
529	Tcl_AddErrorInfo(interp, "\n\t(in ");
530	Tcl_AddErrorInfo(interp, optionName);
531	Tcl_AddErrorInfo(interp, " validation command executed by ");
532	Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->core.tkwin));
533	Tcl_AddErrorInfo(interp, ")");
534	entryPtr->entry.validate = VMODE_NONE;
535	return TCL_ERROR;
536    }
537    return TCL_OK;
538}
539
540/* EntryNeedsValidation --
541 * 	Determine whether the specified VREASON should trigger validation
542 * 	in the current VMODE.
543 */
544static int EntryNeedsValidation(VMODE vmode, VREASON reason)
545{
546    return (reason == VALIDATE_FORCED)
547	|| (vmode == VMODE_ALL)
548	|| (reason == VALIDATE_FOCUSIN
549	    && (vmode == VMODE_FOCUSIN || vmode == VMODE_FOCUS))
550	|| (reason == VALIDATE_FOCUSOUT
551	    && (vmode == VMODE_FOCUSOUT || vmode == VMODE_FOCUS))
552	|| (reason == VALIDATE_INSERT && vmode == VMODE_KEY)
553	|| (reason == VALIDATE_DELETE && vmode == VMODE_KEY)
554	;
555}
556
557/* EntryValidateChange --
558 *	Validate a proposed change to the entry widget's value if required.
559 *	Call the -invalidcommand if validation fails.
560 *
561 * Returns:
562 *	TCL_OK if the change is accepted
563 *	TCL_BREAK if the change is rejected
564 *      TCL_ERROR if any errors occured
565 *
566 * The change will be rejected if -validatecommand returns 0,
567 * or if -validatecommand or -invalidcommand modifies the value.
568 */
569static int
570EntryValidateChange(
571    Entry *entryPtr,		/* Entry that needs validation. */
572    const char *newValue,	/* Potential new value of entry string */
573    int index,			/* index of insert/delete, -1 otherwise */
574    int count,			/* #changed characters */
575    VREASON reason)		/* Reason for change */
576{
577    Tcl_Interp *interp = entryPtr->core.interp;
578    VMODE vmode = entryPtr->entry.validate;
579    int code, change_ok;
580
581    if (   (entryPtr->entry.validateCmd == NULL)
582	|| (entryPtr->core.flags & VALIDATING)
583	|| !EntryNeedsValidation(vmode, reason) )
584    {
585	return TCL_OK;
586    }
587
588    entryPtr->core.flags |= VALIDATING;
589
590    /* Run -validatecommand and check return value:
591     */
592    code = RunValidationScript(interp, entryPtr,
593	    entryPtr->entry.validateCmd, "-validatecommand",
594	    newValue, index, count, reason);
595    if (code != TCL_OK) {
596	goto done;
597    }
598
599    code = Tcl_GetBooleanFromObj(interp,Tcl_GetObjResult(interp), &change_ok);
600    if (code != TCL_OK) {
601	entryPtr->entry.validate = VMODE_NONE;	/* Disable validation */
602	Tcl_AddErrorInfo(interp,
603		"\n(validation command did not return valid boolean)");
604	goto done;
605    }
606
607    /* Run the -invalidcommand if validation failed:
608     */
609    if (!change_ok && entryPtr->entry.invalidCmd != NULL) {
610	code = RunValidationScript(interp, entryPtr,
611		entryPtr->entry.invalidCmd, "-invalidcommand",
612		newValue, index, count, reason);
613	if (code != TCL_OK) {
614	    goto done;
615	}
616    }
617
618    /* Reject the pending change if validation failed
619     * or if a validation script changed the value.
620     */
621    if (!change_ok || (entryPtr->core.flags & VALIDATION_SET_VALUE)) {
622	code = TCL_BREAK;
623    }
624
625done:
626    entryPtr->core.flags &= ~(VALIDATING|VALIDATION_SET_VALUE);
627    return code;
628}
629
630/* EntryRevalidate --
631 * 	Revalidate the current value of an entry widget,
632 * 	update the TTK_STATE_INVALID bit.
633 *
634 * Returns:
635 * 	TCL_OK if valid, TCL_BREAK if invalid, TCL_ERROR on error.
636 */
637static int EntryRevalidate(Tcl_Interp *interp, Entry *entryPtr, VREASON reason)
638{
639    int code = EntryValidateChange(
640		    entryPtr, entryPtr->entry.string, -1,0, reason);
641
642    if (code == TCL_BREAK) {
643	TtkWidgetChangeState(&entryPtr->core, TTK_STATE_INVALID, 0);
644    } else if (code == TCL_OK) {
645	TtkWidgetChangeState(&entryPtr->core, 0, TTK_STATE_INVALID);
646    }
647
648    return code;
649}
650
651/* EntryRevalidateBG --
652 * 	Revalidate in the background (called from event handler).
653 */
654static void EntryRevalidateBG(Entry *entryPtr, VREASON reason)
655{
656    Tcl_Interp *interp = entryPtr->core.interp;
657    if (EntryRevalidate(interp, entryPtr, reason) == TCL_ERROR) {
658	Tcl_BackgroundError(interp);
659    }
660}
661
662/*------------------------------------------------------------------------
663 * +++ Entry widget modification.
664 */
665
666/* AdjustIndex --
667 * 	Adjust index to account for insertion (nChars > 0)
668 * 	or deletion (nChars < 0) at specified index.
669 */
670static int AdjustIndex(int i0, int index, int nChars)
671{
672    if (i0 >= index) {
673	i0 += nChars;
674	if (i0 < index) { /* index was inside deleted range */
675	    i0 = index;
676	}
677    }
678    return i0;
679}
680
681/* AdjustIndices --
682 * 	Adjust all internal entry indexes to account for change.
683 * 	Note that insertPos, and selectFirst have "right gravity",
684 * 	while leftIndex (=xscroll.first) and selectLast have "left gravity".
685 */
686static void AdjustIndices(Entry *entryPtr, int index, int nChars)
687{
688    EntryPart *e = &entryPtr->entry;
689    int g = nChars > 0;		/* left gravity adjustment */
690
691    e->insertPos    = AdjustIndex(e->insertPos, index, nChars);
692    e->selectFirst  = AdjustIndex(e->selectFirst, index, nChars);
693    e->selectLast   = AdjustIndex(e->selectLast, index+g, nChars);
694    e->xscroll.first= AdjustIndex(e->xscroll.first, index+g, nChars);
695
696    if (e->selectLast <= e->selectFirst)
697	e->selectFirst = e->selectLast = -1;
698}
699
700/* EntryStoreValue --
701 *	Replace the contents of a text entry with a given value,
702 *	recompute dependent resources, and schedule a redisplay.
703 *
704 *	See also: EntrySetValue().
705 */
706static void
707EntryStoreValue(Entry *entryPtr, const char *value)
708{
709    size_t numBytes = strlen(value);
710    int numChars = Tcl_NumUtfChars(value, numBytes);
711
712    if (entryPtr->core.flags & VALIDATING)
713	entryPtr->core.flags |= VALIDATION_SET_VALUE;
714
715    /* Make sure all indices remain in bounds:
716     */
717    if (numChars < entryPtr->entry.numChars)
718	AdjustIndices(entryPtr, numChars, numChars - entryPtr->entry.numChars);
719
720    /* Free old value:
721     */
722    if (entryPtr->entry.displayString != entryPtr->entry.string)
723	ckfree(entryPtr->entry.displayString);
724    ckfree(entryPtr->entry.string);
725
726    /* Store new value:
727     */
728    entryPtr->entry.string = ckalloc(numBytes + 1);
729    strcpy(entryPtr->entry.string, value);
730    entryPtr->entry.numBytes = numBytes;
731    entryPtr->entry.numChars = numChars;
732
733    entryPtr->entry.displayString
734	= entryPtr->entry.showChar
735	? EntryDisplayString(entryPtr->entry.showChar, numChars)
736	: entryPtr->entry.string
737	;
738
739    /* Update layout, schedule redisplay:
740     */
741    EntryUpdateTextLayout(entryPtr);
742    TtkRedisplayWidget(&entryPtr->core);
743}
744
745/* EntrySetValue --
746 * 	Stores a new value in the entry widget and updates the
747 * 	linked -textvariable, if any.  The write trace on the
748 * 	text variable is temporarily disabled; however, other
749 * 	write traces may change the value of the variable.
750 * 	If so, the widget is updated again with the new value.
751 *
752 * Returns:
753 * 	TCL_OK if successful, TCL_ERROR otherwise.
754 */
755static int EntrySetValue(Entry *entryPtr, const char *value)
756{
757    EntryStoreValue(entryPtr, value);
758
759    if (entryPtr->entry.textVariableObj) {
760	const char *textVarName =
761	    Tcl_GetString(entryPtr->entry.textVariableObj);
762	if (textVarName && *textVarName) {
763	    entryPtr->core.flags |= SYNCING_VARIABLE;
764	    value = Tcl_SetVar(entryPtr->core.interp, textVarName,
765		    value, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG);
766	    entryPtr->core.flags &= ~SYNCING_VARIABLE;
767	    if (!value || WidgetDestroyed(&entryPtr->core)) {
768		return TCL_ERROR;
769	    } else if (strcmp(value, entryPtr->entry.string) != 0) {
770		/* Some write trace has changed the variable value.
771		 */
772		EntryStoreValue(entryPtr, value);
773	    }
774	}
775    }
776
777    return TCL_OK;
778}
779
780/* EntryTextVariableTrace --
781 *	Variable trace procedure for entry -textvariable
782 */
783static void EntryTextVariableTrace(void *recordPtr, const char *value)
784{
785    Entry *entryPtr = recordPtr;
786
787    if (WidgetDestroyed(&entryPtr->core)) {
788	return;
789    }
790
791    if (entryPtr->core.flags & SYNCING_VARIABLE) {
792	/* Trace was fired due to Tcl_SetVar call in EntrySetValue.
793	 * Don't do anything.
794	 */
795	return;
796    }
797
798    EntryStoreValue(entryPtr, value ? value : "");
799}
800
801/*------------------------------------------------------------------------
802 * +++ Insertion and deletion.
803 */
804
805/* InsertChars --
806 *	Add new characters to an entry widget.
807 */
808static int
809InsertChars(
810    Entry *entryPtr,		/* Entry that is to get the new elements. */
811    int index,			/* Insert before this index */
812    const char *value)		/* New characters to add */
813{
814    char *string = entryPtr->entry.string;
815    size_t byteIndex = Tcl_UtfAtIndex(string, index) - string;
816    size_t byteCount = strlen(value);
817    int charsAdded = Tcl_NumUtfChars(value, byteCount);
818    size_t newByteCount = entryPtr->entry.numBytes + byteCount + 1;
819    char *new;
820    int code;
821
822    if (byteCount == 0) {
823	return TCL_OK;
824    }
825
826    new =  ckalloc(newByteCount);
827    memcpy(new, string, byteIndex);
828    strcpy(new + byteIndex, value);
829    strcpy(new + byteIndex + byteCount, string + byteIndex);
830
831    code = EntryValidateChange(
832	    entryPtr, new, index, charsAdded, VALIDATE_INSERT);
833
834    if (code == TCL_OK) {
835	AdjustIndices(entryPtr, index, charsAdded);
836	code = EntrySetValue(entryPtr, new);
837    } else if (code == TCL_BREAK) {
838	code = TCL_OK;
839    }
840
841    ckfree(new);
842    return code;
843}
844
845/* DeleteChars --
846 *	Remove one or more characters from an entry widget.
847 */
848static int
849DeleteChars(
850    Entry *entryPtr,		/* Entry widget to modify. */
851    int index,			/* Index of first character to delete. */
852    int count)			/* How many characters to delete. */
853{
854    char *string = entryPtr->entry.string;
855    size_t byteIndex, byteCount, newByteCount;
856    char *new;
857    int code;
858
859    if (index < 0) {
860	index = 0;
861    }
862    if (count > entryPtr->entry.numChars - index) {
863	count = entryPtr->entry.numChars - index;
864    }
865    if (count <= 0) {
866	return TCL_OK;
867    }
868
869    byteIndex = Tcl_UtfAtIndex(string, index) - string;
870    byteCount = Tcl_UtfAtIndex(string+byteIndex, count) - (string+byteIndex);
871
872    newByteCount = entryPtr->entry.numBytes + 1 - byteCount;
873    new =  ckalloc(newByteCount);
874    memcpy(new, string, byteIndex);
875    strcpy(new + byteIndex, string + byteIndex + byteCount);
876
877    code = EntryValidateChange(
878	    entryPtr, new, index, count, VALIDATE_DELETE);
879
880    if (code == TCL_OK) {
881	AdjustIndices(entryPtr, index, -count);
882	code = EntrySetValue(entryPtr, new);
883    } else if (code == TCL_BREAK) {
884	code = TCL_OK;
885    }
886    ckfree(new);
887
888    return code;
889}
890
891/*------------------------------------------------------------------------
892 * +++ Event handler.
893 */
894
895/* EntryEventProc --
896 *	Extra event handling for entry widgets:
897 *	Triggers validation on FocusIn and FocusOut events.
898 */
899#define EntryEventMask (FocusChangeMask)
900static void
901EntryEventProc(ClientData clientData, XEvent *eventPtr)
902{
903    Entry *entryPtr = (Entry *) clientData;
904
905    Tcl_Preserve(clientData);
906    switch (eventPtr->type) {
907	case DestroyNotify:
908	    Tk_DeleteEventHandler(entryPtr->core.tkwin,
909		    EntryEventMask, EntryEventProc, clientData);
910	    break;
911	case FocusIn:
912	    EntryRevalidateBG(entryPtr, VALIDATE_FOCUSIN);
913	    break;
914	case FocusOut:
915	    EntryRevalidateBG(entryPtr, VALIDATE_FOCUSOUT);
916	    break;
917    }
918    Tcl_Release(clientData);
919}
920
921/*------------------------------------------------------------------------
922 * +++ Initialization and cleanup.
923 */
924
925static void
926EntryInitialize(Tcl_Interp *interp, void *recordPtr)
927{
928    Entry *entryPtr = recordPtr;
929
930    Tk_CreateEventHandler(
931	entryPtr->core.tkwin, EntryEventMask, EntryEventProc, entryPtr);
932    Tk_CreateSelHandler(entryPtr->core.tkwin, XA_PRIMARY, XA_STRING,
933	EntryFetchSelection, (ClientData) entryPtr, XA_STRING);
934    TtkBlinkCursor(&entryPtr->core);
935
936    entryPtr->entry.string		= ckalloc(1);
937    *entryPtr->entry.string 		= '\0';
938    entryPtr->entry.displayString	= entryPtr->entry.string;
939    entryPtr->entry.textVariableTrace 	= 0;
940    entryPtr->entry.numBytes = entryPtr->entry.numChars = 0;
941
942    EntryInitStyleDefaults(&entryPtr->entry.styleDefaults);
943
944    entryPtr->entry.xscrollHandle =
945	TtkCreateScrollHandle(&entryPtr->core, &entryPtr->entry.xscroll);
946
947    entryPtr->entry.insertPos		= 0;
948    entryPtr->entry.selectFirst 	= -1;
949    entryPtr->entry.selectLast		= -1;
950}
951
952static void
953EntryCleanup(void *recordPtr)
954{
955    Entry *entryPtr = recordPtr;
956
957    if (entryPtr->entry.textVariableTrace)
958	Ttk_UntraceVariable(entryPtr->entry.textVariableTrace);
959
960    TtkFreeScrollHandle(entryPtr->entry.xscrollHandle);
961
962    EntryFreeStyleDefaults(&entryPtr->entry.styleDefaults);
963
964    Tk_DeleteSelHandler(entryPtr->core.tkwin, XA_PRIMARY, XA_STRING);
965
966    Tk_FreeTextLayout(entryPtr->entry.textLayout);
967    if (entryPtr->entry.displayString != entryPtr->entry.string)
968	ckfree(entryPtr->entry.displayString);
969    ckfree(entryPtr->entry.string);
970}
971
972/* EntryConfigure --
973 * 	Configure hook for Entry widgets.
974 */
975static int EntryConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
976{
977    Entry *entryPtr = recordPtr;
978    Tcl_Obj *textVarName = entryPtr->entry.textVariableObj;
979    Ttk_TraceHandle *vt = 0;
980
981    if (mask & TEXTVAR_CHANGED) {
982	if (textVarName && *Tcl_GetString(textVarName)) {
983	    vt = Ttk_TraceVariable(interp,
984		    textVarName,EntryTextVariableTrace,entryPtr);
985	    if (!vt) return TCL_ERROR;
986	}
987    }
988
989    if (TtkCoreConfigure(interp, recordPtr, mask) != TCL_OK) {
990	if (vt) Ttk_UntraceVariable(vt);
991	return TCL_ERROR;
992    }
993
994    /* Update derived resources:
995     */
996    if (mask & TEXTVAR_CHANGED) {
997	if (entryPtr->entry.textVariableTrace)
998	    Ttk_UntraceVariable(entryPtr->entry.textVariableTrace);
999	entryPtr->entry.textVariableTrace = vt;
1000    }
1001
1002    /* Claim the selection, in case we've suddenly started exporting it.
1003     */
1004    if (entryPtr->entry.exportSelection && entryPtr->entry.selectFirst != -1) {
1005	EntryOwnSelection(entryPtr);
1006    }
1007
1008    /* Handle -state compatibility option:
1009     */
1010    if (mask & STATE_CHANGED) {
1011	TtkCheckStateOption(&entryPtr->core, entryPtr->entry.stateObj);
1012    }
1013
1014    /* Force scrollbar update if needed:
1015     */
1016    if (mask & SCROLLCMD_CHANGED) {
1017	TtkScrollbarUpdateRequired(entryPtr->entry.xscrollHandle);
1018    }
1019
1020    /* Recompute the displayString, in case showChar changed:
1021     */
1022    if (entryPtr->entry.displayString != entryPtr->entry.string)
1023	ckfree(entryPtr->entry.displayString);
1024
1025    entryPtr->entry.displayString
1026	= entryPtr->entry.showChar
1027	? EntryDisplayString(entryPtr->entry.showChar, entryPtr->entry.numChars)
1028	: entryPtr->entry.string
1029	;
1030
1031    /* Update textLayout:
1032     */
1033    EntryUpdateTextLayout(entryPtr);
1034    return TCL_OK;
1035}
1036
1037/* EntryPostConfigure --
1038 * 	Post-configuration hook for entry widgets.
1039 */
1040static int EntryPostConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
1041{
1042    Entry *entryPtr = recordPtr;
1043    int status = TCL_OK;
1044
1045    if ((mask & TEXTVAR_CHANGED) && entryPtr->entry.textVariableTrace != NULL) {
1046	status = Ttk_FireTrace(entryPtr->entry.textVariableTrace);
1047    }
1048
1049    return status;
1050}
1051
1052/*------------------------------------------------------------------------
1053 * +++ Layout and display.
1054 */
1055
1056/* EntryCharPosition --
1057 * 	Return the X coordinate of the specified character index.
1058 * 	Precondition: textLayout and layoutX up-to-date.
1059 */
1060static int
1061EntryCharPosition(Entry *entryPtr, int index)
1062{
1063    int xPos;
1064    Tk_CharBbox(entryPtr->entry.textLayout, index, &xPos, NULL, NULL, NULL);
1065    return xPos + entryPtr->entry.layoutX;
1066}
1067
1068/* EntryDoLayout --
1069 * 	Layout hook for entry widgets.
1070 *
1071 * 	Determine position of textLayout based on xscroll.first, justify,
1072 * 	and display area.
1073 *
1074 * 	Recalculates layoutX, layoutY, and rightIndex,
1075 * 	and updates xscroll accordingly.
1076 * 	May adjust xscroll.first to ensure the maximum #characters are onscreen.
1077 */
1078static void
1079EntryDoLayout(void *recordPtr)
1080{
1081    Entry *entryPtr = recordPtr;
1082    WidgetCore *corePtr = &entryPtr->core;
1083    Tk_TextLayout textLayout = entryPtr->entry.textLayout;
1084    int leftIndex = entryPtr->entry.xscroll.first;
1085    int rightIndex;
1086    Ttk_Box textarea;
1087
1088    Ttk_PlaceLayout(corePtr->layout,corePtr->state,Ttk_WinBox(corePtr->tkwin));
1089    textarea = Ttk_ClientRegion(corePtr->layout, "textarea");
1090
1091    /* Center the text vertically within the available parcel:
1092     */
1093    entryPtr->entry.layoutY = textarea.y +
1094	(textarea.height - entryPtr->entry.layoutHeight)/2;
1095
1096    /* Recompute where the leftmost character on the display will
1097     * be drawn (layoutX) and adjust leftIndex if necessary.
1098     */
1099    if (entryPtr->entry.layoutWidth <= textarea.width) {
1100	/* Everything fits.  Set leftIndex to zero (no need to scroll),
1101	 * and compute layoutX based on -justify.
1102	 */
1103	int extraSpace = textarea.width - entryPtr->entry.layoutWidth;
1104	leftIndex = 0;
1105	rightIndex = entryPtr->entry.numChars;
1106	entryPtr->entry.layoutX = textarea.x;
1107	if (entryPtr->entry.justify == TK_JUSTIFY_RIGHT) {
1108	    entryPtr->entry.layoutX += extraSpace;
1109	} else if (entryPtr->entry.justify == TK_JUSTIFY_CENTER) {
1110	    entryPtr->entry.layoutX += extraSpace / 2;
1111	}
1112    } else {
1113	/* The whole string doesn't fit in the window.
1114	 * Limit leftIndex to leave at most one character's worth
1115	 * of empty space on the right.
1116	 */
1117	int overflow = entryPtr->entry.layoutWidth - textarea.width;
1118	int maxLeftIndex = 1 + Tk_PointToChar(textLayout, overflow, 0);
1119	int leftX;
1120
1121	if (leftIndex > maxLeftIndex) {
1122	    leftIndex = maxLeftIndex;
1123	}
1124
1125	/* Compute layoutX and rightIndex.
1126	 * rightIndex is set to one past the last fully-visible character.
1127	 */
1128	Tk_CharBbox(textLayout, leftIndex, &leftX, NULL, NULL, NULL);
1129	rightIndex = Tk_PointToChar(textLayout, leftX + textarea.width, 0);
1130	entryPtr->entry.layoutX = textarea.x - leftX;
1131    }
1132
1133    TtkScrolled(entryPtr->entry.xscrollHandle,
1134	    leftIndex, rightIndex, entryPtr->entry.numChars);
1135}
1136
1137/* EntryGetGC -- Helper routine.
1138 *      Get a GC using the specified foreground color and the entry's font.
1139 *      Result must be freed with Tk_FreeGC().
1140 */
1141static GC EntryGetGC(Entry *entryPtr, Tcl_Obj *colorObj)
1142{
1143    Tk_Window tkwin = entryPtr->core.tkwin;
1144    Tk_Font font = Tk_GetFontFromObj(tkwin, entryPtr->entry.fontObj);
1145    XColor *colorPtr;
1146    unsigned long mask = 0ul;
1147    XGCValues gcValues;
1148
1149    gcValues.line_width = 1; mask |= GCLineWidth;
1150    gcValues.font = Tk_FontId(font); mask |= GCFont;
1151    if (colorObj != 0 && (colorPtr=Tk_GetColorFromObj(tkwin,colorObj)) != 0) {
1152	gcValues.foreground = colorPtr->pixel;
1153	mask |= GCForeground;
1154    }
1155    return Tk_GetGC(entryPtr->core.tkwin, mask, &gcValues);
1156}
1157
1158/* EntryDisplay --
1159 *	Redraws the contents of an entry window.
1160 */
1161static void EntryDisplay(void *clientData, Drawable d)
1162{
1163    Entry *entryPtr = clientData;
1164    Tk_Window tkwin = entryPtr->core.tkwin;
1165    int leftIndex = entryPtr->entry.xscroll.first,
1166	rightIndex = entryPtr->entry.xscroll.last,
1167	selFirst = entryPtr->entry.selectFirst,
1168	selLast = entryPtr->entry.selectLast;
1169    EntryStyleData es;
1170    GC gc;
1171    int showSelection, showCursor;
1172
1173    EntryInitStyleData(entryPtr, &es);
1174
1175    showCursor =
1176	   (entryPtr->core.flags & CURSOR_ON) != 0
1177	&& EntryEditable(entryPtr)
1178	&& entryPtr->entry.insertPos >= leftIndex
1179	&& entryPtr->entry.insertPos <= rightIndex
1180	;
1181    showSelection =
1182	   (entryPtr->core.state & TTK_STATE_DISABLED) == 0
1183	&& selFirst > -1
1184	&& selLast > leftIndex
1185	&& selFirst <= rightIndex
1186	;
1187
1188    /* Adjust selection range to keep in display bounds.
1189     */
1190    if (showSelection) {
1191	if (selFirst < leftIndex)
1192	    selFirst = leftIndex;
1193	if (selLast > rightIndex)
1194	    selLast = rightIndex;
1195    }
1196
1197    /* Draw widget background & border
1198     */
1199    Ttk_DrawLayout(entryPtr->core.layout, entryPtr->core.state, d);
1200
1201    /* Draw selection background
1202     */
1203    if (showSelection && es.selBorderObj) {
1204	Tk_3DBorder selBorder = Tk_Get3DBorderFromObj(tkwin, es.selBorderObj);
1205	int selStartX = EntryCharPosition(entryPtr, selFirst);
1206	int selEndX = EntryCharPosition(entryPtr, selLast);
1207	int borderWidth = 1;
1208
1209	Tcl_GetIntFromObj(NULL, es.selBorderWidthObj, &borderWidth);
1210
1211	if (selBorder) {
1212	    Tk_Fill3DRectangle(tkwin, d, selBorder,
1213		selStartX - borderWidth, entryPtr->entry.layoutY - borderWidth,
1214		selEndX - selStartX + 2*borderWidth,
1215		entryPtr->entry.layoutHeight + 2*borderWidth,
1216		borderWidth, TK_RELIEF_RAISED);
1217	}
1218    }
1219
1220    /* Draw cursor:
1221     */
1222    if (showCursor) {
1223	int cursorX = EntryCharPosition(entryPtr, entryPtr->entry.insertPos),
1224	    cursorY = entryPtr->entry.layoutY,
1225	    cursorHeight = entryPtr->entry.layoutHeight,
1226	    cursorWidth = 1;
1227
1228	Tcl_GetIntFromObj(NULL,es.insertWidthObj,&cursorWidth);
1229	if (cursorWidth <= 0) {
1230	    cursorWidth = 1;
1231	}
1232
1233	/* @@@ should: maybe: SetCaretPos even when blinked off */
1234	Tk_SetCaretPos(tkwin, cursorX, cursorY, cursorHeight);
1235
1236	gc = EntryGetGC(entryPtr, es.insertColorObj);
1237	XFillRectangle(Tk_Display(tkwin), d, gc,
1238	    cursorX-cursorWidth/2, cursorY, cursorWidth, cursorHeight);
1239	Tk_FreeGC(Tk_Display(tkwin), gc);
1240    }
1241
1242    /* Draw the text:
1243     */
1244    gc = EntryGetGC(entryPtr, es.foregroundObj);
1245    Tk_DrawTextLayout(
1246	Tk_Display(tkwin), d, gc, entryPtr->entry.textLayout,
1247	entryPtr->entry.layoutX, entryPtr->entry.layoutY,
1248	leftIndex, rightIndex);
1249    Tk_FreeGC(Tk_Display(tkwin), gc);
1250
1251    /* Overwrite the selected portion (if any) in the -selectforeground color:
1252     */
1253    if (showSelection) {
1254	gc = EntryGetGC(entryPtr, es.selForegroundObj);
1255	Tk_DrawTextLayout(
1256	    Tk_Display(tkwin), d, gc, entryPtr->entry.textLayout,
1257	    entryPtr->entry.layoutX, entryPtr->entry.layoutY,
1258	    selFirst, selLast);
1259	Tk_FreeGC(Tk_Display(tkwin), gc);
1260    }
1261}
1262
1263/*------------------------------------------------------------------------
1264 * +++ Widget commands.
1265 */
1266
1267/* EntryIndex --
1268 *	Parse an index into an entry and return either its value
1269 *	or an error.
1270 *
1271 * Results:
1272 *	A standard Tcl result.  If all went well, then *indexPtr is
1273 *	filled in with the character index (into entryPtr) corresponding to
1274 *	string.  The index value is guaranteed to lie between 0 and
1275 *	the number of characters in the string, inclusive.  If an
1276 *	error occurs then an error message is left in the interp's result.
1277 */
1278static int
1279EntryIndex(
1280    Tcl_Interp *interp,		/* For error messages. */
1281    Entry *entryPtr,		/* Entry widget to query */
1282    Tcl_Obj *indexObj,		/* Symbolic index name */
1283    int *indexPtr)		/* Return value */
1284{
1285#   define EntryWidth(e) (Tk_Width(entryPtr->core.tkwin)) /* Not Right */
1286    int length;
1287    const char *string = Tcl_GetStringFromObj(indexObj, &length);
1288
1289    if (strncmp(string, "end", length) == 0) {
1290	*indexPtr = entryPtr->entry.numChars;
1291    } else if (strncmp(string, "insert", length) == 0) {
1292	*indexPtr = entryPtr->entry.insertPos;
1293    } else if (strncmp(string, "left", length) == 0) {	/* for debugging */
1294	*indexPtr = entryPtr->entry.xscroll.first;
1295    } else if (strncmp(string, "right", length) == 0) {	/* for debugging */
1296	*indexPtr = entryPtr->entry.xscroll.last;
1297    } else if (strncmp(string, "sel.", 4) == 0) {
1298	if (entryPtr->entry.selectFirst < 0) {
1299	    Tcl_ResetResult(interp);
1300	    Tcl_AppendResult(interp, "selection isn't in widget ",
1301		    Tk_PathName(entryPtr->core.tkwin), NULL);
1302	    return TCL_ERROR;
1303	}
1304	if (strncmp(string, "sel.first", length) == 0) {
1305	    *indexPtr = entryPtr->entry.selectFirst;
1306	} else if (strncmp(string, "sel.last", length) == 0) {
1307	    *indexPtr = entryPtr->entry.selectLast;
1308	} else {
1309	    goto badIndex;
1310	}
1311    } else if (string[0] == '@') {
1312	int roundUp = 0;
1313	int maxWidth = EntryWidth(entryPtr);
1314	int x;
1315
1316	if (Tcl_GetInt(interp, string + 1, &x) != TCL_OK) {
1317	    goto badIndex;
1318	}
1319	if (x > maxWidth) {
1320	    x = maxWidth;
1321	    roundUp = 1;
1322	}
1323	*indexPtr = Tk_PointToChar(entryPtr->entry.textLayout,
1324		x - entryPtr->entry.layoutX, 0);
1325
1326	if (*indexPtr < entryPtr->entry.xscroll.first) {
1327	    *indexPtr = entryPtr->entry.xscroll.first;
1328	}
1329
1330	/*
1331	 * Special trick:  if the x-position was off-screen to the right,
1332	 * round the index up to refer to the character just after the
1333	 * last visible one on the screen.  This is needed to enable the
1334	 * last character to be selected, for example.
1335	 */
1336
1337	if (roundUp && (*indexPtr < entryPtr->entry.numChars)) {
1338	    *indexPtr += 1;
1339	}
1340    } else {
1341	if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) {
1342	    goto badIndex;
1343	}
1344	if (*indexPtr < 0) {
1345	    *indexPtr = 0;
1346	} else if (*indexPtr > entryPtr->entry.numChars) {
1347	    *indexPtr = entryPtr->entry.numChars;
1348	}
1349    }
1350    return TCL_OK;
1351
1352badIndex:
1353    Tcl_ResetResult(interp);
1354    Tcl_AppendResult(interp, "bad entry index \"", string, "\"", NULL);
1355    return TCL_ERROR;
1356}
1357
1358/* $entry bbox $index --
1359 * 	Return the bounding box of the character at the specified index.
1360 */
1361static int
1362EntryBBoxCommand(
1363    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1364{
1365    Entry *entryPtr = recordPtr;
1366    Ttk_Box b;
1367    int index;
1368
1369    if (objc != 3) {
1370	Tcl_WrongNumArgs(interp, 2, objv, "index");
1371	return TCL_ERROR;
1372    }
1373    if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) {
1374	return TCL_ERROR;
1375    }
1376    if ((index == entryPtr->entry.numChars) && (index > 0)) {
1377	index--;
1378    }
1379    Tk_CharBbox(entryPtr->entry.textLayout, index,
1380	    &b.x, &b.y, &b.width, &b.height);
1381    b.x += entryPtr->entry.layoutX;
1382    b.y += entryPtr->entry.layoutY;
1383    Tcl_SetObjResult(interp, Ttk_NewBoxObj(b));
1384    return TCL_OK;
1385}
1386
1387/* $entry delete $from ?$to? --
1388 *	Delete the characters in the range [$from,$to).
1389 *	$to defaults to $from+1 if not specified.
1390 */
1391static int
1392EntryDeleteCommand(
1393    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1394{
1395    Entry *entryPtr = recordPtr;
1396    int first, last;
1397
1398    if ((objc < 3) || (objc > 4)) {
1399	Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
1400	return TCL_ERROR;
1401    }
1402    if (EntryIndex(interp, entryPtr, objv[2], &first) != TCL_OK) {
1403	return TCL_ERROR;
1404    }
1405    if (objc == 3) {
1406	last = first + 1;
1407    } else if (EntryIndex(interp, entryPtr, objv[3], &last) != TCL_OK) {
1408	return TCL_ERROR;
1409    }
1410
1411    if (last >= first && EntryEditable(entryPtr)) {
1412	return DeleteChars(entryPtr, first, last - first);
1413    }
1414    return TCL_OK;
1415}
1416
1417/* $entry get --
1418 * 	Return the current value of the entry widget.
1419 */
1420static int
1421EntryGetCommand(
1422    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1423{
1424    Entry *entryPtr = recordPtr;
1425    if (objc != 2) {
1426	Tcl_WrongNumArgs(interp, 2, objv, NULL);
1427	return TCL_ERROR;
1428    }
1429    Tcl_SetResult(interp, entryPtr->entry.string, TCL_VOLATILE);
1430    return TCL_OK;
1431}
1432
1433/* $entry icursor $index --
1434 * 	Set the insert cursor position.
1435 */
1436static int
1437EntryICursorCommand(
1438    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1439{
1440    Entry *entryPtr = recordPtr;
1441    if (objc != 3) {
1442	Tcl_WrongNumArgs(interp, 2, objv, "pos");
1443	return TCL_ERROR;
1444    }
1445    if (EntryIndex(interp, entryPtr, objv[2],
1446	    &entryPtr->entry.insertPos) != TCL_OK) {
1447	return TCL_ERROR;
1448    }
1449    TtkRedisplayWidget(&entryPtr->core);
1450    return TCL_OK;
1451}
1452
1453/* $entry index $index --
1454 * 	Return numeric value (0..numChars) of the specified index.
1455 */
1456static int
1457EntryIndexCommand(
1458    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1459{
1460    Entry *entryPtr = recordPtr;
1461    int index;
1462
1463    if (objc != 3) {
1464	Tcl_WrongNumArgs(interp, 2, objv, "string");
1465	return TCL_ERROR;
1466    }
1467    if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) {
1468	return TCL_ERROR;
1469    }
1470    Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
1471    return TCL_OK;
1472}
1473
1474/* $entry insert $index $text --
1475 * 	Insert $text after position $index.
1476 * 	Silent no-op if the entry is disabled or read-only.
1477 */
1478static int
1479EntryInsertCommand(
1480    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1481{
1482    Entry *entryPtr = recordPtr;
1483    int index;
1484
1485    if (objc != 4) {
1486	Tcl_WrongNumArgs(interp, 2, objv, "index text");
1487	return TCL_ERROR;
1488    }
1489    if (EntryIndex(interp, entryPtr, objv[2], &index) != TCL_OK) {
1490	return TCL_ERROR;
1491    }
1492    if (EntryEditable(entryPtr)) {
1493	return InsertChars(entryPtr, index, Tcl_GetString(objv[3]));
1494    }
1495    return TCL_OK;
1496}
1497
1498/* $entry selection clear --
1499 * 	Clear selection.
1500 */
1501static int EntrySelectionClearCommand(
1502    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1503{
1504    Entry *entryPtr = recordPtr;
1505
1506    if (objc != 3) {
1507	Tcl_WrongNumArgs(interp, 3, objv, NULL);
1508	return TCL_ERROR;
1509    }
1510    entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1;
1511    TtkRedisplayWidget(&entryPtr->core);
1512    return TCL_OK;
1513}
1514
1515/* $entry selection present --
1516 * 	Returns 1 if any characters are selected, 0 otherwise.
1517 */
1518static int EntrySelectionPresentCommand(
1519    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1520{
1521    Entry *entryPtr = recordPtr;
1522    if (objc != 3) {
1523	Tcl_WrongNumArgs(interp, 3, objv, NULL);
1524	return TCL_ERROR;
1525    }
1526    Tcl_SetObjResult(interp,
1527	    Tcl_NewBooleanObj(entryPtr->entry.selectFirst >= 0));
1528    return TCL_OK;
1529}
1530
1531/* $entry selection range $start $end --
1532 * 	Explicitly set the selection range.
1533 */
1534static int EntrySelectionRangeCommand(
1535    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1536{
1537    Entry *entryPtr = recordPtr;
1538    int start, end;
1539    if (objc != 5) {
1540	Tcl_WrongNumArgs(interp, 3, objv, "start end");
1541	return TCL_ERROR;
1542    }
1543    if (    EntryIndex(interp, entryPtr, objv[3], &start) != TCL_OK
1544         || EntryIndex(interp, entryPtr, objv[4], &end) != TCL_OK) {
1545	return TCL_ERROR;
1546    }
1547    if (entryPtr->core.state & TTK_STATE_DISABLED) {
1548	return TCL_OK;
1549    }
1550
1551    if (start >= end) {
1552	entryPtr->entry.selectFirst = entryPtr->entry.selectLast = -1;
1553    } else {
1554	entryPtr->entry.selectFirst = start;
1555	entryPtr->entry.selectLast = end;
1556	EntryOwnSelection(entryPtr);
1557    }
1558    TtkRedisplayWidget(&entryPtr->core);
1559    return TCL_OK;
1560}
1561
1562static const Ttk_Ensemble EntrySelectionCommands[] = {
1563    { "clear",   EntrySelectionClearCommand,0 },
1564    { "present", EntrySelectionPresentCommand,0 },
1565    { "range",   EntrySelectionRangeCommand,0 },
1566    { 0,0,0 }
1567};
1568
1569/* $entry set $value
1570 * 	Sets the value of an entry widget.
1571 */
1572static int EntrySetCommand(
1573    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1574{
1575    Entry *entryPtr = recordPtr;
1576    if (objc != 3) {
1577	Tcl_WrongNumArgs(interp, 2, objv, "value");
1578	return TCL_ERROR;
1579    }
1580    EntrySetValue(entryPtr, Tcl_GetString(objv[2]));
1581    return TCL_OK;
1582}
1583
1584/* $entry validate --
1585 * 	Trigger forced validation.  Returns 1/0 if validation succeeds/fails
1586 * 	or error status from -validatecommand / -invalidcommand.
1587 */
1588static int EntryValidateCommand(
1589    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1590{
1591    Entry *entryPtr = recordPtr;
1592    int code;
1593
1594    if (objc != 2) {
1595	Tcl_WrongNumArgs(interp, 2, objv, NULL);
1596	return TCL_ERROR;
1597    }
1598
1599    code = EntryRevalidate(interp, entryPtr, VALIDATE_FORCED);
1600
1601    if (code == TCL_ERROR)
1602	return code;
1603
1604    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(code == TCL_OK));
1605    return TCL_OK;
1606}
1607
1608/* $entry xview	-- horizontal scrolling interface
1609 */
1610static int EntryXViewCommand(
1611    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1612{
1613    Entry *entryPtr = recordPtr;
1614    return TtkScrollviewCommand(interp, objc, objv, entryPtr->entry.xscrollHandle);
1615}
1616
1617static const Ttk_Ensemble EntryCommands[] = {
1618    { "bbox", 		EntryBBoxCommand,0 },
1619    { "cget", 		TtkWidgetCgetCommand,0 },
1620    { "configure", 	TtkWidgetConfigureCommand,0 },
1621    { "delete", 	EntryDeleteCommand,0 },
1622    { "get", 		EntryGetCommand,0 },
1623    { "icursor", 	EntryICursorCommand,0 },
1624    { "identify",	TtkWidgetIdentifyCommand,0 },
1625    { "index", 		EntryIndexCommand,0 },
1626    { "insert", 	EntryInsertCommand,0 },
1627    { "instate",	TtkWidgetInstateCommand,0 },
1628    { "selection", 	0,EntrySelectionCommands },
1629    { "state",  	TtkWidgetStateCommand,0 },
1630    { "validate", 	EntryValidateCommand,0 },
1631    { "xview", 		EntryXViewCommand,0 },
1632    { 0,0,0 }
1633};
1634
1635/*------------------------------------------------------------------------
1636 * +++ Entry widget definition.
1637 */
1638
1639static WidgetSpec EntryWidgetSpec = {
1640    "TEntry",			/* className */
1641    sizeof(Entry), 		/* recordSize */
1642    EntryOptionSpecs, 		/* optionSpecs */
1643    EntryCommands,  		/* subcommands */
1644    EntryInitialize,     	/* initializeProc */
1645    EntryCleanup,		/* cleanupProc */
1646    EntryConfigure,		/* configureProc */
1647    EntryPostConfigure,  	/* postConfigureProc */
1648    TtkWidgetGetLayout, 	/* getLayoutProc */
1649    TtkWidgetSize, 		/* sizeProc */
1650    EntryDoLayout,		/* layoutProc */
1651    EntryDisplay		/* displayProc */
1652};
1653
1654/*------------------------------------------------------------------------
1655 * +++ Combobox widget record.
1656 */
1657
1658typedef struct {
1659    Tcl_Obj	*postCommandObj;
1660    Tcl_Obj	*valuesObj;
1661    Tcl_Obj	*heightObj;
1662    int 	currentIndex;
1663} ComboboxPart;
1664
1665typedef struct {
1666    WidgetCore core;
1667    EntryPart entry;
1668    ComboboxPart combobox;
1669} Combobox;
1670
1671static Tk_OptionSpec ComboboxOptionSpecs[] = {
1672    {TK_OPTION_STRING, "-height", "height", "Height",
1673        DEF_LIST_HEIGHT, Tk_Offset(Combobox, combobox.heightObj), -1,
1674	0,0,0 },
1675    {TK_OPTION_STRING, "-postcommand", "postCommand", "PostCommand",
1676        "", Tk_Offset(Combobox, combobox.postCommandObj), -1,
1677	0,0,0 },
1678    {TK_OPTION_STRING, "-values", "values", "Values",
1679        "", Tk_Offset(Combobox, combobox.valuesObj), -1,
1680	0,0,0 },
1681    WIDGET_INHERIT_OPTIONS(EntryOptionSpecs)
1682};
1683
1684/* ComboboxInitialize --
1685 * 	Initialization hook for combobox widgets.
1686 */
1687static void
1688ComboboxInitialize(Tcl_Interp *interp, void *recordPtr)
1689{
1690    Combobox *cb = recordPtr;
1691
1692    cb->combobox.currentIndex = -1;
1693    TtkTrackElementState(&cb->core);
1694    EntryInitialize(interp, recordPtr);
1695}
1696
1697/* ComboboxConfigure --
1698 * 	Configuration hook for combobox widgets.
1699 */
1700static int
1701ComboboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
1702{
1703    Combobox *cbPtr = recordPtr;
1704    int unused;
1705
1706    /* Make sure -values is a valid list:
1707     */
1708    if (Tcl_ListObjLength(interp,cbPtr->combobox.valuesObj,&unused) != TCL_OK)
1709	return TCL_ERROR;
1710
1711    return EntryConfigure(interp, recordPtr, mask);
1712}
1713
1714/* $cb current ?newIndex? -- get or set current index.
1715 * 	Setting the current index updates the combobox value,
1716 * 	but the value and -values may be changed independently
1717 * 	of the index.  Instead of trying to keep currentIndex
1718 * 	in sync at all times, [$cb current] double-checks
1719 */
1720static int ComboboxCurrentCommand(
1721    void *recordPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
1722{
1723    Combobox *cbPtr = recordPtr;
1724    int currentIndex = cbPtr->combobox.currentIndex;
1725    const char *currentValue = cbPtr->entry.string;
1726    int nValues;
1727    Tcl_Obj **values;
1728
1729    Tcl_ListObjGetElements(interp,cbPtr->combobox.valuesObj,&nValues,&values);
1730
1731    if (objc == 2) {
1732	/* Check if currentIndex still valid:
1733	 */
1734	if (    currentIndex < 0
1735	     || currentIndex >= nValues
1736	     || strcmp(currentValue,Tcl_GetString(values[currentIndex]))
1737	   )
1738	{
1739	    /* Not valid.  Check current value against each element in -values:
1740	     */
1741	    for (currentIndex = 0; currentIndex < nValues; ++currentIndex) {
1742		if (!strcmp(currentValue,Tcl_GetString(values[currentIndex]))) {
1743		    break;
1744		}
1745	    }
1746	    if (currentIndex >= nValues) {
1747		/* Not found */
1748		currentIndex = -1;
1749	    }
1750	}
1751	cbPtr->combobox.currentIndex = currentIndex;
1752	Tcl_SetObjResult(interp, Tcl_NewIntObj(currentIndex));
1753	return TCL_OK;
1754    } else if (objc == 3) {
1755	if (Tcl_GetIntFromObj(interp, objv[2], &currentIndex) != TCL_OK) {
1756	    return TCL_ERROR;
1757	}
1758	if (currentIndex < 0 || currentIndex >= nValues) {
1759	    Tcl_AppendResult(interp,
1760		    "Index ", Tcl_GetString(objv[2]), " out of range",
1761		    NULL);
1762	    return TCL_ERROR;
1763	}
1764
1765	cbPtr->combobox.currentIndex = currentIndex;
1766
1767	return EntrySetValue(recordPtr, Tcl_GetString(values[currentIndex]));
1768    } else {
1769	Tcl_WrongNumArgs(interp, 2, objv, "?newIndex?");
1770	return TCL_ERROR;
1771    }
1772    return TCL_OK;
1773}
1774
1775/*------------------------------------------------------------------------
1776 * +++ Combobox widget definition.
1777 */
1778static const Ttk_Ensemble ComboboxCommands[] = {
1779    { "bbox", 		EntryBBoxCommand,0 },
1780    { "cget", 		TtkWidgetCgetCommand,0 },
1781    { "configure", 	TtkWidgetConfigureCommand,0 },
1782    { "current", 	ComboboxCurrentCommand,0 },
1783    { "delete", 	EntryDeleteCommand,0 },
1784    { "get", 		EntryGetCommand,0 },
1785    { "icursor", 	EntryICursorCommand,0 },
1786    { "identify",	TtkWidgetIdentifyCommand,0 },
1787    { "index", 		EntryIndexCommand,0 },
1788    { "insert", 	EntryInsertCommand,0 },
1789    { "instate",	TtkWidgetInstateCommand,0 },
1790    { "selection", 	0,EntrySelectionCommands },
1791    { "state",  	TtkWidgetStateCommand,0 },
1792    { "set", 		EntrySetCommand,0 },
1793    { "xview", 		EntryXViewCommand,0 },
1794    { 0,0,0 }
1795};
1796
1797static WidgetSpec ComboboxWidgetSpec = {
1798    "TCombobox",		/* className */
1799    sizeof(Combobox), 		/* recordSize */
1800    ComboboxOptionSpecs,	/* optionSpecs */
1801    ComboboxCommands,  		/* subcommands */
1802    ComboboxInitialize,     	/* initializeProc */
1803    EntryCleanup,		/* cleanupProc */
1804    ComboboxConfigure,		/* configureProc */
1805    EntryPostConfigure,  	/* postConfigureProc */
1806    TtkWidgetGetLayout, 	/* getLayoutProc */
1807    TtkWidgetSize, 		/* sizeProc */
1808    EntryDoLayout,		/* layoutProc */
1809    EntryDisplay		/* displayProc */
1810};
1811
1812/*------------------------------------------------------------------------
1813 * +++ Spinbox widget.
1814 */
1815
1816typedef struct {
1817    Tcl_Obj	*valuesObj;
1818
1819    Tcl_Obj	*fromObj;
1820    Tcl_Obj	*toObj;
1821    Tcl_Obj	*incrementObj;
1822    Tcl_Obj	*formatObj;
1823
1824    Tcl_Obj	*wrapObj;
1825    Tcl_Obj	*commandObj;
1826} SpinboxPart;
1827
1828typedef struct {
1829    WidgetCore core;
1830    EntryPart entry;
1831    SpinboxPart spinbox;
1832} Spinbox;
1833
1834static Tk_OptionSpec SpinboxOptionSpecs[] = {
1835    {TK_OPTION_STRING, "-values", "values", "Values",
1836        "", Tk_Offset(Spinbox, spinbox.valuesObj), -1,
1837	0,0,0 },
1838
1839    {TK_OPTION_DOUBLE, "-from", "from", "From",
1840	"0", Tk_Offset(Spinbox,spinbox.fromObj), -1,
1841	0,0,0 },
1842    {TK_OPTION_DOUBLE, "-to", "to", "To",
1843	"0", Tk_Offset(Spinbox,spinbox.toObj), -1,
1844	0,0,0 },
1845    {TK_OPTION_DOUBLE, "-increment", "increment", "Increment",
1846	"1", Tk_Offset(Spinbox,spinbox.incrementObj), -1,
1847	0,0,0 },
1848    {TK_OPTION_STRING, "-format", "format", "Format",
1849	"", Tk_Offset(Spinbox, spinbox.formatObj), -1,
1850	0,0,0 },
1851
1852    {TK_OPTION_STRING, "-command", "command", "Command",
1853	"", Tk_Offset(Spinbox, spinbox.commandObj), -1,
1854	0,0,0 },
1855    {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap",
1856	"0", Tk_Offset(Spinbox,spinbox.wrapObj), -1,
1857	0,0,0 },
1858
1859    WIDGET_INHERIT_OPTIONS(EntryOptionSpecs)
1860};
1861
1862/* SpinboxInitialize --
1863 * 	Initialization hook for spinbox widgets.
1864 */
1865static void
1866SpinboxInitialize(Tcl_Interp *interp, void *recordPtr)
1867{
1868    Spinbox *sb = recordPtr;
1869    TtkTrackElementState(&sb->core);
1870    EntryInitialize(interp, recordPtr);
1871}
1872
1873/* SpinboxConfigure --
1874 * 	Configuration hook for spinbox widgets.
1875 */
1876static int
1877SpinboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask)
1878{
1879    Spinbox *sb = recordPtr;
1880    int unused;
1881
1882    /* Make sure -values is a valid list:
1883     */
1884    if (Tcl_ListObjLength(interp,sb->spinbox.valuesObj,&unused) != TCL_OK)
1885	return TCL_ERROR;
1886
1887    return EntryConfigure(interp, recordPtr, mask);
1888}
1889
1890static const Ttk_Ensemble SpinboxCommands[] = {
1891    { "bbox", 		EntryBBoxCommand,0 },
1892    { "cget", 		TtkWidgetCgetCommand,0 },
1893    { "configure", 	TtkWidgetConfigureCommand,0 },
1894    { "delete", 	EntryDeleteCommand,0 },
1895    { "get", 		EntryGetCommand,0 },
1896    { "icursor", 	EntryICursorCommand,0 },
1897    { "identify",	TtkWidgetIdentifyCommand,0 },
1898    { "index", 		EntryIndexCommand,0 },
1899    { "insert", 	EntryInsertCommand,0 },
1900    { "instate",	TtkWidgetInstateCommand,0 },
1901    { "selection", 	0,EntrySelectionCommands },
1902    { "state",  	TtkWidgetStateCommand,0 },
1903    { "set", 		EntrySetCommand,0 },
1904    { "validate",	EntryValidateCommand,0 },
1905    { "xview", 		EntryXViewCommand,0 },
1906    { 0,0,0 }
1907};
1908
1909static WidgetSpec SpinboxWidgetSpec = {
1910    "TSpinbox",			/* className */
1911    sizeof(Spinbox), 		/* recordSize */
1912    SpinboxOptionSpecs,		/* optionSpecs */
1913    SpinboxCommands,  		/* subcommands */
1914    SpinboxInitialize,     	/* initializeProc */
1915    EntryCleanup,		/* cleanupProc */
1916    SpinboxConfigure,		/* configureProc */
1917    EntryPostConfigure,  	/* postConfigureProc */
1918    TtkWidgetGetLayout, 	/* getLayoutProc */
1919    TtkWidgetSize, 		/* sizeProc */
1920    EntryDoLayout,		/* layoutProc */
1921    EntryDisplay		/* displayProc */
1922};
1923
1924/*------------------------------------------------------------------------
1925 * +++ Textarea element.
1926 *
1927 * Text display area for Entry widgets.
1928 * Just computes requested size; display is handled by the widget itself.
1929 */
1930
1931typedef struct {
1932    Tcl_Obj	*fontObj;
1933    Tcl_Obj	*widthObj;
1934} TextareaElement;
1935
1936static Ttk_ElementOptionSpec TextareaElementOptions[] = {
1937    { "-font", TK_OPTION_FONT,
1938	Tk_Offset(TextareaElement,fontObj), DEF_ENTRY_FONT },
1939    { "-width", TK_OPTION_INT,
1940	Tk_Offset(TextareaElement,widthObj), "20" },
1941    { NULL, 0, 0, NULL }
1942};
1943
1944static void TextareaElementSize(
1945    void *clientData, void *elementRecord, Tk_Window tkwin,
1946    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
1947{
1948    TextareaElement *textarea = elementRecord;
1949    Tk_Font font = Tk_GetFontFromObj(tkwin, textarea->fontObj);
1950    int avgWidth = Tk_TextWidth(font, "0", 1);
1951    Tk_FontMetrics fm;
1952    int prefWidth = 1;
1953
1954    Tk_GetFontMetrics(font, &fm);
1955    Tcl_GetIntFromObj(NULL, textarea->widthObj, &prefWidth);
1956    if (prefWidth <= 0)
1957	prefWidth = 1;
1958
1959    *heightPtr = fm.linespace;
1960    *widthPtr = prefWidth * avgWidth;
1961}
1962
1963static Ttk_ElementSpec TextareaElementSpec = {
1964    TK_STYLE_VERSION_2,
1965    sizeof(TextareaElement),
1966    TextareaElementOptions,
1967    TextareaElementSize,
1968    TtkNullElementDraw
1969};
1970
1971/*------------------------------------------------------------------------
1972 * +++ Widget layouts.
1973 */
1974
1975TTK_BEGIN_LAYOUT(EntryLayout)
1976    TTK_GROUP("Entry.field", TTK_FILL_BOTH|TTK_BORDER,
1977	TTK_GROUP("Entry.padding", TTK_FILL_BOTH,
1978	    TTK_NODE("Entry.textarea", TTK_FILL_BOTH)))
1979TTK_END_LAYOUT
1980
1981TTK_BEGIN_LAYOUT(ComboboxLayout)
1982    TTK_GROUP("Combobox.field", TTK_FILL_BOTH,
1983	TTK_NODE("Combobox.downarrow", TTK_PACK_RIGHT|TTK_FILL_Y)
1984	TTK_GROUP("Combobox.padding", TTK_FILL_BOTH|TTK_PACK_LEFT|TTK_EXPAND,
1985	    TTK_NODE("Combobox.textarea", TTK_FILL_BOTH)))
1986TTK_END_LAYOUT
1987
1988TTK_BEGIN_LAYOUT(SpinboxLayout)
1989     TTK_GROUP("Spinbox.field", TTK_PACK_TOP|TTK_FILL_X,
1990	 TTK_GROUP("null", TTK_PACK_RIGHT,
1991	     TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP|TTK_STICK_E)
1992	     TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM|TTK_STICK_E))
1993	 TTK_GROUP("Spinbox.padding", TTK_FILL_BOTH,
1994	     TTK_NODE("Spinbox.textarea", TTK_FILL_BOTH)))
1995TTK_END_LAYOUT
1996
1997/*------------------------------------------------------------------------
1998 * +++ Initialization.
1999 */
2000MODULE_SCOPE
2001void TtkEntry_Init(Tcl_Interp *interp)
2002{
2003    Ttk_Theme themePtr =  Ttk_GetDefaultTheme(interp);
2004
2005    Ttk_RegisterElement(interp, themePtr, "textarea", &TextareaElementSpec, 0);
2006
2007    Ttk_RegisterLayout(themePtr, "TEntry", EntryLayout);
2008    Ttk_RegisterLayout(themePtr, "TCombobox", ComboboxLayout);
2009    Ttk_RegisterLayout(themePtr, "TSpinbox", SpinboxLayout);
2010
2011    RegisterWidget(interp, "ttk::entry", &EntryWidgetSpec);
2012    RegisterWidget(interp, "ttk::combobox", &ComboboxWidgetSpec);
2013    RegisterWidget(interp, "ttk::spinbox", &SpinboxWidgetSpec);
2014}
2015
2016/*EOF*/
2017