diff --git a/Doc/lib/tkinter.tex b/Doc/lib/tkinter.tex index e0c613f28aa..55f822ffd33 100644 --- a/Doc/lib/tkinter.tex +++ b/Doc/lib/tkinter.tex @@ -94,13 +94,24 @@ Or, more often: from Tkinter import * \end{verbatim} -\begin{classdesc}{Tk}{screenName=None, baseName=None, className='Tk'} +\begin{classdesc}{Tk}{screenName=None, baseName=None, className='Tk', useTk=1} The \class{Tk} class is instantiated without arguments. This creates a toplevel widget of Tk which usually is the main window of an appliation. Each instance has its own associated Tcl interpreter. % FIXME: The following keyword arguments are currently recognized: \end{classdesc} +\begin{funcdesc}{Tcl}{screenName=None, baseName=None, className='Tk', useTk=0} +The \function{Tcl} function is a factory function which creates an object +much like that created by the \class{Tk} class, except that it does not +initialize the Tk subsystem. This is most often useful when driving the Tcl +interpreter in an environment where one doesn't want to create extraneous +toplevel windows, or where one cannot (i.e. Unix/Linux systems without an X +server). An object created by the \function{Tcl} object can have a Toplevel +window created (and the Tk subsystem initialized) by calling its +\method{loadtk} method. +\end{funcdesc} + Other modules that provide Tk support include: \begin{description} diff --git a/Lib/lib-tk/Tkinter.py b/Lib/lib-tk/Tkinter.py index 67e942e18ad..5ad065da2d9 100644 --- a/Lib/lib-tk/Tkinter.py +++ b/Lib/lib-tk/Tkinter.py @@ -1546,23 +1546,36 @@ class Tk(Misc, Wm): """Toplevel widget of Tk which represents mostly the main window of an appliation. It has an associated Tcl interpreter.""" _w = '.' - def __init__(self, screenName=None, baseName=None, className='Tk'): + def __init__(self, screenName=None, baseName=None, className='Tk', useTk=1): """Return a new Toplevel widget on screen SCREENNAME. A new Tcl interpreter will be created. BASENAME will be used for the identification of the profile file (see readprofile). It is constructed from sys.argv[0] without extensions if None is given. CLASSNAME is the name of the widget class.""" - global _default_root self.master = None self.children = {} + self._tkloaded = 0 + # to avoid recursions in the getattr code in case of failure, we + # ensure that self.tk is always _something_. + self.tk = None if baseName is None: import sys, os baseName = os.path.basename(sys.argv[0]) baseName, ext = os.path.splitext(baseName) if ext not in ('.py', '.pyc', '.pyo'): baseName = baseName + ext - self.tk = _tkinter.create(screenName, baseName, className) - self.tk.wantobjects(wantobjects) + interactive = 0 + self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk) + if useTk: + self._loadtk() + self.readprofile(baseName, className) + def loadtk(self): + if not self._tkloaded: + self.tk.loadtk() + self._loadtk() + def _loadtk(self): + self._tkloaded = 1 + global _default_root if _MacOS and hasattr(_MacOS, 'SchedParams'): # Disable event scanning except for Command-Period _MacOS.SchedParams(1, 0) @@ -1587,7 +1600,6 @@ class Tk(Misc, Wm): % str(TkVersion) self.tk.createcommand('tkerror', _tkerror) self.tk.createcommand('exit', _exit) - self.readprofile(baseName, className) if _support_default_root and not _default_root: _default_root = self self.protocol("WM_DELETE_WINDOW", self.destroy) @@ -1629,6 +1641,15 @@ class Tk(Misc, Wm): sys.last_value = val sys.last_traceback = tb traceback.print_exception(exc, val, tb) + def __getattr__(self, attr): + "Delegate attribute access to the interpreter object" + return getattr(self.tk, attr) + def __hasattr__(self, attr): + "Delegate attribute access to the interpreter object" + return hasattr(self.tk, attr) + def __delattr__(self, attr): + "Delegate attribute access to the interpreter object" + return delattr(self.tk, attr) # Ideally, the classes Pack, Place and Grid disappear, the # pack/place/grid methods are defined on the Widget class, and @@ -1644,6 +1665,10 @@ class Tk(Misc, Wm): # toplevel and interior widgets). Again, for compatibility, these are # copied into the Pack, Place or Grid class. + +def Tcl(screenName=None, baseName=None, className='Tk', useTk=0): + return Tk(screenName, baseName, className, useTk) + class Pack: """Geometry manager Pack. diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py new file mode 100644 index 00000000000..3e0a781814d --- /dev/null +++ b/Lib/test/test_tcl.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python + +import unittest +import os +from Tkinter import Tcl +from _tkinter import TclError + +class TclTest(unittest.TestCase): + + def setUp(self): + self.interp = Tcl() + + def testEval(self): + tcl = self.interp + tcl.eval('set a 1') + self.assertEqual(tcl.eval('set a'),'1') + + def testEvalException(self): + tcl = self.interp + self.assertRaises(TclError,tcl.eval,'set a') + + def testEvalException2(self): + tcl = self.interp + self.assertRaises(TclError,tcl.eval,'this is wrong') + + def testCall(self): + tcl = self.interp + tcl.call('set','a','1') + self.assertEqual(tcl.call('set','a'),'1') + + def testCallException(self): + tcl = self.interp + self.assertRaises(TclError,tcl.call,'set','a') + + def testCallException2(self): + tcl = self.interp + self.assertRaises(TclError,tcl.call,'this','is','wrong') + + def testSetVar(self): + tcl = self.interp + tcl.setvar('a','1') + self.assertEqual(tcl.eval('set a'),'1') + + def testSetVarArray(self): + tcl = self.interp + tcl.setvar('a(1)','1') + self.assertEqual(tcl.eval('set a(1)'),'1') + + def testGetVar(self): + tcl = self.interp + tcl.eval('set a 1') + self.assertEqual(tcl.getvar('a'),'1') + + def testGetVarArray(self): + tcl = self.interp + tcl.eval('set a(1) 1') + self.assertEqual(tcl.getvar('a(1)'),'1') + + def testGetVarException(self): + tcl = self.interp + self.assertRaises(TclError,tcl.getvar,'a') + + def testGetVarArrayException(self): + tcl = self.interp + self.assertRaises(TclError,tcl.getvar,'a(1)') + + def testUnsetVar(self): + tcl = self.interp + tcl.setvar('a',1) + self.assertEqual(tcl.eval('info exists a'),'1') + tcl.unsetvar('a') + self.assertEqual(tcl.eval('info exists a'),'0') + + def testUnsetVarArray(self): + tcl = self.interp + tcl.setvar('a(1)',1) + tcl.setvar('a(2)',2) + self.assertEqual(tcl.eval('info exists a(1)'),'1') + self.assertEqual(tcl.eval('info exists a(2)'),'1') + tcl.unsetvar('a(1)') + self.assertEqual(tcl.eval('info exists a(1)'),'0') + self.assertEqual(tcl.eval('info exists a(2)'),'1') + + def testUnsetVarException(self): + tcl = self.interp + self.assertRaises(TclError,tcl.unsetvar,'a') + + def testEvalFile(self): + tcl = self.interp + filename = "testEvalFile.tcl" + fd = open(filename,'w') + script = """set a 1 + set b 2 + set c [ expr $a + $b ] + """ + fd.write(script) + fd.close() + tcl.evalfile(filename) + self.assertEqual(tcl.eval('set a'),'1') + self.assertEqual(tcl.eval('set b'),'2') + self.assertEqual(tcl.eval('set c'),'3') + + def testEvalFileException(self): + tcl = self.interp + filename = "doesnotexists" + try: + os.remove(filename) + except Exception,e: + pass + self.assertRaises(TclError,tcl.evalfile,filename) + + def testPackageRequire(self): + tcl = self.interp + tcl.eval('package require Tclx') + tcl.eval('keylset a b.c 1') + self.assertEqual(tcl.eval('keylget a b.c'),'1') + + def testPackageRequireException(self): + tcl = self.interp + self.assertRaises(TclError,tcl.eval,'package require DNE') + + def testLoadTk(self): + import os + if 'DISPLAY' not in os.environ: + # skipping test of clean upgradeability + return + tcl = Tcl() + self.assertRaises(TclError,tcl.winfo_geometry) + tcl.loadtk() + self.assertEqual('1x1+0+0', tcl.winfo_geometry()) + + def testLoadTkFailure(self): + import os + old_display = None + import sys + if sys.platform.startswith('win'): + return # no failure possible on windows? + if 'DISPLAY' in os.environ: + old_display = os.environ['DISPLAY'] + del os.environ['DISPLAY'] + # on some platforms, deleting environment variables + # doesn't actually carry through to the process level + # because they don't support unsetenv + # If that's the case, abort. + display = os.popen('echo $DISPLAY').read().strip() + if display: + return + try: + tcl = Tcl() + self.assertRaises(TclError, tcl.winfo_geometry) + self.assertRaises(TclError, tcl.loadtk) + finally: + if old_display is not None: + os.environ['DISPLAY'] = old_display + +if __name__ == "__main__": + unittest.main() + + diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index e6f89531da9..39a93da84f0 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -546,15 +546,19 @@ int Tcl_AppInit(Tcl_Interp *interp) { Tk_Window main; + const char * _tkinter_skip_tk_init; - main = Tk_MainWindow(interp); if (Tcl_Init(interp) == TCL_ERROR) { PySys_WriteStderr("Tcl_Init error: %s\n", Tcl_GetStringResult(interp)); return TCL_ERROR; } - if (Tk_Init(interp) == TCL_ERROR) { - PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp)); - return TCL_ERROR; + _tkinter_skip_tk_init = Tcl_GetVar(interp, "_tkinter_skip_tk_init", TCL_GLOBAL_ONLY); + if (_tkinter_skip_tk_init == NULL || strcmp(_tkinter_skip_tk_init, "1") != 0) { + main = Tk_MainWindow(interp); + if (Tk_Init(interp) == TCL_ERROR) { + PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp)); + return TCL_ERROR; + } } return TCL_OK; } @@ -572,11 +576,10 @@ static void DisableEventHook(void); /* Forward */ static TkappObject * Tkapp_New(char *screenName, char *baseName, char *className, - int interactive, int wantobjects) + int interactive, int wantobjects, int wantTk) { TkappObject *v; char *argv0; - v = PyObject_New(TkappObject, &Tkapp_Type); if (v == NULL) return NULL; @@ -637,6 +640,10 @@ Tkapp_New(char *screenName, char *baseName, char *className, Tcl_SetVar(v->interp, "argv0", argv0, TCL_GLOBAL_ONLY); ckfree(argv0); + if (! wantTk) { + Tcl_SetVar(v->interp, "_tkinter_skip_tk_init", "1", TCL_GLOBAL_ONLY); + } + if (Tcl_AppInit(v->interp) != TCL_OK) return (TkappObject *)Tkinter_Error((PyObject *)v); @@ -2562,6 +2569,41 @@ Tkapp_InterpAddr(PyObject *self, PyObject *args) return PyInt_FromLong((long)Tkapp_Interp(self)); } +static PyObject * +Tkapp_TkInit(PyObject *self, PyObject *args) +{ + Tcl_Interp *interp = Tkapp_Interp(self); + Tk_Window main; + const char * _tk_exists = NULL; + PyObject *res = NULL; + int err; + main = Tk_MainWindow(interp); + if (!PyArg_ParseTuple(args, ":loadtk")) + return NULL; + + /* We want to guard against calling Tk_Init() multiple times */ + CHECK_TCL_APPARTMENT; + ENTER_TCL + err = Tcl_Eval(Tkapp_Interp(self), "info exists tk_version"); + ENTER_OVERLAP + if (err == TCL_ERROR) { + res = Tkinter_Error(self); + } else { + _tk_exists = Tkapp_Result(self); + } + LEAVE_OVERLAP_TCL + if (err == TCL_ERROR) { + return NULL; + } + if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0) { + if (Tk_Init(interp) == TCL_ERROR) { + PyErr_SetString(Tkinter_TclError, Tcl_GetStringResult(Tkapp_Interp(self))); + return NULL; + } + } + Py_INCREF(Py_None); + return Py_None; +} static PyObject * Tkapp_WantObjects(PyObject *self, PyObject *args) @@ -2629,6 +2671,7 @@ static PyMethodDef Tkapp_methods[] = {"dooneevent", Tkapp_DoOneEvent, METH_VARARGS}, {"quit", Tkapp_Quit, METH_VARARGS}, {"interpaddr", Tkapp_InterpAddr, METH_VARARGS}, + {"loadtk", Tkapp_TkInit, METH_VARARGS}, {NULL, NULL} }; @@ -2793,6 +2836,7 @@ Tkinter_Create(PyObject *self, PyObject *args) char *className = NULL; int interactive = 0; int wantobjects = 0; + int wantTk = 1; /* If false, then Tk_Init() doesn't get called */ baseName = strrchr(Py_GetProgramName(), '/'); if (baseName != NULL) @@ -2801,13 +2845,13 @@ Tkinter_Create(PyObject *self, PyObject *args) baseName = Py_GetProgramName(); className = "Tk"; - if (!PyArg_ParseTuple(args, "|zssii:create", + if (!PyArg_ParseTuple(args, "|zssiii:create", &screenName, &baseName, &className, - &interactive, &wantobjects)) + &interactive, &wantobjects, &wantTk)) return NULL; return (PyObject *) Tkapp_New(screenName, baseName, className, - interactive, wantobjects); + interactive, wantobjects, wantTk); } static PyObject * diff --git a/Modules/tkappinit.c b/Modules/tkappinit.c index 96c545d7e9a..42b6bb88304 100644 --- a/Modules/tkappinit.c +++ b/Modules/tkappinit.c @@ -19,6 +19,7 @@ int Tcl_AppInit(Tcl_Interp *interp) { Tk_Window main_window; + const char * _tkinter_skip_tk_init; #ifdef TK_AQUA #ifndef MAX_PATH_LEN @@ -68,7 +69,15 @@ Tcl_AppInit(Tcl_Interp *interp) TclSetLibraryPath(pathPtr); #endif - if (Tk_Init (interp) == TCL_ERROR) +#ifdef WITH_XXX + // Initialize modules that don't require Tk +#endif + + _tkinter_skip_tk_init = Tcl_GetVar(interp, "_tkinter_skip_tk_init", TCL_GLOBAL_ONLY); + if (_tkinter_skip_tk_init != NULL && strcmp(_tkinter_skip_tk_init, "1") == 0) { + return TCL_OK; + } + if (Tk_Init(interp) == TCL_ERROR) return TCL_ERROR; main_window = Tk_MainWindow(interp);