]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Implementation of patch 869468
authorDavid Ascher <david.ascher@gmail.com>
Wed, 18 Feb 2004 05:59:53 +0000 (05:59 +0000)
committerDavid Ascher <david.ascher@gmail.com>
Wed, 18 Feb 2004 05:59:53 +0000 (05:59 +0000)
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.

Doc/lib/tkinter.tex
Lib/lib-tk/Tkinter.py
Lib/test/test_tcl.py [new file with mode: 0644]
Modules/_tkinter.c
Modules/tkappinit.c

index e0c613f28aa083d6de6b0d78b85be2f68c8fb486..55f822ffd33b4aa2c47e615e65008b5eae80817f 100644 (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}
index 67e942e18ad1b35c02b9dfba28ded99e29fcf27f..5ad065da2d94f2ee9c4f565794b5cc232d097f0c 100644 (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.
 
diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
new file mode 100644 (file)
index 0000000..3e0a781
--- /dev/null
@@ -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()
+
+
index e6f89531da9a4c57cc3f77752b252d59d9eafc20..39a93da84f038f45b3b43f9a4b992e8b0f82a7ad 100644 (file)
@@ -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 *
index 96c545d7e9a4873d81e7100c895d4821a3c54696..42b6bb883046847be634cb452e278f8460965c36 100644 (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);