Index: Lib/lib-tk/Tkinter.py =================================================================== --- Lib/lib-tk/Tkinter.py (revision 67238) +++ Lib/lib-tk/Tkinter.py (working copy) @@ -191,6 +191,8 @@ self.set(self._default) def __del__(self): """Unset the variable in Tcl.""" + for item in self.trace_vinfo(): + self.trace_vdelete(*item) self._tk.globalunsetvar(self._name) def __str__(self): """Return the name of the variable in Tcl.""" @@ -210,9 +212,9 @@ Return the name of the callback. """ - cbname = self._master._register(callback) - self._tk.call("trace", "variable", self._name, mode, cbname) - return cbname + cb = self._master._register(callback) + self._tk.call("trace", "variable", self._name, mode, cb) + return cb.name trace = trace_variable def trace_vdelete(self, mode, cbname): """Delete the trace callback for a variable. @@ -495,7 +497,7 @@ func(*args) finally: try: - self.deletecommand(name) + self.deletecommand(str(name)) except TclError: pass name = self._register(callit) @@ -926,23 +928,47 @@ self.tk.call('bindtags', self._w)) else: self.tk.call('bindtags', self._w, tagList) - def _bind(self, what, sequence, func, add, needcleanup=1): + def _bind(self, what, sequence, func, add, widgetcmd=1): """Internal function.""" if type(func) is StringType: self.tk.call(what + (sequence, func)) elif func: - funcid = self._register(func, self._substitute, - needcleanup) + if not add: + # remove previous registered command(s) if any + for cmd in self._bind(what, sequence, None, None): + if widgetcmd: + self.deletecommand(cmd) + else: + self._root().deletecommand(cmd) + + func = self._register(func, self._substitute, widgetcmd) cmd = ('%sif {"[%s %s]" == "break"} break\n' % (add and '+' or '', - funcid, self._subst_format_str)) - self.tk.call(what + (sequence, cmd)) - return funcid + func, self._subst_format_str)) + self.tk.call(what + (sequence, cmd), cmdcreate=[func]) elif sequence: - return self.tk.call(what + (sequence,)) + return self._bind_names(self.tk.call(what + (sequence,))) else: return self.tk.splitlist(self.tk.call(what)) + + def _bind_names(self, cmdlines): + """Internal function. + + Extract command names from cmdlines according to the format + used by _bind while creating a new command in Tcl. + """ + start = 0 + names = [] + + while True: + start = cmdlines.find('[', start) + 1 + if not start: + break + names.append(cmdlines[start:cmdlines.find(' ', start)]) + + return names + def bind(self, sequence=None, func=None, add=None): """Bind to this widget at event SEQUENCE a call to function FUNC. @@ -976,19 +1002,19 @@ be called additionally to the other bound function or whether it will replace the previous function. - Bind will return an identifier to allow deletion of the bound function with - unbind without memory leak. - If FUNC or SEQUENCE is omitted the bound function or list of bound events are returned.""" - return self._bind(('bind', self._w), sequence, func, add) def unbind(self, sequence, funcid=None): - """Unbind for this widget for event SEQUENCE the - function identified with FUNCID.""" + """Unbind all the callbacks registered for this widget for event + sequence. + + Note that funcid is never used, it is here for backward + compatibility.""" + names = self.bind(sequence) self.tk.call('bind', self._w, sequence, '') - if funcid: - self.deletecommand(funcid) + for cmd in names: + self.deletecommand(cmd) def bind_all(self, sequence=None, func=None, add=None): """Bind to all widgets at an event SEQUENCE a call to function FUNC. An additional boolean parameter ADD specifies whether FUNC will @@ -997,7 +1023,10 @@ return self._bind(('bind', 'all'), sequence, func, add, 0) def unbind_all(self, sequence): """Unbind for all widgets for event SEQUENCE all functions.""" + names = self.bind_all(sequence) self.tk.call('bind', 'all' , sequence, '') + for cmd in names: + self._root().deletecommand(cmd) def bind_class(self, className, sequence=None, func=None, add=None): """Bind to widgets with bindtag CLASSNAME at event @@ -1011,7 +1040,10 @@ def unbind_class(self, className, sequence): """Unbind for a all widgets with bindtag CLASSNAME for event SEQUENCE all functions.""" + names = self.bind_class(className, sequence) self.tk.call('bind', className , sequence, '') + for cmd in names: + self._root().deletecommand(cmd) def mainloop(self, n=0): """Call the mainloop of Tk.""" self.tk.mainloop(n) @@ -1048,6 +1080,23 @@ if v is not None: if k[-1] == '_': k = k[:-1] if callable(v): + # If we are replacing a callback, delete it first + try: + cmd = self.cget(k) + except TclError: + # received an unrecognized option for this widget, + # ignore the error for now + pass + else: + # delete command if any, then add one + if cmd: + self.deletecommand(cmd) + + # command registration cannot be skipped even if TclError + # is raised because _options might be called for some + # other use besides widget creation or configuration of + # widget commands (e.g. handling of options for specific + # Tcl commands). v = self._register(v) elif isinstance(v, (tuple, list)): nv = [] @@ -1080,12 +1129,12 @@ return w _nametowidget = nametowidget - def _register(self, func, subst=None, needcleanup=1): - """Return a newly created Tcl function. If this - function is called, the Python function FUNC will - be executed. An optional function SUBST can - be given which will be executed before FUNC.""" - f = CallWrapper(func, subst, self).__call__ + + def _pre_register(self, func, subst): + """Internal function. + + Wrap caller, func and subst in a CallWrapper and return it.""" + f = CallWrapper(func, subst, self) name = repr(id(f)) try: func = func.im_func @@ -1095,13 +1144,39 @@ name = name + func.__name__ except AttributeError: pass + + f.name = name + return f + + def _register(self, func, subst=None, widgetcmd=1): + """Prepares func to be associated by a Tcl command, the "prepared" + function is returned and will be registered when calling into Tcl. + + Any time the associated command in Tcl is invoked, the Python func + will be called. If subst is given, it should be a function to be + executed before func.""" + f = self._pre_register(func, subst) + f.widgetcmd = widgetcmd + return f + + register = _register + + def _finish_register(self, name, f, widgetcmd): + """Internal function. + + Associates name to f in Tcl.""" self.tk.createcommand(name, f) - if needcleanup: + + if widgetcmd: # this command pertains to a single widget if self._tclCommands is None: self._tclCommands = [] self._tclCommands.append(name) - return name - register = _register + else: # this command pertains to the associated interpreter + root = self._root() + if root._tclCommands is None: + root._tclCommands = [] + root._tclCommands.append(name) + def _root(self): """Internal function.""" w = self @@ -1402,6 +1477,11 @@ self.func = func self.subst = subst self.widget = widget + self.name = '' + + def __str__(self): + return self.name + def __call__(self, *args): """Apply first function SUBST to arguments, than FUNC.""" try: @@ -1577,12 +1657,15 @@ """Bind function FUNC to command NAME for this widget. Return the function bound to NAME if None is given. NAME could be e.g. "WM_SAVE_YOURSELF" or "WM_DELETE_WINDOW".""" + if func or func == '': + cmd = self.wm_protocol(name) + if cmd: + self.deletecommand(cmd) + if hasattr(func, '__call__'): - command = self._register(func) - else: - command = func + func = self._register(func) return self.tk.call( - 'wm', 'protocol', self._w, name, command) + 'wm', 'protocol', self._w, name, func) protocol = wm_protocol def wm_resizable(self, width=None, height=None): """Instruct the window manager whether this width can be resized @@ -1641,9 +1724,38 @@ baseName = baseName + ext interactive = 0 self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use) + + self._tkcall = self.tk.call + self.tk.call = self.tclcall + if useTk: self._loadtk() self.readprofile(baseName, className) + + def tclcall(self, *args, **kw): + """Call into Tcl with args. + + If the call fails, any created commands will be removed.""" + if len(args) == 1 and isinstance(args[0], tuple): # XXX compatibility + args = args[0] + cmds = [] + + for item in kw.get('cmdcreate', ()): + Misc._finish_register(item.widget, str(item), item, item.widgetcmd) + cmds.append(item) + for arg in args: + if isinstance(arg, CallWrapper): + Misc._finish_register(arg.widget, str(arg), arg, arg.widgetcmd) + cmds.append(arg) + + try: + return self._tkcall(*args) + except TclError: + # call failed, remove commands registered for it + for arg in cmds: + Misc.deletecommand(arg.widget, str(arg)) + raise + def loadtk(self): if not self._tkloaded: self.tk.loadtk() @@ -1686,6 +1798,11 @@ self.tk.call('destroy', self._w) Misc.destroy(self) global _default_root + + # remove the overrided tk.call + self._tkcall = None + del self.tk.call + if _support_default_root and _default_root is self: _default_root = None def readprofile(self, baseName, className): Index: Modules/_tkinter.c =================================================================== --- Modules/_tkinter.c (revision 67899) +++ Modules/_tkinter.c (working copy) @@ -246,6 +246,7 @@ typedef struct { PyObject_HEAD + PyObject *dict; Tcl_Interp *interp; int wantobjects; int threaded; /* True if tcl_platform[threaded] */ @@ -591,10 +592,13 @@ TkappObject *v; char *argv0; - v = PyObject_New(TkappObject, &Tkapp_Type); + v = PyObject_GC_New(TkappObject, &Tkapp_Type); if (v == NULL) return NULL; + v->dict = NULL; + PyObject_GC_Track(v); + v->interp = Tcl_CreateInterp(); v->wantobjects = wantobjects; v->threaded = Tcl_GetVar2Ex(v->interp, "tcl_platform", "threaded", @@ -2742,23 +2746,74 @@ /**** Tkapp Type Methods ****/ +static PyObject * +Tkapp_getdict(TkappObject *self) +{ + if (self->dict == NULL) { + self->dict = PyDict_New(); + if (self->dict == NULL) + return NULL; + } + Py_INCREF(self->dict); + return self->dict; +} + +static int +Tkapp_setdict(TkappObject *self, PyObject *value) +{ + PyObject *tmp; + + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete __dict__ attribute"); + return -1; + } + + if (!PyDict_Check(value)) { + PyErr_Format(PyExc_TypeError, "'%s' is not a dict", + value->ob_type->tp_name); + return -1; + } + + tmp = self->dict; + Py_INCREF(value); + self->dict = value; + Py_XDECREF(tmp); + return 0; +} + +static PyGetSetDef Tkapp_GetSet[] = { + {"__dict__", (getter)Tkapp_getdict, (setter)Tkapp_setdict}, + {NULL} +}; + +static int +Tkapp_Traverse(TkappObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->dict); + return 0; +} + +static int +Tkapp_Clear(TkappObject *self) +{ + Py_CLEAR(self->dict); + return 0; +} + static void Tkapp_Dealloc(PyObject *self) { + Tkapp_Clear((TkappObject *)self); + PyObject_GC_UnTrack(self); /*CHECK_TCL_APPARTMENT;*/ ENTER_TCL Tcl_DeleteInterp(Tkapp_Interp(self)); LEAVE_TCL - PyObject_Del(self); + + self->ob_type->tp_free(self); DisableEventHook(); } -static PyObject * -Tkapp_GetAttr(PyObject *self, char *name) -{ - return Py_FindMethod(Tkapp_methods, self, name); -} - static PyTypeObject Tkapp_Type = { PyVarObject_HEAD_INIT(NULL, 0) @@ -2767,14 +2822,35 @@ 0, /*tp_itemsize */ Tkapp_Dealloc, /*tp_dealloc */ 0, /*tp_print */ - Tkapp_GetAttr, /*tp_getattr */ + 0, /*tp_getattr */ 0, /*tp_setattr */ 0, /*tp_compare */ 0, /*tp_repr */ 0, /*tp_as_number */ 0, /*tp_as_sequence */ 0, /*tp_as_mapping */ - 0, /*tp_hash */ + 0, /*tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + 0, /* tp_doc */ + (traverseproc)Tkapp_Traverse, /* tp_traverse */ + (inquiry)Tkapp_Clear, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Tkapp_methods, /* tp_methods */ + 0, /* tp_members */ + Tkapp_GetSet, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(TkappObject, dict), /* tp_dictoffset */ };