Implementation of patch 869468

Allow the user to create Tkinter.Tcl objects which are
just like Tkinter.Tk objects except that they do not
initialize Tk. This is useful in circumstances where the
script is being run on machines that do not have an X
server running -- in those cases, Tk initialization fails,
even if no window is ever created.

Includes documentation change and tests.

Tested on Linux, Solaris and Windows.

Reviewed by Martin von Loewis.
This commit is contained in:
David Ascher 2004-02-18 05:59:53 +00:00
parent f06116dcab
commit e2b4b32025
5 changed files with 264 additions and 16 deletions

View File

@ -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}

View File

@ -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.

159
Lib/test/test_tcl.py Normal file
View File

@ -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()

View File

@ -546,16 +546,20 @@ 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;
}
_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;
}
#endif /* !WITH_APPINIT */
@ -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 *

View File

@ -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);