From 2c8a777381c7c653bf9304fd50a526a8a81ce293 Mon Sep 17 00:00:00 2001
From: hobbs <hobbs>
Date: Thu, 2 Mar 2000 21:52:40 +0000
Subject: 	* generic/tkListbox.c (DestroyListbox): fixed crash in 
 DestroyListbox due to null tkwin.  [Bug: 4207]

	* tests/entry.test: added test suite for entry validation
	* doc/entry.n: improved docs discussing caveats and gotchas when
	mixing textvar with widget validation
	* generic/tkEntry.c (EntryValidateChange): improved handling of
	validation with relation to -textvariable.  Previously, it would
	turn off whenever the textvar was set.  Now it will it will turn
	off only when the textvar is set and validation returns 0.  Added
	%V (type of validation occuring) to %-subs to help work with
	trickier validation.
---
 generic/tkEntry.c   | 44 +++++++++++++++++++++++++++++++++++---------
 generic/tkListbox.c | 40 +++++++++++++++++++---------------------
 2 files changed, 54 insertions(+), 30 deletions(-)

diff --git a/generic/tkEntry.c b/generic/tkEntry.c
index 9f3b560..d860289 100644
--- a/generic/tkEntry.c
+++ b/generic/tkEntry.c
@@ -11,7 +11,7 @@
  * 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.9 2000/01/12 11:45:02 hobbs Exp $
+ * RCS: @(#) $Id: tkEntry.c,v 1.10 2000/03/02 21:52:40 hobbs Exp $
  */
 
 #include "tkInt.h"
@@ -181,6 +181,7 @@ 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.
+ * ENTRY_DELETED:		This entry has been effectively destroyed.
  * 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
@@ -2818,13 +2819,13 @@ EntryValidateChange(entryPtr, change, new, index, type)
      int type;                  /* forced, delete, insert,
 				 * focusin or focusout */
 {
-    int code;
+    int code, varValidate = (entryPtr->flags & VALIDATE_VAR);
     char *p;
     Tcl_DString script;
     
     if (entryPtr->validateCmd == NULL ||
 	entryPtr->validate == VALIDATE_NONE) {
-	return (entryPtr->flags & VALIDATE_VAR) ? TCL_ERROR : TCL_OK;
+	return (varValidate ? TCL_ERROR : TCL_OK);
     }
 
     /*
@@ -2834,7 +2835,7 @@ EntryValidateChange(entryPtr, change, new, index, type)
      */
     if (entryPtr->flags & VALIDATING) {
 	entryPtr->validate = VALIDATE_NONE;
-	return (entryPtr->flags & VALIDATE_VAR) ? TCL_ERROR : TCL_OK;
+	return (varValidate ? TCL_ERROR : TCL_OK);
     }
 
     entryPtr->flags |= VALIDATING;
@@ -2853,12 +2854,13 @@ EntryValidateChange(entryPtr, change, new, index, type)
     Tcl_DStringFree(&script);
 
     /*
-     * If e->validate has become VALIDATE_NONE during the validation,
+     * If e->validate has become VALIDATE_NONE during the validation, or
+     * we now have VALIDATE_VAR set (from EntrySetValue) and didn't before,
      * 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)) {
+    if (entryPtr->validate == VALIDATE_NONE
+	    || (!varValidate && (entryPtr->flags & VALIDATE_VAR))) {
 	code = TCL_ERROR;
     }
     /*
@@ -2869,7 +2871,17 @@ EntryValidateChange(entryPtr, change, new, index, type)
     if (code == TCL_ERROR) {
 	entryPtr->validate = VALIDATE_NONE;
     } else if (code == TCL_BREAK) {
-	if (entryPtr->invalidCmd != NULL) {
+	/*
+	 * If we were doing forced validation (like via a variable
+	 * trace) and the command returned 0, the we turn off validation
+	 * because we assume that textvariables have precedence in
+	 * managing the value.  We also don't call the invcmd, as it
+	 * may want to do entry manipulation which the setting of the
+	 * var will later wipe anyway.
+	 */
+	if (varValidate) {
+	    entryPtr->validate = VALIDATE_NONE;
+	} else if (entryPtr->invalidCmd != NULL) {
 	    Tcl_DStringInit(&script);
 	    ExpandPercents(entryPtr, entryPtr->invalidCmd,
 			   change, new, index, type, &script);
@@ -2989,9 +3001,23 @@ ExpandPercents(entryPtr, before, change, new, index, type, dsPtr)
 	case 'S': /* string to be inserted/deleted, if any */
 	    string = change;
 	    break;
-	case 'v': /* type of validation */
+	case 'v': /* type of validation currently set */
 	    string = validateStrings[entryPtr->validate];
 	    break;
+	case 'V': /* type of validation in effect */
+	    switch (type) {
+	    case VALIDATE_INSERT:
+	    case VALIDATE_DELETE:
+		string = validateStrings[VALIDATE_KEY];
+		break;
+	    case VALIDATE_FORCED:
+		string = "forced";
+		break;
+	    default:
+		string = validateStrings[type];
+		break;
+	    }
+	    break;
 	case 'W': /* widget name */
 	    string = Tk_PathName(entryPtr->tkwin);
 	    break;
diff --git a/generic/tkListbox.c b/generic/tkListbox.c
index 2c587ed..4e9081a 100644
--- a/generic/tkListbox.c
+++ b/generic/tkListbox.c
@@ -11,7 +11,7 @@
  * See the file "license.terms" for information on usage and redistribution
  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
- * RCS: @(#) $Id: tkListbox.c,v 1.15 2000/01/21 03:54:42 hobbs Exp $
+ * RCS: @(#) $Id: tkListbox.c,v 1.16 2000/03/02 21:52:41 hobbs Exp $
  */
 
 #include "tkPort.h"
@@ -183,13 +183,15 @@ typedef struct {
  * GOT_FOCUS:			Non-zero means this widget currently
  *				has the input focus.
  * MAXWIDTH_IS_STALE:           Stored maxWidth may be out-of-date
+ * LISTBOX_DELETED:		This listbox has been effectively destroyed.
  */
 
 #define REDRAW_PENDING		1
 #define UPDATE_V_SCROLLBAR	2
 #define UPDATE_H_SCROLLBAR	4
 #define GOT_FOCUS		8
-#define MAXWIDTH_IS_STALE      16
+#define MAXWIDTH_IS_STALE	16
+#define LISTBOX_DELETED		32
 
 /*
  * The optionSpecs table defines the valid configuration options for the
@@ -1409,7 +1411,17 @@ DestroyListbox(memPtr)
     register Listbox *listPtr = (Listbox *) memPtr;
     Tcl_HashEntry *entry;
     Tcl_HashSearch search;
-    
+
+    listPtr->flags |= LISTBOX_DELETED;
+
+    Tcl_DeleteCommandFromToken(listPtr->interp, listPtr->widgetCmd);
+    if (listPtr->setGrid) {
+	Tk_UnsetGrid(listPtr->tkwin);
+    }
+    if (listPtr->flags & REDRAW_PENDING) {
+	Tcl_CancelIdleCall(DisplayListbox, (ClientData) listPtr);
+    }
+
     /* If we have an internal list object, free it */
     if (listPtr->listObj != NULL) {
 	Tcl_DecrRefCount(listPtr->listObj);
@@ -1448,6 +1460,7 @@ DestroyListbox(memPtr)
     }
     Tk_FreeConfigOptions((char *)listPtr, listPtr->optionTable,
 	    listPtr->tkwin);
+    listPtr->tkwin = NULL;
     ckfree((char *) listPtr);
 }
 
@@ -2352,17 +2365,7 @@ ListboxEventProc(clientData, eventPtr)
 		NearestListboxElement(listPtr, eventPtr->xexpose.y
 		+ eventPtr->xexpose.height));
     } else if (eventPtr->type == DestroyNotify) {
-	if (listPtr->tkwin != NULL) {
-	    if (listPtr->setGrid) {
-		Tk_UnsetGrid(listPtr->tkwin);
-	    }
-	    listPtr->tkwin = NULL;
-	    Tcl_DeleteCommandFromToken(listPtr->interp, listPtr->widgetCmd);
-	}
-	if (listPtr->flags & REDRAW_PENDING) {
-	    Tcl_CancelIdleCall(DisplayListbox, (ClientData) listPtr);
-	}
-	Tcl_EventuallyFree((ClientData) listPtr, DestroyListbox);
+	DestroyListbox((char *) clientData);
     } else if (eventPtr->type == ConfigureNotify) {
 	int vertSpace;
 
@@ -2421,7 +2424,6 @@ ListboxCmdDeletedProc(clientData)
     ClientData clientData;	/* Pointer to widget record for widget. */
 {
     Listbox *listPtr = (Listbox *) clientData;
-    Tk_Window tkwin = listPtr->tkwin;
 
     /*
      * This procedure could be invoked either because the window was
@@ -2430,12 +2432,8 @@ ListboxCmdDeletedProc(clientData)
      * destroys the widget.
      */
 
-    if (tkwin != NULL) {
-	if (listPtr->setGrid) {
-	    Tk_UnsetGrid(listPtr->tkwin);
-	}
-	listPtr->tkwin = NULL;
-	Tk_DestroyWindow(tkwin);
+    if (!(listPtr->flags & LISTBOX_DELETED)) {
+	Tk_DestroyWindow(listPtr->tkwin);
     }
 }
 
-- 
cgit v0.12