Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e2b4b32

Browse files
committed
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.
1 parent f06116d commit e2b4b32

5 files changed

Lines changed: 264 additions & 16 deletions

File tree

Doc/lib/tkinter.tex

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,24 @@ \subsection{Tkinter Modules}
9494
from Tkinter import *
9595
\end{verbatim}
9696

97-
\begin{classdesc}{Tk}{screenName=None, baseName=None, className='Tk'}
97+
\begin{classdesc}{Tk}{screenName=None, baseName=None, className='Tk', useTk=1}
9898
The \class{Tk} class is instantiated without arguments.
9999
This creates a toplevel widget of Tk which usually is the main window
100100
of an appliation. Each instance has its own associated Tcl interpreter.
101101
% FIXME: The following keyword arguments are currently recognized:
102102
\end{classdesc}
103103

104+
\begin{funcdesc}{Tcl}{screenName=None, baseName=None, className='Tk', useTk=0}
105+
The \function{Tcl} function is a factory function which creates an object
106+
much like that created by the \class{Tk} class, except that it does not
107+
initialize the Tk subsystem. This is most often useful when driving the Tcl
108+
interpreter in an environment where one doesn't want to create extraneous
109+
toplevel windows, or where one cannot (i.e. Unix/Linux systems without an X
110+
server). An object created by the \function{Tcl} object can have a Toplevel
111+
window created (and the Tk subsystem initialized) by calling its
112+
\method{loadtk} method.
113+
\end{funcdesc}
114+
104115
Other modules that provide Tk support include:
105116

106117
\begin{description}

Lib/lib-tk/Tkinter.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,23 +1546,36 @@ class Tk(Misc, Wm):
15461546
"""Toplevel widget of Tk which represents mostly the main window
15471547
of an appliation. It has an associated Tcl interpreter."""
15481548
_w = '.'
1549-
def __init__(self, screenName=None, baseName=None, className='Tk'):
1549+
def __init__(self, screenName=None, baseName=None, className='Tk', useTk=1):
15501550
"""Return a new Toplevel widget on screen SCREENNAME. A new Tcl interpreter will
15511551
be created. BASENAME will be used for the identification of the profile file (see
15521552
readprofile).
15531553
It is constructed from sys.argv[0] without extensions if None is given. CLASSNAME
15541554
is the name of the widget class."""
1555-
global _default_root
15561555
self.master = None
15571556
self.children = {}
1557+
self._tkloaded = 0
1558+
# to avoid recursions in the getattr code in case of failure, we
1559+
# ensure that self.tk is always _something_.
1560+
self.tk = None
15581561
if baseName is None:
15591562
import sys, os
15601563
baseName = os.path.basename(sys.argv[0])
15611564
baseName, ext = os.path.splitext(baseName)
15621565
if ext not in ('.py', '.pyc', '.pyo'):
15631566
baseName = baseName + ext
1564-
self.tk = _tkinter.create(screenName, baseName, className)
1565-
self.tk.wantobjects(wantobjects)
1567+
interactive = 0
1568+
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk)
1569+
if useTk:
1570+
self._loadtk()
1571+
self.readprofile(baseName, className)
1572+
def loadtk(self):
1573+
if not self._tkloaded:
1574+
self.tk.loadtk()
1575+
self._loadtk()
1576+
def _loadtk(self):
1577+
self._tkloaded = 1
1578+
global _default_root
15661579
if _MacOS and hasattr(_MacOS, 'SchedParams'):
15671580
# Disable event scanning except for Command-Period
15681581
_MacOS.SchedParams(1, 0)
@@ -1587,7 +1600,6 @@ def __init__(self, screenName=None, baseName=None, className='Tk'):
15871600
% str(TkVersion)
15881601
self.tk.createcommand('tkerror', _tkerror)
15891602
self.tk.createcommand('exit', _exit)
1590-
self.readprofile(baseName, className)
15911603
if _support_default_root and not _default_root:
15921604
_default_root = self
15931605
self.protocol("WM_DELETE_WINDOW", self.destroy)
@@ -1629,6 +1641,15 @@ def report_callback_exception(self, exc, val, tb):
16291641
sys.last_value = val
16301642
sys.last_traceback = tb
16311643
traceback.print_exception(exc, val, tb)
1644+
def __getattr__(self, attr):
1645+
"Delegate attribute access to the interpreter object"
1646+
return getattr(self.tk, attr)
1647+
def __hasattr__(self, attr):
1648+
"Delegate attribute access to the interpreter object"
1649+
return hasattr(self.tk, attr)
1650+
def __delattr__(self, attr):
1651+
"Delegate attribute access to the interpreter object"
1652+
return delattr(self.tk, attr)
16321653

16331654
# Ideally, the classes Pack, Place and Grid disappear, the
16341655
# pack/place/grid methods are defined on the Widget class, and
@@ -1644,6 +1665,10 @@ def report_callback_exception(self, exc, val, tb):
16441665
# toplevel and interior widgets). Again, for compatibility, these are
16451666
# copied into the Pack, Place or Grid class.
16461667

1668+
1669+
def Tcl(screenName=None, baseName=None, className='Tk', useTk=0):
1670+
return Tk(screenName, baseName, className, useTk)
1671+
16471672
class Pack:
16481673
"""Geometry manager Pack.
16491674

Lib/test/test_tcl.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env python
2+
3+
import unittest
4+
import os
5+
from Tkinter import Tcl
6+
from _tkinter import TclError
7+
8+
class TclTest(unittest.TestCase):
9+
10+
def setUp(self):
11+
self.interp = Tcl()
12+
13+
def testEval(self):
14+
tcl = self.interp
15+
tcl.eval('set a 1')
16+
self.assertEqual(tcl.eval('set a'),'1')
17+
18+
def testEvalException(self):
19+
tcl = self.interp
20+
self.assertRaises(TclError,tcl.eval,'set a')
21+
22+
def testEvalException2(self):
23+
tcl = self.interp
24+
self.assertRaises(TclError,tcl.eval,'this is wrong')
25+
26+
def testCall(self):
27+
tcl = self.interp
28+
tcl.call('set','a','1')
29+
self.assertEqual(tcl.call('set','a'),'1')
30+
31+
def testCallException(self):
32+
tcl = self.interp
33+
self.assertRaises(TclError,tcl.call,'set','a')
34+
35+
def testCallException2(self):
36+
tcl = self.interp
37+
self.assertRaises(TclError,tcl.call,'this','is','wrong')
38+
39+
def testSetVar(self):
40+
tcl = self.interp
41+
tcl.setvar('a','1')
42+
self.assertEqual(tcl.eval('set a'),'1')
43+
44+
def testSetVarArray(self):
45+
tcl = self.interp
46+
tcl.setvar('a(1)','1')
47+
self.assertEqual(tcl.eval('set a(1)'),'1')
48+
49+
def testGetVar(self):
50+
tcl = self.interp
51+
tcl.eval('set a 1')
52+
self.assertEqual(tcl.getvar('a'),'1')
53+
54+
def testGetVarArray(self):
55+
tcl = self.interp
56+
tcl.eval('set a(1) 1')
57+
self.assertEqual(tcl.getvar('a(1)'),'1')
58+
59+
def testGetVarException(self):
60+
tcl = self.interp
61+
self.assertRaises(TclError,tcl.getvar,'a')
62+
63+
def testGetVarArrayException(self):
64+
tcl = self.interp
65+
self.assertRaises(TclError,tcl.getvar,'a(1)')
66+
67+
def testUnsetVar(self):
68+
tcl = self.interp
69+
tcl.setvar('a',1)
70+
self.assertEqual(tcl.eval('info exists a'),'1')
71+
tcl.unsetvar('a')
72+
self.assertEqual(tcl.eval('info exists a'),'0')
73+
74+
def testUnsetVarArray(self):
75+
tcl = self.interp
76+
tcl.setvar('a(1)',1)
77+
tcl.setvar('a(2)',2)
78+
self.assertEqual(tcl.eval('info exists a(1)'),'1')
79+
self.assertEqual(tcl.eval('info exists a(2)'),'1')
80+
tcl.unsetvar('a(1)')
81+
self.assertEqual(tcl.eval('info exists a(1)'),'0')
82+
self.assertEqual(tcl.eval('info exists a(2)'),'1')
83+
84+
def testUnsetVarException(self):
85+
tcl = self.interp
86+
self.assertRaises(TclError,tcl.unsetvar,'a')
87+
88+
def testEvalFile(self):
89+
tcl = self.interp
90+
filename = "testEvalFile.tcl"
91+
fd = open(filename,'w')
92+
script = """set a 1
93+
set b 2
94+
set c [ expr $a + $b ]
95+
"""
96+
fd.write(script)
97+
fd.close()
98+
tcl.evalfile(filename)
99+
self.assertEqual(tcl.eval('set a'),'1')
100+
self.assertEqual(tcl.eval('set b'),'2')
101+
self.assertEqual(tcl.eval('set c'),'3')
102+
103+
def testEvalFileException(self):
104+
tcl = self.interp
105+
filename = "doesnotexists"
106+
try:
107+
os.remove(filename)
108+
except Exception,e:
109+
pass
110+
self.assertRaises(TclError,tcl.evalfile,filename)
111+
112+
def testPackageRequire(self):
113+
tcl = self.interp
114+
tcl.eval('package require Tclx')
115+
tcl.eval('keylset a b.c 1')
116+
self.assertEqual(tcl.eval('keylget a b.c'),'1')
117+
118+
def testPackageRequireException(self):
119+
tcl = self.interp
120+
self.assertRaises(TclError,tcl.eval,'package require DNE')
121+
122+
def testLoadTk(self):
123+
import os
124+
if 'DISPLAY' not in os.environ:
125+
# skipping test of clean upgradeability
126+
return
127+
tcl = Tcl()
128+
self.assertRaises(TclError,tcl.winfo_geometry)
129+
tcl.loadtk()
130+
self.assertEqual('1x1+0+0', tcl.winfo_geometry())
131+
132+
def testLoadTkFailure(self):
133+
import os
134+
old_display = None
135+
import sys
136+
if sys.platform.startswith('win'):
137+
return # no failure possible on windows?
138+
if 'DISPLAY' in os.environ:
139+
old_display = os.environ['DISPLAY']
140+
del os.environ['DISPLAY']
141+
# on some platforms, deleting environment variables
142+
# doesn't actually carry through to the process level
143+
# because they don't support unsetenv
144+
# If that's the case, abort.
145+
display = os.popen('echo $DISPLAY').read().strip()
146+
if display:
147+
return
148+
try:
149+
tcl = Tcl()
150+
self.assertRaises(TclError, tcl.winfo_geometry)
151+
self.assertRaises(TclError, tcl.loadtk)
152+
finally:
153+
if old_display is not None:
154+
os.environ['DISPLAY'] = old_display
155+
156+
if __name__ == "__main__":
157+
unittest.main()
158+
159+

Modules/_tkinter.c

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -546,15 +546,19 @@ int
546546
Tcl_AppInit(Tcl_Interp *interp)
547547
{
548548
Tk_Window main;
549+
const char * _tkinter_skip_tk_init;
549550

550-
main = Tk_MainWindow(interp);
551551
if (Tcl_Init(interp) == TCL_ERROR) {
552552
PySys_WriteStderr("Tcl_Init error: %s\n", Tcl_GetStringResult(interp));
553553
return TCL_ERROR;
554554
}
555-
if (Tk_Init(interp) == TCL_ERROR) {
556-
PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp));
557-
return TCL_ERROR;
555+
_tkinter_skip_tk_init = Tcl_GetVar(interp, "_tkinter_skip_tk_init", TCL_GLOBAL_ONLY);
556+
if (_tkinter_skip_tk_init == NULL || strcmp(_tkinter_skip_tk_init, "1") != 0) {
557+
main = Tk_MainWindow(interp);
558+
if (Tk_Init(interp) == TCL_ERROR) {
559+
PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp));
560+
return TCL_ERROR;
561+
}
558562
}
559563
return TCL_OK;
560564
}
@@ -572,11 +576,10 @@ static void DisableEventHook(void); /* Forward */
572576

573577
static TkappObject *
574578
Tkapp_New(char *screenName, char *baseName, char *className,
575-
int interactive, int wantobjects)
579+
int interactive, int wantobjects, int wantTk)
576580
{
577581
TkappObject *v;
578582
char *argv0;
579-
580583
v = PyObject_New(TkappObject, &Tkapp_Type);
581584
if (v == NULL)
582585
return NULL;
@@ -637,6 +640,10 @@ Tkapp_New(char *screenName, char *baseName, char *className,
637640
Tcl_SetVar(v->interp, "argv0", argv0, TCL_GLOBAL_ONLY);
638641
ckfree(argv0);
639642

643+
if (! wantTk) {
644+
Tcl_SetVar(v->interp, "_tkinter_skip_tk_init", "1", TCL_GLOBAL_ONLY);
645+
}
646+
640647
if (Tcl_AppInit(v->interp) != TCL_OK)
641648
return (TkappObject *)Tkinter_Error((PyObject *)v);
642649

@@ -2562,6 +2569,41 @@ Tkapp_InterpAddr(PyObject *self, PyObject *args)
25622569
return PyInt_FromLong((long)Tkapp_Interp(self));
25632570
}
25642571

2572+
static PyObject *
2573+
Tkapp_TkInit(PyObject *self, PyObject *args)
2574+
{
2575+
Tcl_Interp *interp = Tkapp_Interp(self);
2576+
Tk_Window main;
2577+
const char * _tk_exists = NULL;
2578+
PyObject *res = NULL;
2579+
int err;
2580+
main = Tk_MainWindow(interp);
2581+
if (!PyArg_ParseTuple(args, ":loadtk"))
2582+
return NULL;
2583+
2584+
/* We want to guard against calling Tk_Init() multiple times */
2585+
CHECK_TCL_APPARTMENT;
2586+
ENTER_TCL
2587+
err = Tcl_Eval(Tkapp_Interp(self), "info exists tk_version");
2588+
ENTER_OVERLAP
2589+
if (err == TCL_ERROR) {
2590+
res = Tkinter_Error(self);
2591+
} else {
2592+
_tk_exists = Tkapp_Result(self);
2593+
}
2594+
LEAVE_OVERLAP_TCL
2595+
if (err == TCL_ERROR) {
2596+
return NULL;
2597+
}
2598+
if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0) {
2599+
if (Tk_Init(interp) == TCL_ERROR) {
2600+
PyErr_SetString(Tkinter_TclError, Tcl_GetStringResult(Tkapp_Interp(self)));
2601+
return NULL;
2602+
}
2603+
}
2604+
Py_INCREF(Py_None);
2605+
return Py_None;
2606+
}
25652607

25662608
static PyObject *
25672609
Tkapp_WantObjects(PyObject *self, PyObject *args)
@@ -2629,6 +2671,7 @@ static PyMethodDef Tkapp_methods[] =
26292671
{"dooneevent", Tkapp_DoOneEvent, METH_VARARGS},
26302672
{"quit", Tkapp_Quit, METH_VARARGS},
26312673
{"interpaddr", Tkapp_InterpAddr, METH_VARARGS},
2674+
{"loadtk", Tkapp_TkInit, METH_VARARGS},
26322675
{NULL, NULL}
26332676
};
26342677

@@ -2793,6 +2836,7 @@ Tkinter_Create(PyObject *self, PyObject *args)
27932836
char *className = NULL;
27942837
int interactive = 0;
27952838
int wantobjects = 0;
2839+
int wantTk = 1; /* If false, then Tk_Init() doesn't get called */
27962840

27972841
baseName = strrchr(Py_GetProgramName(), '/');
27982842
if (baseName != NULL)
@@ -2801,13 +2845,13 @@ Tkinter_Create(PyObject *self, PyObject *args)
28012845
baseName = Py_GetProgramName();
28022846
className = "Tk";
28032847

2804-
if (!PyArg_ParseTuple(args, "|zssii:create",
2848+
if (!PyArg_ParseTuple(args, "|zssiii:create",
28052849
&screenName, &baseName, &className,
2806-
&interactive, &wantobjects))
2850+
&interactive, &wantobjects, &wantTk))
28072851
return NULL;
28082852

28092853
return (PyObject *) Tkapp_New(screenName, baseName, className,
2810-
interactive, wantobjects);
2854+
interactive, wantobjects, wantTk);
28112855
}
28122856

28132857
static PyObject *

0 commit comments

Comments
 (0)