diff options
Diffstat (limited to 'generic/tkEntry.c')
-rw-r--r-- | generic/tkEntry.c | 467 |
1 files changed, 462 insertions, 5 deletions
diff --git a/generic/tkEntry.c b/generic/tkEntry.c index 8ae1b06..ac0f288 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -11,12 +11,14 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkEntry.c,v 1.6 1999/11/10 02:56:24 hobbs Exp $ + * RCS: @(#) $Id: tkEntry.c,v 1.7 1999/12/14 06:52:27 hobbs Exp $ */ #include "tkInt.h" #include "default.h" +#define ENTRY_VALIDATE + /* * A data structure of the following type is kept for each entry * widget managed by this file: @@ -151,6 +153,16 @@ typedef struct { int avgWidth; /* Width of average character. */ int flags; /* Miscellaneous flags; see below for * definitions. */ + Tk_TSOffset tsoffset; + +#ifdef ENTRY_VALIDATE + char *validateCmd; /* Command prefix to use when invoking + * validate command. NULL means don't + * invoke commands. Malloc'ed. */ + int validate; /* Non-zero means try to validate */ + char *invalidCmd; /* Command called when a validation returns 0 + * (successfully fails), defaults to {}. */ +#endif /* ENTRY_VALIDATE */ } Entry; /* @@ -169,6 +181,11 @@ typedef struct { * UPDATE_SCROLLBAR: Non-zero means scrollbar should be updated * during next redisplay operation. * GOT_SELECTION: Non-zero means we've claimed the selection. + * VALIDATING: Non-zero means we are in a validateCmd + * VALIDATE_VAR: Non-zero means we are attempting to validate + * the entry's textvariable with validateCmd + * VALIDATE_ABORT: Non-zero if validatecommand signals an abort + * for current procedure and make no changes */ #define REDRAW_PENDING 1 @@ -178,6 +195,11 @@ typedef struct { #define UPDATE_SCROLLBAR 0x10 #define GOT_SELECTION 0x20 #define ENTRY_DELETED 0x40 +#ifdef ENTRY_VALIDATE +#define VALIDATING 0x80 +#define VALIDATE_VAR 0x100 +#define VALIDATE_ABORT 0x200 +#endif /* ENTRY_VALIDATE */ /* * The following macro defines how many extra pixels to leave on each @@ -201,6 +223,27 @@ static char *stateStrings[] = { "disabled", "normal", (char *) NULL }; +#ifdef ENTRY_VALIDATE +/* + * Definitions for -validate option values: + */ + +static char *validateStrings[] = { + "all", "key", "focus", "focusin", "focusout", "none", (char *) NULL +}; +enum validateType { + VALIDATE_ALL, VALIDATE_KEY, VALIDATE_FOCUS, + VALIDATE_FOCUSIN, VALIDATE_FOCUSOUT, VALIDATE_NONE, + /* + * These extra enums are for use with EntryValidateChange + */ + VALIDATE_FORCED, VALIDATE_DELETE, VALIDATE_INSERT +}; +#define DEF_ENTRY_VALIDATE "none" +#define DEF_ENTRY_INVALIDCMD "" + +#endif /* ENTRY_VALIDATE */ + /* * Information used for argv parsing. */ @@ -256,6 +299,13 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", DEF_ENTRY_INSERT_WIDTH, -1, Tk_Offset(Entry, insertWidth), 0, 0, 0}, +#ifdef ENTRY_VALIDATE + {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand", + DEF_ENTRY_INVALIDCMD, -1, Tk_Offset(Entry, invalidCmd), + 0, 0, 0}, + {TK_OPTION_SYNONYM, "-invcmd", (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-invalidcommand", 0}, +#endif /* ENTRY_VALIDATE */ {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify", DEF_ENTRY_JUSTIFY, -1, Tk_Offset(Entry, justify), 0, 0, 0}, {TK_OPTION_RELIEF, "-relief", "relief", "Relief", @@ -283,6 +333,16 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable", DEF_ENTRY_TEXT_VARIABLE, -1, Tk_Offset(Entry, textVarName), TK_CONFIG_NULL_OK, 0, 0}, +#ifdef ENTRY_VALIDATE + {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate", + DEF_ENTRY_VALIDATE, -1, Tk_Offset(Entry, validate), + 0, (ClientData) validateStrings, 0}, + {TK_OPTION_STRING, "-validatecommand", "validateCommand", "ValidateCommand", + (char *) NULL, -1, Tk_Offset(Entry, validateCmd), + TK_CONFIG_NULL_OK, 0, 0}, + {TK_OPTION_SYNONYM, "-vcmd", (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-validatecommand", 0}, +#endif /* ENTRY_VALIDATE */ {TK_OPTION_INT, "-width", "width", "Width", DEF_ENTRY_WIDTH, -1, Tk_Offset(Entry, prefWidth), 0, 0, 0}, {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", @@ -307,13 +367,21 @@ static Tk_OptionSpec optionSpecs[] = { static char *commandNames[] = { "bbox", "cget", "configure", "delete", "get", "icursor", "index", - "insert", "scan", "selection", "xview", (char *) NULL + "insert", "scan", "selection", +#ifdef ENTRY_VALIDATE + "validate", +#endif + "xview", (char *) NULL }; enum command { COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_DELETE, COMMAND_GET, COMMAND_ICURSOR, COMMAND_INDEX, COMMAND_INSERT, - COMMAND_SCAN, COMMAND_SELECTION, COMMAND_XVIEW + COMMAND_SCAN, COMMAND_SELECTION, +#ifdef ENTRY_VALIDATE + COMMAND_VALIDATE, +#endif + COMMAND_XVIEW }; static char *selCommandNames[] = { @@ -358,6 +426,15 @@ static char * EntryTextVarProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags)); static void EntryUpdateScrollbar _ANSI_ARGS_((Entry *entryPtr)); +#ifdef ENTRY_VALIDATE +static int EntryValidate _ANSI_ARGS_((Entry *entryPtr, + char *cmd)); +static int EntryValidateChange _ANSI_ARGS_((Entry *entryPtr, + char *change, char *new, int index, int type)); +static void ExpandPercents _ANSI_ARGS_((Entry *entryPtr, + char *before, char *change, char *new, int index, + int type, Tcl_DString *dsPtr)); +#endif /* ENTRY_VALIDATE */ static void EntryValueChanged _ANSI_ARGS_((Entry *entryPtr)); static void EntryVisibleRange _ANSI_ARGS_((Entry *entryPtr, double *firstPtr, double *lastPtr)); @@ -506,6 +583,11 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) entryPtr->highlightGC = None; entryPtr->avgWidth = 1; entryPtr->flags = 0; +#ifdef ENTRY_VALIDATE + entryPtr->validateCmd = NULL; + entryPtr->validate = VALIDATE_NONE; + entryPtr->invalidCmd = NULL; +#endif /* ENTRY_VALIDATE */ Tk_SetClass(entryPtr->tkwin, "Entry"); TkSetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr); @@ -888,6 +970,26 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) break; } +#ifdef ENTRY_VALIDATE + case COMMAND_VALIDATE: { + int code; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "validate"); + goto error; + } + selIndex = entryPtr->validate; + entryPtr->validate = VALIDATE_ALL; + code = EntryValidateChange(entryPtr, (char *) NULL, + entryPtr->string, -1, VALIDATE_FORCED); + if (entryPtr->validate != VALIDATE_NONE) { + entryPtr->validate = selIndex; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj((code == TCL_OK))); + break; + } +#endif + case COMMAND_XVIEW: { int index; @@ -1192,7 +1294,7 @@ EntryWorldChanged(instanceData) ClientData instanceData; /* Information about widget. */ { XGCValues gcValues; - GC gc; + GC gc = None; unsigned long mask; Entry *entryPtr; @@ -1203,6 +1305,10 @@ EntryWorldChanged(instanceData) entryPtr->avgWidth = 1; } + if (entryPtr->normalBorder != NULL) { + Tk_SetBackgroundFromBorder(entryPtr->tkwin, entryPtr->normalBorder); + } + gcValues.foreground = entryPtr->fgColorPtr->pixel; gcValues.font = Tk_FontId(entryPtr->tkfont); gcValues.graphics_exposures = False; @@ -1313,7 +1419,7 @@ DisplayEntry(clientData) */ Tk_Fill3DRectangle(tkwin, pixmap, entryPtr->normalBorder, - 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); + 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); string = entryPtr->displayString; if (showSelection @@ -1599,6 +1705,16 @@ InsertChars(entryPtr, index, value) strcpy(new + byteIndex, value); strcpy(new + byteIndex + byteCount, string + byteIndex); +#ifdef ENTRY_VALIDATE + if ((entryPtr->validate == VALIDATE_KEY || + entryPtr->validate == VALIDATE_ALL) && + EntryValidateChange(entryPtr, value, new, index, + VALIDATE_INSERT) != TCL_OK) { + ckfree(new); + return; + } +#endif /* ENTRY_VALIDATE */ + ckfree(string); entryPtr->string = new; @@ -1674,6 +1790,9 @@ DeleteChars(entryPtr, index, count) { int byteIndex, byteCount, newByteCount; char *new, *string; +#ifdef ENTRY_VALIDATE + char *todelete; +#endif if ((index + count) > entryPtr->numChars) { count = entryPtr->numChars - index; @@ -1691,6 +1810,23 @@ DeleteChars(entryPtr, index, count) memcpy(new, string, (size_t) byteIndex); strcpy(new + byteIndex, string + byteIndex + byteCount); +#ifdef ENTRY_VALIDATE + todelete = (char *) ckalloc((unsigned) (byteCount + 1)); + memcpy(todelete, string + byteIndex, (size_t) byteCount); + todelete[byteCount] = '\0'; + + if ((entryPtr->validate == VALIDATE_KEY || + entryPtr->validate == VALIDATE_ALL) && + EntryValidateChange(entryPtr, todelete, new, index, + VALIDATE_DELETE) != TCL_OK) { + ckfree(new); + ckfree(todelete); + return; + } + + ckfree(todelete); +#endif /* ENTRY_VALIDATE */ + ckfree(entryPtr->string); entryPtr->string = new; entryPtr->numChars -= count; @@ -1830,6 +1966,30 @@ EntrySetValue(entryPtr, value) char *value; /* New text to display in entry. */ { char *oldSource; +#ifdef ENTRY_VALIDATE + int code; + + if (strcmp(value, entryPtr->string) == 0) { + return; + } + + if (entryPtr->flags & VALIDATE_VAR) { + entryPtr->flags |= VALIDATE_ABORT; + } else { + entryPtr->flags |= VALIDATE_VAR; + code = EntryValidateChange(entryPtr, (char *) NULL, value, -1, + VALIDATE_FORCED); + entryPtr->flags &= ~VALIDATE_VAR; + /* + * If VALIDATE_ABORT has been set, then this operation should be + * aborted because the validatecommand did something else instead + */ + if (entryPtr->flags & VALIDATE_ABORT) { + entryPtr->flags &= ~VALIDATE_ABORT; + return; + } + } +#endif /* ENTRY_VALIDATE */ oldSource = entryPtr->string; @@ -2489,9 +2649,25 @@ EntryFocusProc(entryPtr, gotFocus) entryPtr->insertOnTime, EntryBlinkProc, (ClientData) entryPtr); } +#ifdef ENTRY_VALIDATE + if (entryPtr->validate == VALIDATE_ALL || + entryPtr->validate == VALIDATE_FOCUS || + entryPtr->validate == VALIDATE_FOCUSIN) { + EntryValidateChange(entryPtr, (char *) NULL, + entryPtr->string, -1, VALIDATE_FOCUSIN); + } +#endif /* ENTRY_VALIDATE */ } else { entryPtr->flags &= ~(GOT_FOCUS | CURSOR_ON); entryPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; +#ifdef ENTRY_VALIDATE + if (entryPtr->validate == VALIDATE_ALL || + entryPtr->validate == VALIDATE_FOCUS || + entryPtr->validate == VALIDATE_FOCUSOUT) { + EntryValidateChange(entryPtr, (char *) NULL, + entryPtr->string, -1, VALIDATE_FOCUSOUT); + } +#endif /* ENTRY_VALIDATE */ } EventuallyRedraw(entryPtr); } @@ -2553,8 +2729,289 @@ EntryTextVarProc(clientData, interp, name1, name2, flags) if (value == NULL) { value = ""; } +#ifdef ENTRY_VALIDATE + EntrySetValue(entryPtr, value); +#else if (strcmp(value, entryPtr->string) != 0) { EntrySetValue(entryPtr, value); } +#endif /* ENTRY_VALIDATE */ return (char *) NULL; } +#ifdef ENTRY_VALIDATE + +/* + *-------------------------------------------------------------- + * + * EntryValidate -- + * + * This procedure is invoked when any character is added or + * removed from the entry widget, or a focus has trigerred validation. + * + * Results: + * TCL_OK if the validatecommand passes the new string. + * TCL_BREAK if the vcmd executed OK, but rejects the string. + * TCL_ERROR if an error occurred while executing the vcmd + * or a valid Tcl_Bool is not returned. + * + * Side effects: + * An error condition may arise + * + *-------------------------------------------------------------- + */ + +static int +EntryValidate(entryPtr, cmd) + register Entry *entryPtr; /* Entry that needs validation. */ + register char *cmd; /* Validation command (NULL-terminated + * string). */ +{ + register Tcl_Interp *interp = entryPtr->interp; + int code, bool; + + code = Tcl_GlobalEval(interp, cmd); + + if (code != TCL_OK && code != TCL_RETURN) { + Tcl_AddErrorInfo(interp, + "\n\t(in validation command executed by entry)"); + Tcl_BackgroundError(interp); + return TCL_ERROR; + } + + if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp), + &bool) != TCL_OK) { + Tcl_AddErrorInfo(interp, + "\nValid Tcl Boolean not returned by validation command"); + Tcl_BackgroundError(interp); + Tcl_SetObjLength(Tcl_GetObjResult(interp), 0); + return TCL_ERROR; + } + + Tcl_SetObjLength(Tcl_GetObjResult(interp), 0); + return (bool ? TCL_OK : TCL_BREAK); +} + +/* + *-------------------------------------------------------------- + * + * EntryValidateChange -- + * + * This procedure is invoked when any character is added or + * removed from the entry widget, or a focus has trigerred validation. + * + * Results: + * TCL_OK if the validatecommand accepts the new string, + * TCL_ERROR if any problems occured with validatecommand. + * + * Side effects: + * The insertion/deletion may be aborted, and the + * validatecommand might turn itself off (if an error + * or loop condition arises). + * + *-------------------------------------------------------------- + */ + +static int +EntryValidateChange(entryPtr, change, new, index, type) + register Entry *entryPtr; /* Entry that needs validation. */ + char *change; /* Characters to be added/deleted + * (NULL-terminated string). */ + char *new; /* Potential new value of entry string */ + int index; /* index of insert/delete, -1 otherwise */ + int type; /* forced, delete, insert, + * focusin or focusout */ +{ + int code; + char *p; + Tcl_DString script; + + if (entryPtr->validateCmd == NULL || + entryPtr->validate == VALIDATE_NONE) { + return (entryPtr->flags & VALIDATE_VAR) ? TCL_ERROR : TCL_OK; + } + + /* + * If we're already validating, then we're hitting a loop condition + * Return and set validate to 0 to disallow further validations + * and prevent current validation from finishing + */ + if (entryPtr->flags & VALIDATING) { + entryPtr->validate = VALIDATE_NONE; + return (entryPtr->flags & VALIDATE_VAR) ? TCL_ERROR : TCL_OK; + } + + entryPtr->flags |= VALIDATING; + + /* + * Now form command string and run through the -validatecommand + */ + + Tcl_DStringInit(&script); + ExpandPercents(entryPtr, entryPtr->validateCmd, + change, new, index, type, &script); + Tcl_DStringAppend(&script, "", 1); + + p = Tcl_DStringValue(&script); + code = EntryValidate(entryPtr, p); + Tcl_DStringFree(&script); + + /* + * If e->validate has become VALIDATE_NONE during the validation, + * it means that a loop condition almost occured. Do not allow + * this validation result to finish. + */ + if (entryPtr->validate == VALIDATE_NONE || + (entryPtr->flags & VALIDATE_VAR)) { + code = TCL_ERROR; + } + /* + * If validate will return ERROR, then disallow further validations + * Otherwise, if it didn't accept the new string (returned TCL_BREAK) + * then eval the invalidCmd (if it's set) + */ + if (code == TCL_ERROR) { + entryPtr->validate = VALIDATE_NONE; + } else if (code == TCL_BREAK) { + if (entryPtr->invalidCmd != NULL) { + Tcl_DStringInit(&script); + ExpandPercents(entryPtr, entryPtr->invalidCmd, + change, new, index, type, &script); + Tcl_DStringAppend(&script, "", 1); + p = Tcl_DStringValue(&script); + if (Tcl_GlobalEval(entryPtr->interp, p) != TCL_OK) { + Tcl_AddErrorInfo(entryPtr->interp, + "\n\t(in invalidcommand executed by entry)"); + Tcl_BackgroundError(entryPtr->interp); + code = TCL_ERROR; + entryPtr->validate = VALIDATE_NONE; + } + Tcl_DStringFree(&script); + } + } + + entryPtr->flags &= ~VALIDATING; + + return code; +} + +/* + *-------------------------------------------------------------- + * + * ExpandPercents -- + * + * Given a command and an event, produce a new command + * by replacing % constructs in the original command + * with information from the X event. + * + * Results: + * The new expanded command is appended to the dynamic string + * given by dsPtr. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static void +ExpandPercents(entryPtr, before, change, new, index, type, dsPtr) + register Entry *entryPtr; /* Entry that needs validation. */ + register char *before; /* Command containing percent + * expressions to be replaced. */ + char *change; /* Characters to added/deleted + * (NULL-terminated string). */ + char *new; /* Potential new value of entry string */ + int index; /* index of insert/delete */ + int type; /* INSERT or DELETE */ + Tcl_DString *dsPtr; /* Dynamic string in which to append + * new command. */ +{ + int spaceNeeded, cvtFlags; /* Used to substitute string as proper Tcl + * list element. */ + int number, length; + register char *string; + Tcl_UniChar ch; + char numStorage[2*TCL_INTEGER_SPACE]; + + while (1) { + if (*before == '\0') { + break; + } + /* + * Find everything up to the next % character and append it + * to the result string. + */ + + string = before; + /* No need to convert '%', as it is in ascii range */ + string = Tcl_UtfFindFirst(before, '%'); + if (string == (char *) NULL) { + Tcl_DStringAppend(dsPtr, before, -1); + break; + } else if (string != before) { + Tcl_DStringAppend(dsPtr, before, string-before); + before = string; + } + + /* + * There's a percent sequence here. Process it. + */ + + before++; /* skip over % */ + if (*before != '\0') { + before += Tcl_UtfToUniChar(before, &ch); + } else { + ch = '%'; + } + switch (ch) { + case 'd': /* Type of call that caused validation */ + switch (type) { + case VALIDATE_INSERT: + number = 1; + break; + case VALIDATE_DELETE: + number = 0; + break; + default: + number = -1; + break; + } + sprintf(numStorage, "%d", number); + string = numStorage; + break; + case 'i': /* index of insert/delete */ + sprintf(numStorage, "%d", index); + string = numStorage; + break; + case 'P': /* 'Peeked' new value of the string */ + string = new; + break; + case 's': /* Current string value of entry */ + string = entryPtr->string; + break; + case 'S': /* string to be inserted/deleted, if any */ + string = change; + break; + case 'v': /* type of validation */ + string = validateStrings[entryPtr->validate]; + break; + case 'W': /* widget name */ + string = Tk_PathName(entryPtr->tkwin); + break; + default: + length = Tcl_UniCharToUtf(ch, numStorage); + numStorage[length] = '\0'; + string = numStorage; + break; + } + + spaceNeeded = Tcl_ScanElement(string, &cvtFlags); + length = Tcl_DStringLength(dsPtr); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + spaceNeeded = Tcl_ConvertElement(string, + Tcl_DStringValue(dsPtr) + length, + cvtFlags | TCL_DONT_USE_BRACES); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + } +} +#endif /* ENTRY_VALIDATE */ |