summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/tkinter.tex13
-rw-r--r--Lib/lib-tk/Tkinter.py35
-rw-r--r--Lib/test/test_tcl.py159
-rw-r--r--Modules/_tkinter.c62
-rw-r--r--Modules/tkappinit.c11
5 files changed, 264 insertions, 16 deletions
diff --git a/Doc/lib/tkinter.tex b/Doc/lib/tkinter.tex
index e0c613f28a..55f822ffd3 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 67e942e18a..5ad065da2d 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 0000000000..3e0a781814
--- /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 e6f89531da..39a93da84f 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 96c545d7e9..42b6bb8830 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);