/*
 * tixOption.c --
 *
 *	Handle the "$widget config" commands
 *
 * Copyright (c) 1996, Expert Interface Technologies
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */

/* ToDo:
 *
 * 1) ConfigSpec structures shouldn't be shared between parent and child
 *    classes.
 * 2) Specs array should be compacted (no duplication) and sorted according
 *    to argvName during class declaration.
 * 3) Merge aliases with specs
 *
 */
#include <tk.h>
#include <tixPort.h>
#include <tixInt.h>

static char *			FormatConfigInfo _ANSI_ARGS_((
				    Tcl_Interp *interp, TixClassRecord *cPtr,
				    char * widRec, TixConfigSpec * spec));
/*
 * This is a lightweight interface for querying the value of a
 * variable in the object. This is simpler compared to
 * [lindex [$w config -flag] 4]
 */
int Tix_GetVar (interp, cPtr, widRec, flag)
    Tcl_Interp *interp;
    TixClassRecord *cPtr;
    char * widRec;
    char * flag;
{
    TixConfigSpec * spec;
    char * value;

    spec = Tix_FindConfigSpecByName(interp, cPtr, flag);
    if (spec != NULL) {
	if (spec->isAlias) {
	    flag = spec->realPtr->argvName;
	} else {
	    /* The user may have specified a shorthand like -backgro */
	    flag = spec->argvName;
	}
	value = Tcl_GetVar2(interp, widRec, flag, TCL_GLOBAL_ONLY);

	Tcl_AppendResult(interp, value, (char*)NULL);
	return TCL_OK;
    }
    else {
	return TCL_ERROR;
    }
}

int
Tix_QueryOneOption(interp, cPtr, widRec, flag)
    Tcl_Interp *interp;
    TixClassRecord *cPtr;
    char *widRec;
    char *flag;
{
    TixConfigSpec * spec;
    char * list;

    spec = Tix_FindConfigSpecByName(interp, cPtr, flag);
    if (spec != NULL) {
	list = FormatConfigInfo(interp, cPtr, widRec, spec);
	Tcl_SetResult(interp, list, TCL_VOLATILE);

	ckfree(list);
	return TCL_OK;
    }
    else {
	return TCL_ERROR;
    }
}

/*
 * Tix_QueryAllOptions --
 *
 *	Note: This function does not call Tix_FindConfigSpec() because
 * it just needs to print out the list of all configSpecs from the
 * class structure.
 *
 */
int
Tix_QueryAllOptions (interp, cPtr, widRec)
    Tcl_Interp *interp;
    TixClassRecord * cPtr;
    char *widRec;
{
    int		i, code = TCL_OK;
    char      * list;
    char      * lead = "{";

    /* Iterate over all the options of class */
    for (i=0; i<cPtr->nSpecs; i++) {
	if (cPtr->specs[i] && cPtr->specs[i]->argvName) {
	    list = FormatConfigInfo(interp, cPtr, widRec, cPtr->specs[i]);
	    Tcl_AppendResult(interp, lead, list, "}", (char *) NULL);

	    ckfree(list);
	    lead = " {";
	}
    }

    return code;
}

TixConfigSpec *
Tix_FindConfigSpecByName(interp, cPtr, flag)
    Tcl_Interp * interp;
    TixClassRecord * cPtr;
    char * flag;
{
    char * classRec;
    char * key;
    int nMatch, i;
    size_t len;
    Tcl_HashEntry *hashPtr;
    TixConfigSpec *configSpec;

    classRec = cPtr->className;

    /*
     * First try to look up the confifspec in a hash table, 
     * it should be faster.
     */

    key = Tix_GetConfigSpecFullName(classRec, flag);
    hashPtr = Tcl_FindHashEntry(_TixGetHashTable(interp, "tixSpecTab", NULL),
	    key);
    ckfree(key);

    if (hashPtr) {
	return (TixConfigSpec *) Tcl_GetHashValue(hashPtr);
    }

    /*
     * The user may specified a partial name. Try to match, but will
     * return error if we don't get exactly one match.
     */
    len = strlen(flag);
    for (configSpec=NULL,nMatch=0,i=0; i<cPtr->nSpecs; i++) {
	if (strncmp(flag, cPtr->specs[i]->argvName, len) == 0) {
	    if (nMatch > 0) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "ambiguous option \"", flag, "\"",
		    (char*)NULL);
		return NULL;
	    } else {
		nMatch ++;
		configSpec = cPtr->specs[i];
	    }
	}
    }

    if (configSpec == NULL) {
	Tcl_ResetResult(interp);
	Tcl_AppendResult(interp, "unknown option \"", flag, "\"", (char*)NULL);
	return NULL;
    } else {
	return configSpec;
    }
}

char * Tix_GetConfigSpecFullName(classRec, flag)
    char * classRec;
    char * flag;
{
    char * buff;
    int    max;
    int    conLen;

    conLen = strlen(classRec);
    max = conLen + strlen(flag) + 1;
    buff = (char*)ckalloc(max * sizeof(char));

    strcpy(buff, classRec);
    strcpy(buff+conLen, flag);

    return buff;
}

int Tix_ChangeOptions(interp, cPtr, widRec, argc, argv)
    Tcl_Interp *interp;
    TixClassRecord *cPtr;
    char * widRec;
    int argc;
    char ** argv;
{
    int i, code = TCL_OK;
    TixConfigSpec * spec;

    if (argc == 0) {
	goto done;
    }

    if ((argc %2) != 0) {
	if (Tix_FindConfigSpecByName(interp, cPtr, argv[argc-1])) {
	    Tcl_AppendResult(interp, "value for \"", argv[argc-1],
		"\" missing", (char*)NULL);
	} else {
	    /* The error message is already appended by 
	     * Tix_FindConfigSpecByName()
	     */
	}
	code = TCL_ERROR;
	goto done;
    }

    for (i=0; i<argc; i+=2) {
	spec = Tix_FindConfigSpecByName(interp, cPtr, argv[i]);

	if (spec == NULL) {
	    code = TCL_ERROR;
	    goto done;
	}
	if (Tix_ChangeOneOption(interp, cPtr, widRec, spec,
		argv[i+1], 0, 0)!=TCL_OK) {
	    code = TCL_ERROR;
	    goto done;
	}
    }

  done:
    return code;
}

int Tix_CallConfigMethod(interp, cPtr, widRec, spec, value)
    Tcl_Interp *interp;
    TixClassRecord *cPtr;
    char * widRec;
    TixConfigSpec *spec;
    char * value;
{
    char * argv[2];
    char method[200];
    char * context = Tix_GetContext(interp, widRec);
    char * c;

    sprintf(method, "config%s", spec->argvName);

    c = Tix_FindMethod(interp, context, method);
    if (c != NULL) {
	argv[0] = value;
	return Tix_CallMethod(interp, c, widRec, method, 1, argv);
    }

    c = Tix_FindMethod(interp, context, "config");
    if (c != NULL) {
	argv[0] = spec->argvName;
	argv[1] = value;
	return Tix_CallMethod(interp, c, widRec, "config", 2, argv);
    }

    return TCL_OK;
}

int Tix_ChangeOneOption(interp, cPtr, widRec, spec, value, isDefault, isInit)
    Tcl_Interp *interp;
    TixClassRecord *cPtr;
    char * widRec;
    TixConfigSpec *spec;
    char * value;
    int isDefault;		/* Set to be true when Tix tries to set
				 * the options according to their default
				 * values */
    int isInit;			/* Set to true only if the option was
				 * specified at the widget creation command
				 */
{
    int code = TCL_OK;
    char * newValue = NULL;

    if (spec->isAlias) {
	spec = spec->realPtr;
    }

    /* -- STEP 1 --
     * Check if these variables are protected.
     *	readonly means the variable can never be assigned to.
     *	static means ths variable can only ne assigned during initialization.
     */

    if (!isDefault && spec->readOnly) {
	Tcl_AppendResult(interp, "cannot assigned to readonly variable \"",
		spec->argvName, "\"", (char*) NULL);
	code = TCL_ERROR;
	goto done;
    }
    if (!(isInit || isDefault) && spec->isStatic) {
	Tcl_AppendResult(interp, "cannot assigned to static variable \"",
		spec->argvName, "\"", (char*) NULL);
	code = TCL_ERROR;
	goto done;
    }

    /* -- STEP 2 --
     * Call the verify command to check whether the value is acceptable
     *
     */
    if (spec->verifyCmd != NULL) {
	char * cmdArgv[2];
	cmdArgv[0] = spec->verifyCmd;
	cmdArgv[1] = value;

	if (Tix_EvalArgv(interp, 2, cmdArgv) != TCL_OK) {
	    code = TCL_ERROR;
	    goto done;
	} else {
	    newValue = value = tixStrDup(interp->result);
	}
    }

    /* -- STEP 3 --
     * Call the configuration method of the widget. This method may
     * override the user-supplied value. The configuration method should
     * not be called during initialization.
     */
    if (isInit || isDefault) {
	/* No need to call the configuration method */
	Tcl_SetVar2(interp, widRec, spec->argvName, value,TCL_GLOBAL_ONLY);
    }
    else {
	if (Tix_CallConfigMethod(interp, cPtr, widRec, spec, value)!=TCL_OK) {
	    code = TCL_ERROR;
	    goto done;
	}
	/* -- STEP 4 --
	 * If the configuration method does not override the value, set
	 * it in the widget record.
	 *
	 * If the configuration method has override the value, it will
	 * return a non-empty string. In this case, it is the configuration
	 * method's responsibility to set the value in the widget record.
	 */
	if (interp->result && (*interp->result)) {
	    /* value was overrided. Don't do anything */
	    Tcl_ResetResult(interp);
	} else {
	    Tcl_SetVar2(interp, widRec, spec->argvName, value,TCL_GLOBAL_ONLY);
	}
    }

  done:
    if (newValue) {
	ckfree(newValue);
    }
    return code;
}

static char * FormatConfigInfo(interp, cPtr, widRec, sPtr)
    Tcl_Interp *interp;
    TixClassRecord *cPtr;
    char * widRec;
    TixConfigSpec * sPtr;
{
    char *argv[6];

    if (sPtr->isAlias) {
	if (cPtr->isWidget) {
	    argv[0] = sPtr->argvName;
	    argv[1] = sPtr->realPtr->dbName;
	} else {
	    argv[0] = sPtr->argvName;
	    argv[1] = sPtr->realPtr->argvName;
	}
	return Tcl_Merge(2, argv);
    } else {
	argv[0] = sPtr->argvName;
	argv[1] = sPtr->dbName;
	argv[2] = sPtr->dbClass;
	argv[3] = sPtr->defValue;
	argv[4] = Tcl_GetVar2(interp, widRec, argv[0], TCL_GLOBAL_ONLY);

	return Tcl_Merge(5, argv);
    }
}
