tkinter-web-backend 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tkinter_web/__init__.py +89 -0
- tkinter_web/__main__.py +69 -0
- tkinter_web/_tkinter.py +321 -0
- tkinter_web/_vendor/LICENSE-Python.txt +277 -0
- tkinter_web/_vendor/README.md +9 -0
- tkinter_web/_vendor/tkinter/__init__.py +4988 -0
- tkinter_web/_vendor/tkinter/__main__.py +7 -0
- tkinter_web/_vendor/tkinter/colorchooser.py +86 -0
- tkinter_web/_vendor/tkinter/commondialog.py +53 -0
- tkinter_web/_vendor/tkinter/constants.py +110 -0
- tkinter_web/_vendor/tkinter/dialog.py +49 -0
- tkinter_web/_vendor/tkinter/dnd.py +324 -0
- tkinter_web/_vendor/tkinter/filedialog.py +492 -0
- tkinter_web/_vendor/tkinter/font.py +239 -0
- tkinter_web/_vendor/tkinter/messagebox.py +146 -0
- tkinter_web/_vendor/tkinter/scrolledtext.py +56 -0
- tkinter_web/_vendor/tkinter/simpledialog.py +441 -0
- tkinter_web/_vendor/tkinter/ttk.py +1647 -0
- tkinter_web/app.py +358 -0
- tkinter_web/client/fonts/LICENSE-DejaVu.txt +47 -0
- tkinter_web/client/fonts/mono-bold-italic.woff2 +0 -0
- tkinter_web/client/fonts/mono-bold.woff2 +0 -0
- tkinter_web/client/fonts/mono-italic.woff2 +0 -0
- tkinter_web/client/fonts/mono.woff2 +0 -0
- tkinter_web/client/fonts/sans-bold-italic.woff2 +0 -0
- tkinter_web/client/fonts/sans-bold.woff2 +0 -0
- tkinter_web/client/fonts/sans-italic.woff2 +0 -0
- tkinter_web/client/fonts/sans.woff2 +0 -0
- tkinter_web/client/fonts/serif-bold-italic.woff2 +0 -0
- tkinter_web/client/fonts/serif-bold.woff2 +0 -0
- tkinter_web/client/fonts/serif-italic.woff2 +0 -0
- tkinter_web/client/fonts/serif.woff2 +0 -0
- tkinter_web/client/index.html +17 -0
- tkinter_web/client/js/bitmaps.js +82 -0
- tkinter_web/client/js/main.js +1427 -0
- tkinter_web/client/js/paint.js +694 -0
- tkinter_web/client/tkweb.css +342 -0
- tkinter_web/embed.py +204 -0
- tkinter_web/page.py +49 -0
- tkinter_web/tk/LICENSE-Tk.txt +40 -0
- tkinter_web/tk/__init__.py +11 -0
- tkinter_web/tk/_keysyms.py +547 -0
- tkinter_web/tk/_specs.py +1137 -0
- tkinter_web/tk/colors.json +1 -0
- tkinter_web/tk/dialogs.py +554 -0
- tkinter_web/tk/events.py +458 -0
- tkinter_web/tk/fontmetrics.json +1 -0
- tkinter_web/tk/fonts.py +467 -0
- tkinter_web/tk/geometry.py +1170 -0
- tkinter_web/tk/images.py +875 -0
- tkinter_web/tk/input.py +361 -0
- tkinter_web/tk/interp.py +1287 -0
- tkinter_web/tk/layout.py +192 -0
- tkinter_web/tk/options.py +368 -0
- tkinter_web/tk/render.py +258 -0
- tkinter_web/tk/tcl.py +262 -0
- tkinter_web/tk/ttk/__init__.py +16 -0
- tkinter_web/tk/ttk/style.py +357 -0
- tkinter_web/tk/ttk/treeview.py +817 -0
- tkinter_web/tk/ttk/widgets.py +1818 -0
- tkinter_web/tk/variables.py +152 -0
- tkinter_web/tk/widgets/__init__.py +89 -0
- tkinter_web/tk/widgets/base.py +255 -0
- tkinter_web/tk/widgets/buttons.py +452 -0
- tkinter_web/tk/widgets/canvas.py +1643 -0
- tkinter_web/tk/widgets/entry.py +752 -0
- tkinter_web/tk/widgets/frame.py +189 -0
- tkinter_web/tk/widgets/listbox.py +450 -0
- tkinter_web/tk/widgets/menu.py +723 -0
- tkinter_web/tk/widgets/message.py +85 -0
- tkinter_web/tk/widgets/panedwindow.py +249 -0
- tkinter_web/tk/widgets/scale.py +423 -0
- tkinter_web/tk/widgets/scrollbar.py +224 -0
- tkinter_web/tk/widgets/text.py +1546 -0
- tkinter_web/tk/winfo.py +147 -0
- tkinter_web/tk/wm.py +236 -0
- tkinter_web/transport/__init__.py +1 -0
- tkinter_web/transport/base.py +34 -0
- tkinter_web/transport/port.py +88 -0
- tkinter_web/transport/websocket.py +142 -0
- tkinter_web_backend-0.1.0.dist-info/METADATA +247 -0
- tkinter_web_backend-0.1.0.dist-info/RECORD +84 -0
- tkinter_web_backend-0.1.0.dist-info/WHEEL +4 -0
- tkinter_web_backend-0.1.0.dist-info/licenses/LICENSE +674 -0
tkinter_web/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tkinter-web-backend: a web backend for tkinter.
|
|
3
|
+
|
|
4
|
+
tkinter programs run unchanged; their windows are displayed in a web
|
|
5
|
+
browser. A Tk engine written in Python replaces Tcl/Tk (the _tkinter
|
|
6
|
+
module): it keeps the state of the windows, computes their geometry and
|
|
7
|
+
sends display operations to the browser, which sends back user input.
|
|
8
|
+
|
|
9
|
+
Use it with ``python -m tkinter_web script.py``, or connect it from the
|
|
10
|
+
program (commenting the second line gives back the native Tk)::
|
|
11
|
+
|
|
12
|
+
import tkinter_web
|
|
13
|
+
tkinter_web.install()
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
__all__ = ["install", "uninstall", "is_installed"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def install():
|
|
23
|
+
"""Make tkinter use tkinter-web-backend.
|
|
24
|
+
|
|
25
|
+
It can be called before or after importing tkinter, but before creating
|
|
26
|
+
any window.
|
|
27
|
+
"""
|
|
28
|
+
from . import _tkinter
|
|
29
|
+
|
|
30
|
+
current = sys.modules.get("_tkinter")
|
|
31
|
+
if current is _tkinter:
|
|
32
|
+
return
|
|
33
|
+
tkinter = sys.modules.get("tkinter")
|
|
34
|
+
if tkinter is not None and current is not None:
|
|
35
|
+
if getattr(tkinter, "_default_root", None) is not None:
|
|
36
|
+
raise RuntimeError("a tkinter window already exists with the native Tk: "
|
|
37
|
+
"call tkinter_web.install() before creating it")
|
|
38
|
+
_rebind(current, _tkinter)
|
|
39
|
+
sys.modules["_tkinter"] = _tkinter
|
|
40
|
+
_ensure_tkinter_package()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _rebind(old, new):
|
|
44
|
+
"""Point the modules already imported (tkinter's, the program's after
|
|
45
|
+
``from tkinter import *``...) at the new _tkinter."""
|
|
46
|
+
replace = {id(old): new}
|
|
47
|
+
for name, value in vars(old).items():
|
|
48
|
+
if not name.startswith("__") and hasattr(new, name) and \
|
|
49
|
+
not isinstance(value, (int, float, str)):
|
|
50
|
+
replace[id(value)] = getattr(new, name)
|
|
51
|
+
for name, module in list(sys.modules.items()):
|
|
52
|
+
namespace = getattr(module, "__dict__", None)
|
|
53
|
+
if namespace is None or module is old:
|
|
54
|
+
continue
|
|
55
|
+
for key, value in list(namespace.items()):
|
|
56
|
+
if id(value) in replace:
|
|
57
|
+
namespace[key] = replace[id(value)]
|
|
58
|
+
tkinter = sys.modules["tkinter"]
|
|
59
|
+
tkinter.TkVersion = float(new.TK_VERSION)
|
|
60
|
+
tkinter.TclVersion = float(new.TCL_VERSION)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def uninstall():
|
|
64
|
+
from . import _tkinter
|
|
65
|
+
|
|
66
|
+
if sys.modules.get("_tkinter") is _tkinter:
|
|
67
|
+
del sys.modules["_tkinter"]
|
|
68
|
+
for name in [n for n in sys.modules if n == "tkinter" or n.startswith("tkinter.")]:
|
|
69
|
+
del sys.modules[name]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def is_installed():
|
|
73
|
+
from . import _tkinter
|
|
74
|
+
|
|
75
|
+
return sys.modules.get("_tkinter") is _tkinter
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _ensure_tkinter_package():
|
|
79
|
+
"""Use the bundled copy of tkinter if the Python installation has none."""
|
|
80
|
+
import importlib.util
|
|
81
|
+
import os
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
|
|
84
|
+
forced = os.environ.get("TKINTER_WEB_VENDORED", "") not in ("", "0")
|
|
85
|
+
if not forced and importlib.util.find_spec("tkinter") is not None:
|
|
86
|
+
return
|
|
87
|
+
vendor = str(Path(__file__).parent / "_vendor")
|
|
88
|
+
if vendor not in sys.path:
|
|
89
|
+
sys.path.insert(0, vendor)
|
tkinter_web/__main__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Run a tkinter program with tkinter-web-backend:
|
|
3
|
+
|
|
4
|
+
python -m tkinter_web [options] script.py [args...]
|
|
5
|
+
python -m tkinter_web [options] -m module [args...]
|
|
6
|
+
python -m tkinter_web [options] (interactive console)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import code
|
|
11
|
+
import os
|
|
12
|
+
import runpy
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
from . import __version__, install
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv=None):
|
|
19
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
20
|
+
parser = argparse.ArgumentParser(
|
|
21
|
+
prog="python -m tkinter_web",
|
|
22
|
+
description="Run a tkinter program in a web browser.",
|
|
23
|
+
usage="%(prog)s [options] [-m module | script] [args ...]")
|
|
24
|
+
parser.add_argument("-m", dest="module", help="run a library module as a script")
|
|
25
|
+
parser.add_argument("--port", type=int, help="port of the local server (default: any)")
|
|
26
|
+
parser.add_argument("--no-browser", action="store_true",
|
|
27
|
+
help="do not open a web browser")
|
|
28
|
+
parser.add_argument("--version", action="version", version=f"tkinter-web-backend {__version__}")
|
|
29
|
+
# options end at the script or at the module name: what follows is theirs
|
|
30
|
+
options, rest = [], []
|
|
31
|
+
i = 0
|
|
32
|
+
while i < len(argv):
|
|
33
|
+
a = argv[i]
|
|
34
|
+
if a == "-m" and i + 1 < len(argv):
|
|
35
|
+
options += argv[i:i + 2]
|
|
36
|
+
rest = argv[i + 2:]
|
|
37
|
+
break
|
|
38
|
+
if a == "--port" and i + 1 < len(argv):
|
|
39
|
+
options += argv[i:i + 2]
|
|
40
|
+
i += 2
|
|
41
|
+
continue
|
|
42
|
+
if a.startswith("-") and a != "-":
|
|
43
|
+
options.append(a)
|
|
44
|
+
i += 1
|
|
45
|
+
continue
|
|
46
|
+
rest = argv[i:]
|
|
47
|
+
break
|
|
48
|
+
ns = parser.parse_args(options)
|
|
49
|
+
if ns.port is not None:
|
|
50
|
+
os.environ["TKINTER_WEB_PORT"] = str(ns.port)
|
|
51
|
+
if ns.no_browser:
|
|
52
|
+
os.environ["TKINTER_WEB_BROWSER"] = "0"
|
|
53
|
+
install()
|
|
54
|
+
if ns.module:
|
|
55
|
+
sys.argv = [ns.module] + rest
|
|
56
|
+
runpy.run_module(ns.module, run_name="__main__", alter_sys=True)
|
|
57
|
+
elif rest:
|
|
58
|
+
script = rest[0]
|
|
59
|
+
sys.argv = rest
|
|
60
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(script)))
|
|
61
|
+
runpy.run_path(script, run_name="__main__")
|
|
62
|
+
else:
|
|
63
|
+
sys.ps1 = ">>> "
|
|
64
|
+
code.interact(banner=f"tkinter-web-backend {__version__} (Python {sys.version.split()[0]})",
|
|
65
|
+
local={"__name__": "__main__"})
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
tkinter_web/_tkinter.py
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A replacement of the _tkinter C extension module.
|
|
3
|
+
|
|
4
|
+
tkinter (the pure Python part of the standard library) talks to Tcl/Tk
|
|
5
|
+
through this module only: here, ``create()`` returns an application object
|
|
6
|
+
whose commands are executed by the tkinter_web engine, rendered in a web
|
|
7
|
+
browser instead of a native window.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import threading
|
|
12
|
+
|
|
13
|
+
from .tk.tcl import TclError, get_boolean, get_double, get_int, split_list, to_str
|
|
14
|
+
from .tk.interp import TK_VERSION
|
|
15
|
+
|
|
16
|
+
__all__ = ["create", "TclError", "TkappType", "Tcl_Obj"]
|
|
17
|
+
|
|
18
|
+
TK_VERSION = TK_VERSION
|
|
19
|
+
TCL_VERSION = TK_VERSION
|
|
20
|
+
|
|
21
|
+
READABLE = 2
|
|
22
|
+
WRITABLE = 4
|
|
23
|
+
EXCEPTION = 8
|
|
24
|
+
|
|
25
|
+
WINDOW_EVENTS = 4
|
|
26
|
+
FILE_EVENTS = 8
|
|
27
|
+
TIMER_EVENTS = 16
|
|
28
|
+
IDLE_EVENTS = 32
|
|
29
|
+
ALL_EVENTS = -3
|
|
30
|
+
DONT_WAIT = 2
|
|
31
|
+
|
|
32
|
+
_busywaitinterval = 20
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def setbusywaitinterval(ms):
|
|
36
|
+
global _busywaitinterval
|
|
37
|
+
_busywaitinterval = int(ms)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def getbusywaitinterval():
|
|
41
|
+
return _busywaitinterval
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Tcl_Obj:
|
|
45
|
+
"""Tcl objects returned without conversion (rare with tkinter_web)."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, value, typename="string"):
|
|
48
|
+
self._value = value
|
|
49
|
+
self.typename = typename
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def string(self):
|
|
53
|
+
return to_str(self._value)
|
|
54
|
+
|
|
55
|
+
def __str__(self):
|
|
56
|
+
return to_str(self._value)
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
return f"<{self.typename} object: {self.string!r}>"
|
|
60
|
+
|
|
61
|
+
def __eq__(self, other):
|
|
62
|
+
if isinstance(other, Tcl_Obj):
|
|
63
|
+
return self.string == other.string
|
|
64
|
+
return NotImplemented
|
|
65
|
+
|
|
66
|
+
def __hash__(self):
|
|
67
|
+
return hash(self.string)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TkttObject:
|
|
71
|
+
def __init__(self, interp, ident):
|
|
72
|
+
self._interp = interp
|
|
73
|
+
self._id = ident
|
|
74
|
+
|
|
75
|
+
def deletetimerhandler(self):
|
|
76
|
+
self._interp.call("after", "cancel", self._id)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _value(v):
|
|
80
|
+
"""Values returned to tkinter (Python objects, as with wantobjects)."""
|
|
81
|
+
if isinstance(v, list):
|
|
82
|
+
return tuple(_value(x) for x in v)
|
|
83
|
+
if isinstance(v, tuple):
|
|
84
|
+
return tuple(_value(x) for x in v)
|
|
85
|
+
if v is None:
|
|
86
|
+
return ""
|
|
87
|
+
return v
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_NATIVE = (str, int, float, bytes, bytearray, Tcl_Obj)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _arg(a):
|
|
94
|
+
"""Arguments of calls as _tkinter converts them (objects become strings)."""
|
|
95
|
+
if isinstance(a, _NATIVE):
|
|
96
|
+
return a
|
|
97
|
+
if isinstance(a, (tuple, list)):
|
|
98
|
+
return tuple(_arg(x) for x in a)
|
|
99
|
+
if a is None:
|
|
100
|
+
return ""
|
|
101
|
+
return str(a)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TkappType:
|
|
105
|
+
"""The Tcl/Tk application of a Tk() window."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, screenName=None, baseName=None, className="Tk", interactive=False,
|
|
108
|
+
wantobjects=1, useTk=True, sync=False, use=None):
|
|
109
|
+
from .app import get_app
|
|
110
|
+
self._app = get_app()
|
|
111
|
+
self._interp = self._app.new_interp(className or "Tk", baseName or "tk")
|
|
112
|
+
if not useTk:
|
|
113
|
+
# Tcl(): an interpreter without a main window
|
|
114
|
+
self._interp.root.wm["state"] = "withdrawn"
|
|
115
|
+
self._wantobjects = wantobjects
|
|
116
|
+
self._trace = None
|
|
117
|
+
self._thread = threading.get_ident()
|
|
118
|
+
|
|
119
|
+
# -- evaluation
|
|
120
|
+
|
|
121
|
+
def call(self, *args):
|
|
122
|
+
if len(args) == 1 and isinstance(args[0], tuple):
|
|
123
|
+
args = args[0]
|
|
124
|
+
if None in args:
|
|
125
|
+
# as _tkinter: None ends the argument list
|
|
126
|
+
args = args[: args.index(None)]
|
|
127
|
+
args = tuple(_arg(a) for a in args)
|
|
128
|
+
if self._trace is not None:
|
|
129
|
+
self._trace(args)
|
|
130
|
+
with self._app.lock:
|
|
131
|
+
return _value(self._interp.call(*args))
|
|
132
|
+
|
|
133
|
+
def eval(self, script):
|
|
134
|
+
if self._trace is not None:
|
|
135
|
+
self._trace((script,))
|
|
136
|
+
with self._app.lock:
|
|
137
|
+
return _value(self._interp.eval(script))
|
|
138
|
+
|
|
139
|
+
def evalfile(self, filename):
|
|
140
|
+
return ""
|
|
141
|
+
|
|
142
|
+
def record(self, script):
|
|
143
|
+
return self.eval(script)
|
|
144
|
+
|
|
145
|
+
def adderrorinfo(self, msg):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
# -- variables
|
|
149
|
+
|
|
150
|
+
def _varname(self, args):
|
|
151
|
+
if len(args) >= 2 and isinstance(args[1], str) and args[1] and len(args) == 3:
|
|
152
|
+
return f"{args[0]}({args[1]})"
|
|
153
|
+
return to_str(args[0])
|
|
154
|
+
|
|
155
|
+
def setvar(self, *args):
|
|
156
|
+
if len(args) == 3:
|
|
157
|
+
name, value = f"{args[0]}({args[1]})", args[2]
|
|
158
|
+
elif len(args) == 2:
|
|
159
|
+
name, value = to_str(args[0]), args[1]
|
|
160
|
+
else:
|
|
161
|
+
raise TypeError("setvar requires 2 or 3 arguments")
|
|
162
|
+
with self._app.lock:
|
|
163
|
+
self._interp.variables.set(name, value)
|
|
164
|
+
|
|
165
|
+
globalsetvar = setvar
|
|
166
|
+
|
|
167
|
+
def getvar(self, *args):
|
|
168
|
+
name = f"{args[0]}({args[1]})" if len(args) == 2 and args[1] else to_str(args[0])
|
|
169
|
+
with self._app.lock:
|
|
170
|
+
return _value(self._interp.variables.get(name))
|
|
171
|
+
|
|
172
|
+
globalgetvar = getvar
|
|
173
|
+
|
|
174
|
+
def unsetvar(self, *args):
|
|
175
|
+
name = f"{args[0]}({args[1]})" if len(args) == 2 and args[1] else to_str(args[0])
|
|
176
|
+
with self._app.lock:
|
|
177
|
+
self._interp.variables.unset(name)
|
|
178
|
+
|
|
179
|
+
globalunsetvar = unsetvar
|
|
180
|
+
|
|
181
|
+
# -- conversions
|
|
182
|
+
|
|
183
|
+
def getint(self, arg):
|
|
184
|
+
if isinstance(arg, Tcl_Obj):
|
|
185
|
+
arg = arg.string
|
|
186
|
+
if isinstance(arg, float):
|
|
187
|
+
raise TypeError("getint() argument must be str or int")
|
|
188
|
+
return get_int(arg)
|
|
189
|
+
|
|
190
|
+
def getdouble(self, arg):
|
|
191
|
+
if isinstance(arg, Tcl_Obj):
|
|
192
|
+
arg = arg.string
|
|
193
|
+
return get_double(arg)
|
|
194
|
+
|
|
195
|
+
def getboolean(self, arg):
|
|
196
|
+
if isinstance(arg, Tcl_Obj):
|
|
197
|
+
arg = arg.string
|
|
198
|
+
return bool(get_boolean(arg))
|
|
199
|
+
|
|
200
|
+
def exprstring(self, s):
|
|
201
|
+
return to_str(s)
|
|
202
|
+
|
|
203
|
+
def exprlong(self, s):
|
|
204
|
+
return get_int(s)
|
|
205
|
+
|
|
206
|
+
def exprdouble(self, s):
|
|
207
|
+
return get_double(s)
|
|
208
|
+
|
|
209
|
+
def exprboolean(self, s):
|
|
210
|
+
return bool(get_boolean(s))
|
|
211
|
+
|
|
212
|
+
def splitlist(self, arg):
|
|
213
|
+
if isinstance(arg, Tcl_Obj):
|
|
214
|
+
arg = arg.string
|
|
215
|
+
if isinstance(arg, (tuple, list)):
|
|
216
|
+
return tuple(arg)
|
|
217
|
+
if isinstance(arg, str):
|
|
218
|
+
return split_list(arg)
|
|
219
|
+
if isinstance(arg, (bytes, bytearray)):
|
|
220
|
+
return split_list(arg.decode())
|
|
221
|
+
return (arg,)
|
|
222
|
+
|
|
223
|
+
def split(self, arg):
|
|
224
|
+
if isinstance(arg, str):
|
|
225
|
+
items = split_list(arg)
|
|
226
|
+
if len(items) == 1 and items[0] == arg:
|
|
227
|
+
return arg
|
|
228
|
+
return tuple(self.split(i) if " " in i else i for i in items)
|
|
229
|
+
return arg
|
|
230
|
+
|
|
231
|
+
# -- commands
|
|
232
|
+
|
|
233
|
+
def createcommand(self, name, func):
|
|
234
|
+
if not callable(func):
|
|
235
|
+
raise TypeError("command not callable")
|
|
236
|
+
with self._app.lock:
|
|
237
|
+
self._interp.commands[to_str(name)] = func
|
|
238
|
+
|
|
239
|
+
def deletecommand(self, name):
|
|
240
|
+
with self._app.lock:
|
|
241
|
+
if self._interp.commands.pop(to_str(name), None) is None:
|
|
242
|
+
raise TclError(f"can't delete Tcl command")
|
|
243
|
+
|
|
244
|
+
def createfilehandler(self, file, mask, func):
|
|
245
|
+
raise NotImplementedError("createfilehandler is not supported by tkinter-web-backend")
|
|
246
|
+
|
|
247
|
+
def deletefilehandler(self, file):
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
def createtimerhandler(self, milliseconds, func):
|
|
251
|
+
name = f"::tkinter_web_timer{id(func)}"
|
|
252
|
+
interp = self._interp
|
|
253
|
+
|
|
254
|
+
def run():
|
|
255
|
+
interp.commands.pop(name, None)
|
|
256
|
+
func()
|
|
257
|
+
|
|
258
|
+
with self._app.lock:
|
|
259
|
+
interp.commands[name] = run
|
|
260
|
+
ident = interp.call("after", milliseconds, name)
|
|
261
|
+
return TkttObject(interp, ident)
|
|
262
|
+
|
|
263
|
+
# -- event loop
|
|
264
|
+
|
|
265
|
+
def mainloop(self, threshold=0):
|
|
266
|
+
if threading.get_ident() != self._thread:
|
|
267
|
+
raise RuntimeError("Calling Tcl from different apartment")
|
|
268
|
+
self._app.mainloop(self._interp, threshold)
|
|
269
|
+
|
|
270
|
+
def dooneevent(self, flags=0):
|
|
271
|
+
self._app.step(block=not flags & DONT_WAIT)
|
|
272
|
+
return 1
|
|
273
|
+
|
|
274
|
+
def quit(self):
|
|
275
|
+
self._app.quit()
|
|
276
|
+
|
|
277
|
+
def interpaddr(self):
|
|
278
|
+
return id(self._interp)
|
|
279
|
+
|
|
280
|
+
def loadtk(self):
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
def willdispatch(self):
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
def wantobjects(self, *args):
|
|
287
|
+
if args:
|
|
288
|
+
self._wantobjects = args[0]
|
|
289
|
+
return None
|
|
290
|
+
return self._wantobjects
|
|
291
|
+
|
|
292
|
+
def settrace(self, func):
|
|
293
|
+
self._trace = func
|
|
294
|
+
|
|
295
|
+
def gettrace(self):
|
|
296
|
+
return self._trace
|
|
297
|
+
|
|
298
|
+
# access to the engine (tests, debugging)
|
|
299
|
+
@property
|
|
300
|
+
def tkinter_web_interp(self):
|
|
301
|
+
return self._interp
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def create(screenName=None, baseName=None, className="Tk", interactive=False,
|
|
305
|
+
wantobjects=1, useTk=True, sync=False, use=None):
|
|
306
|
+
return TkappType(screenName, baseName, className, interactive, wantobjects,
|
|
307
|
+
useTk, sync, use)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _flatten(item):
|
|
311
|
+
res = []
|
|
312
|
+
|
|
313
|
+
def walk(x):
|
|
314
|
+
if isinstance(x, (tuple, list)):
|
|
315
|
+
for y in x:
|
|
316
|
+
walk(y)
|
|
317
|
+
elif x is not None:
|
|
318
|
+
res.append(x)
|
|
319
|
+
|
|
320
|
+
walk(item)
|
|
321
|
+
return tuple(res)
|