Files
2021-03-30 00:54:10 +01:00

193 lines
5.3 KiB
C

/*
* Copyright 2003, Joe English
*
* Simplified interface to Tcl_TraceVariable.
*
* PROBLEM: Can't distinguish "variable does not exist" (which is OK)
* from other errors (which are not).
*/
#include "tkInt.h"
#include "ttkTheme.h"
#include "ttkWidget.h"
struct TtkTraceHandle_
{
Tcl_Interp *interp; /* Containing interpreter */
Tcl_Obj *varnameObj; /* Name of variable being traced */
Ttk_TraceProc callback; /* Callback procedure */
void *clientData; /* Data to pass to callback */
};
/*
* Tcl_VarTraceProc for trace handles.
*/
static char *
VarTraceProc(
ClientData clientData, /* Widget record pointer */
Tcl_Interp *interp, /* Interpreter containing variable. */
const char *name1, /* (unused) */
const char *name2, /* (unused) */
int flags) /* Information about what happened. */
{
Ttk_TraceHandle *tracePtr = (Ttk_TraceHandle *)clientData;
const char *name, *value;
Tcl_Obj *valuePtr;
(void)name1;
(void)name2;
if (Tcl_InterpDeleted(interp)) {
return NULL;
}
name = Tcl_GetString(tracePtr->varnameObj);
/*
* If the variable is being unset, then re-establish the trace:
*/
if (flags & TCL_TRACE_DESTROYED) {
/*
* If a prior call to Ttk_UntraceVariable() left behind an
* indicator that we wanted this handler to be deleted (see below),
* cleanup the ClientData bits and exit.
*/
if (tracePtr->interp == NULL) {
Tcl_DecrRefCount(tracePtr->varnameObj);
ckfree(tracePtr);
return NULL;
}
Tcl_TraceVar2(interp, name, NULL,
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
VarTraceProc, clientData);
tracePtr->callback(tracePtr->clientData, NULL);
return NULL;
}
/*
* Call the callback:
*/
valuePtr = Tcl_GetVar2Ex(interp, name, NULL, TCL_GLOBAL_ONLY);
value = valuePtr ? Tcl_GetString(valuePtr) : NULL;
tracePtr->callback(tracePtr->clientData, value);
return NULL;
}
/* Ttk_TraceVariable(interp, varNameObj, callback, clientdata) --
* Attach a write trace to the specified variable,
* which will pass the variable's value to 'callback'
* whenever the variable is set.
*
* When the variable is unset, passes NULL to the callback
* and reattaches the trace.
*/
Ttk_TraceHandle *Ttk_TraceVariable(
Tcl_Interp *interp,
Tcl_Obj *varnameObj,
Ttk_TraceProc callback,
void *clientData)
{
Ttk_TraceHandle *h = (Ttk_TraceHandle *)ckalloc(sizeof(*h));
int status;
h->interp = interp;
h->varnameObj = Tcl_DuplicateObj(varnameObj);
Tcl_IncrRefCount(h->varnameObj);
h->clientData = clientData;
h->callback = callback;
status = Tcl_TraceVar2(interp, Tcl_GetString(varnameObj),
NULL, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
VarTraceProc, h);
if (status != TCL_OK) {
Tcl_DecrRefCount(h->varnameObj);
ckfree(h);
return NULL;
}
return h;
}
/*
* Ttk_UntraceVariable --
* Remove previously-registered trace and free the handle.
*/
void Ttk_UntraceVariable(Ttk_TraceHandle *h)
{
if (h) {
ClientData cd = NULL;
/*
* Workaround for Tcl Bug 3062331. The trace design problem is
* that when variable unset traces fire, Tcl documents that the
* traced variable has already been unset. It's already gone.
* So from within an unset trace, if you try to call
* Tcl_UntraceVar() on that variable, it will do nothing, because
* the variable by that name can no longer be found. It's gone.
* This means callers of Tcl_UntraceVar() that might be running
* in response to an unset trace have to handle the possibility
* that their Tcl_UntraceVar() call will do nothing. In this case,
* we have to support the possibility that Tcl_UntraceVar() will
* leave the trace in place, so we need to leave the ClientData
* untouched so when that trace does fire it will not crash.
*/
/*
* Search the traces on the variable to see if the one we are tasked
* with removing is present.
*/
while ((cd = Tcl_VarTraceInfo(h->interp, Tcl_GetString(h->varnameObj),
TCL_GLOBAL_ONLY, VarTraceProc, cd)) != NULL) {
if (cd == h) {
break;
}
}
/*
* If the trace we wish to delete is not visible, Tcl_UntraceVar
* will do nothing, so don't try to call it. Instead set an
* indicator in the Ttk_TraceHandle that we need to cleanup later.
*/
if (cd == NULL) {
h->interp = NULL;
return;
}
Tcl_UntraceVar2(h->interp, Tcl_GetString(h->varnameObj),
NULL, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
VarTraceProc, h);
Tcl_DecrRefCount(h->varnameObj);
ckfree(h);
}
}
/*
* Ttk_FireTrace --
* Executes a trace handle as if the variable has been written.
*
* Note: may reenter the interpreter.
*/
int Ttk_FireTrace(Ttk_TraceHandle *tracePtr)
{
Tcl_Interp *interp = tracePtr->interp;
void *clientData = tracePtr->clientData;
const char *name = Tcl_GetString(tracePtr->varnameObj);
Ttk_TraceProc callback = tracePtr->callback;
Tcl_Obj *valuePtr;
const char *value;
/* Read the variable.
* Note that this can reenter the interpreter, and anything can happen --
* including the current trace handle being freed!
*/
valuePtr = Tcl_GetVar2Ex(interp, name, NULL, TCL_GLOBAL_ONLY);
value = valuePtr ? Tcl_GetString(valuePtr) : NULL;
/* Call callback.
*/
callback(clientData, value);
return TCL_OK;
}
/*EOF*/