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.
Files changed (84) hide show
  1. tkinter_web/__init__.py +89 -0
  2. tkinter_web/__main__.py +69 -0
  3. tkinter_web/_tkinter.py +321 -0
  4. tkinter_web/_vendor/LICENSE-Python.txt +277 -0
  5. tkinter_web/_vendor/README.md +9 -0
  6. tkinter_web/_vendor/tkinter/__init__.py +4988 -0
  7. tkinter_web/_vendor/tkinter/__main__.py +7 -0
  8. tkinter_web/_vendor/tkinter/colorchooser.py +86 -0
  9. tkinter_web/_vendor/tkinter/commondialog.py +53 -0
  10. tkinter_web/_vendor/tkinter/constants.py +110 -0
  11. tkinter_web/_vendor/tkinter/dialog.py +49 -0
  12. tkinter_web/_vendor/tkinter/dnd.py +324 -0
  13. tkinter_web/_vendor/tkinter/filedialog.py +492 -0
  14. tkinter_web/_vendor/tkinter/font.py +239 -0
  15. tkinter_web/_vendor/tkinter/messagebox.py +146 -0
  16. tkinter_web/_vendor/tkinter/scrolledtext.py +56 -0
  17. tkinter_web/_vendor/tkinter/simpledialog.py +441 -0
  18. tkinter_web/_vendor/tkinter/ttk.py +1647 -0
  19. tkinter_web/app.py +358 -0
  20. tkinter_web/client/fonts/LICENSE-DejaVu.txt +47 -0
  21. tkinter_web/client/fonts/mono-bold-italic.woff2 +0 -0
  22. tkinter_web/client/fonts/mono-bold.woff2 +0 -0
  23. tkinter_web/client/fonts/mono-italic.woff2 +0 -0
  24. tkinter_web/client/fonts/mono.woff2 +0 -0
  25. tkinter_web/client/fonts/sans-bold-italic.woff2 +0 -0
  26. tkinter_web/client/fonts/sans-bold.woff2 +0 -0
  27. tkinter_web/client/fonts/sans-italic.woff2 +0 -0
  28. tkinter_web/client/fonts/sans.woff2 +0 -0
  29. tkinter_web/client/fonts/serif-bold-italic.woff2 +0 -0
  30. tkinter_web/client/fonts/serif-bold.woff2 +0 -0
  31. tkinter_web/client/fonts/serif-italic.woff2 +0 -0
  32. tkinter_web/client/fonts/serif.woff2 +0 -0
  33. tkinter_web/client/index.html +17 -0
  34. tkinter_web/client/js/bitmaps.js +82 -0
  35. tkinter_web/client/js/main.js +1427 -0
  36. tkinter_web/client/js/paint.js +694 -0
  37. tkinter_web/client/tkweb.css +342 -0
  38. tkinter_web/embed.py +204 -0
  39. tkinter_web/page.py +49 -0
  40. tkinter_web/tk/LICENSE-Tk.txt +40 -0
  41. tkinter_web/tk/__init__.py +11 -0
  42. tkinter_web/tk/_keysyms.py +547 -0
  43. tkinter_web/tk/_specs.py +1137 -0
  44. tkinter_web/tk/colors.json +1 -0
  45. tkinter_web/tk/dialogs.py +554 -0
  46. tkinter_web/tk/events.py +458 -0
  47. tkinter_web/tk/fontmetrics.json +1 -0
  48. tkinter_web/tk/fonts.py +467 -0
  49. tkinter_web/tk/geometry.py +1170 -0
  50. tkinter_web/tk/images.py +875 -0
  51. tkinter_web/tk/input.py +361 -0
  52. tkinter_web/tk/interp.py +1287 -0
  53. tkinter_web/tk/layout.py +192 -0
  54. tkinter_web/tk/options.py +368 -0
  55. tkinter_web/tk/render.py +258 -0
  56. tkinter_web/tk/tcl.py +262 -0
  57. tkinter_web/tk/ttk/__init__.py +16 -0
  58. tkinter_web/tk/ttk/style.py +357 -0
  59. tkinter_web/tk/ttk/treeview.py +817 -0
  60. tkinter_web/tk/ttk/widgets.py +1818 -0
  61. tkinter_web/tk/variables.py +152 -0
  62. tkinter_web/tk/widgets/__init__.py +89 -0
  63. tkinter_web/tk/widgets/base.py +255 -0
  64. tkinter_web/tk/widgets/buttons.py +452 -0
  65. tkinter_web/tk/widgets/canvas.py +1643 -0
  66. tkinter_web/tk/widgets/entry.py +752 -0
  67. tkinter_web/tk/widgets/frame.py +189 -0
  68. tkinter_web/tk/widgets/listbox.py +450 -0
  69. tkinter_web/tk/widgets/menu.py +723 -0
  70. tkinter_web/tk/widgets/message.py +85 -0
  71. tkinter_web/tk/widgets/panedwindow.py +249 -0
  72. tkinter_web/tk/widgets/scale.py +423 -0
  73. tkinter_web/tk/widgets/scrollbar.py +224 -0
  74. tkinter_web/tk/widgets/text.py +1546 -0
  75. tkinter_web/tk/winfo.py +147 -0
  76. tkinter_web/tk/wm.py +236 -0
  77. tkinter_web/transport/__init__.py +1 -0
  78. tkinter_web/transport/base.py +34 -0
  79. tkinter_web/transport/port.py +88 -0
  80. tkinter_web/transport/websocket.py +142 -0
  81. tkinter_web_backend-0.1.0.dist-info/METADATA +247 -0
  82. tkinter_web_backend-0.1.0.dist-info/RECORD +84 -0
  83. tkinter_web_backend-0.1.0.dist-info/WHEEL +4 -0
  84. tkinter_web_backend-0.1.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,189 @@
1
+ """
2
+ Frames: frame, toplevel (and the root window) and labelframe.
3
+ """
4
+
5
+ from ..tcl import to_str
6
+ from .base import Widget
7
+
8
+ LABELSPACING = 1
9
+ LABELMARGIN = 4
10
+
11
+
12
+ class Frame(Widget):
13
+ kind = "frame"
14
+ default_class = "Frame"
15
+
16
+ def internal_border(self):
17
+ o = self.options
18
+ b = o.pixels("-borderwidth") + o.pixels("-highlightthickness")
19
+ px, py = o.pixels("-padx"), o.pixels("-pady")
20
+ return (b + px, b + py, b + px, b + py)
21
+
22
+ def natural_size(self):
23
+ w, h = self.options.pixels("-width"), self.options.pixels("-height")
24
+ if w > 0 or h > 0:
25
+ return max(w, 0), max(h, 0)
26
+ return None # no request of its own
27
+
28
+ def display(self):
29
+ return self.common_display()
30
+
31
+
32
+ class Toplevel(Frame):
33
+ kind = "toplevel"
34
+ default_class = "Toplevel"
35
+
36
+ def __init__(self, interp, path, parent, args):
37
+ self.wm = {
38
+ "title": None, "geometry": None, "position": None, "state": "normal",
39
+ "resizable": (True, True), "minsize": (1, 1), "maxsize": None,
40
+ "protocols": {}, "attributes": {"-alpha": 1.0, "-topmost": 0,
41
+ "-fullscreen": 0, "-type": "",
42
+ "-zoomed": 0},
43
+ "overrideredirect": False, "transient": "", "iconphoto": None,
44
+ "group": "", "user_size": None, "grid": None, "aspect": None,
45
+ "iconname": "",
46
+ }
47
+ super().__init__(interp, path, parent, args)
48
+ if path == ".":
49
+ self.tk_class = interp.class_name
50
+ self.wm["title"] = None
51
+
52
+ def is_toplevel_like(self):
53
+ return True
54
+
55
+ def natural_size(self):
56
+ w, h = self.options.pixels("-width"), self.options.pixels("-height")
57
+ if w > 0 or h > 0:
58
+ return max(w, 0), max(h, 0)
59
+ return 200, 200
60
+
61
+ def title(self):
62
+ t = self.wm["title"]
63
+ if t is None:
64
+ return self.interp.app_name if self.path == "." else self.name
65
+ return t
66
+
67
+ def viewable(self):
68
+ return self.wm["state"] == "normal"
69
+
70
+
71
+ class Labelframe(Frame):
72
+ kind = "labelframe"
73
+ default_class = "Labelframe"
74
+
75
+ def _label_size(self):
76
+ o = self.options
77
+ text = o.str("-text")
78
+ lw = o.str("-labelwidget")
79
+ if lw:
80
+ try:
81
+ w = self.interp.widget(lw)
82
+ return w.req_w, w.req_h, True
83
+ except Exception:
84
+ return 0, 0, False
85
+ if text:
86
+ font = self.interp.font(o["-font"])
87
+ layout = font.layout(text, 0, "center")
88
+ return layout.width + 2 * LABELSPACING, layout.height + 2 * LABELSPACING, True
89
+ return 0, 0, False
90
+
91
+ def _anchor(self):
92
+ return self.options.str("-labelanchor") or "nw"
93
+
94
+ def internal_border(self):
95
+ o = self.options
96
+ bw = o.pixels("-borderwidth")
97
+ b = bw + o.pixels("-highlightthickness")
98
+ px, py = o.pixels("-padx"), o.pixels("-pady")
99
+ left, right, top, bottom = b + px, b + px, b + py, b + py
100
+ lw, lh, any_label = self._label_size()
101
+ if any_label:
102
+ a = self._anchor()
103
+ if a[0] in "n" or a in ("n", "ne", "nw"):
104
+ top += max(lh, bw) - bw
105
+ elif a[0] == "s":
106
+ bottom += max(lh, bw) - bw
107
+ elif a[0] == "e":
108
+ right += max(lw, bw) - bw
109
+ else:
110
+ left += max(lw, bw) - bw
111
+ return (left, top, right, bottom)
112
+
113
+ def min_req(self):
114
+ o = self.options
115
+ lw, lh, any_label = self._label_size()
116
+ if not any_label:
117
+ return 0, 0
118
+ bw, ht = o.pixels("-borderwidth"), o.pixels("-highlightthickness")
119
+ padding = ht + (bw + LABELMARGIN if bw > 0 else 0)
120
+ padding *= 2
121
+ if self._anchor()[0] in "ns":
122
+ return lw + padding, max(lh, bw) + bw + ht
123
+ return max(lw, bw) + bw + ht, lh + padding
124
+
125
+ def label_box(self):
126
+ """(x, y, w, h) of the label."""
127
+ o = self.options
128
+ lw, lh, any_label = self._label_size()
129
+ if not any_label:
130
+ return None
131
+ bw, ht = o.pixels("-borderwidth"), o.pixels("-highlightthickness")
132
+ a = self._anchor()
133
+ padding = ht + (bw + LABELMARGIN if bw > 0 else 0)
134
+ W, H = self.width, self.height
135
+ if a[0] in "ns":
136
+ lw = min(lw, max(W - 2 * padding, 1))
137
+ y = ht if a[0] == "n" else H - lh - ht
138
+ sub = a[1:] or "c"
139
+ if sub == "w":
140
+ x = padding
141
+ elif sub == "e":
142
+ x = W - padding - lw
143
+ else:
144
+ x = (W - lw) // 2
145
+ else:
146
+ lh = min(lh, max(H - 2 * padding, 1))
147
+ x = ht if a[0] == "w" else W - lw - ht
148
+ sub = a[1:] or "c"
149
+ if sub == "n":
150
+ y = padding
151
+ elif sub == "s":
152
+ y = H - padding - lh
153
+ else:
154
+ y = (H - lh) // 2
155
+ return x, y, lw, lh
156
+
157
+ def display(self):
158
+ o = self.options
159
+ bg = o.color("-background") or "#d9d9d9"
160
+ bw, ht = o.pixels("-borderwidth"), o.pixels("-highlightthickness")
161
+ res = [["fill", 0, 0, self.width, self.height, bg]]
162
+ box = self.label_box()
163
+ bx, by, bwid, bhei = ht, ht, self.width - 2 * ht, self.height - 2 * ht
164
+ if box is not None:
165
+ lx, ly, lw, lh = box
166
+ a = self._anchor()
167
+ if a[0] == "n":
168
+ by = ht + (lh - bw) // 2
169
+ bhei = self.height - by - ht
170
+ elif a[0] == "s":
171
+ bhei = self.height - ht - (lh - bw) // 2 - by
172
+ elif a[0] == "w":
173
+ bx = ht + (lw - bw) // 2
174
+ bwid = self.width - bx - ht
175
+ else:
176
+ bwid = self.width - ht - (lw - bw) // 2 - bx
177
+ relief = o.str("-relief")
178
+ if bw > 0 and relief != "flat":
179
+ res.append(["border", bx, by, bwid, bhei, bw, relief, bg])
180
+ if box is not None and not o.str("-labelwidget"):
181
+ lx, ly, lw, lh = box
182
+ res.append(["fill", lx, ly, lw, lh, bg])
183
+ font = self.interp.font(o["-font"])
184
+ layout = font.layout(o.str("-text"), 0, "center")
185
+ for x, y, text, _ in layout.runs(lx + LABELSPACING, ly + LABELSPACING):
186
+ res.append(["text", x, y, text, font.css(),
187
+ o.color("-foreground") or "#000000", -1])
188
+ res.extend(self.highlight_display())
189
+ return res
@@ -0,0 +1,450 @@
1
+ """
2
+ Listbox (tkListbox.c and library/listbox.tcl).
3
+ """
4
+
5
+ from ..events import Event
6
+ from ..options import OptionSet
7
+ from ..tcl import TclError, get_double, get_int, split_list, to_str
8
+ from .base import Widget
9
+
10
+ _ITEM_SPECS = (
11
+ ("-background", "", "", ""), ("-foreground", "", "", ""),
12
+ ("-selectbackground", "", "", ""), ("-selectforeground", "", "", ""),
13
+ )
14
+
15
+
16
+ class Listbox(Widget):
17
+ kind = "listbox"
18
+ default_class = "Listbox"
19
+ traversal = True
20
+
21
+ def __init__(self, interp, path, parent, args):
22
+ self.items = []
23
+ self.item_options = {} # index -> dict
24
+ self.selection = set()
25
+ self.active = 0
26
+ self.anchor = 0
27
+ self.top = 0 # first visible line
28
+ self.xoffset = 0
29
+ self.drag_start = None
30
+ super().__init__(interp, path, parent, args)
31
+
32
+ def options_changed(self, names, creating):
33
+ if "-listvariable" in names:
34
+ self.interp.variables.relink(self, "-listvariable")
35
+ var = self.options.str("-listvariable")
36
+ if var:
37
+ value = self.interp.variables.peek(var)
38
+ if value is None:
39
+ self._sync_var()
40
+ else:
41
+ self.items = [to_str(x) for x in split_list(value)]
42
+ super().options_changed(names, creating)
43
+
44
+ def variable_changed(self, option):
45
+ var = self.options.str("-listvariable")
46
+ value = self.interp.variables.peek(var)
47
+ self.items = [] if value is None else [to_str(x) for x in split_list(value)]
48
+ self.selection = {i for i in self.selection if i < len(self.items)}
49
+ self.mark_dirty(layout=True)
50
+ self._yscroll()
51
+
52
+ def _sync_var(self):
53
+ var = self.options.str("-listvariable")
54
+ if var:
55
+ self.interp.variables.set(var, tuple(self.items), notify_widget=self)
56
+
57
+ # -- geometry
58
+
59
+ def font(self):
60
+ return self.interp.font(self.options["-font"])
61
+
62
+ def inset(self):
63
+ o = self.options
64
+ return o.pixels("-highlightthickness") + o.pixels("-borderwidth")
65
+
66
+ def line_height(self):
67
+ return self.font().linespace + 1 + 2 * self.options.pixels("-selectborderwidth")
68
+
69
+ def natural_size(self):
70
+ o = self.options
71
+ font = self.font()
72
+ unit = font.measure("0") or 1
73
+ width = o.int("-width")
74
+ if width <= 0:
75
+ maxw = max((font.measure(t) for t in self.items), default=0)
76
+ width = max((maxw + unit - 1) // unit, 1)
77
+ sbw = o.pixels("-selectborderwidth")
78
+ inset = self.inset()
79
+ height = o.int("-height")
80
+ if height <= 0:
81
+ height = max(len(self.items), 1)
82
+ return width * unit + 2 * inset + 2 * sbw, height * self.line_height() + 2 * inset
83
+
84
+ def visible_lines(self):
85
+ h = self.height - 2 * self.inset()
86
+ return max(h // self.line_height(), 1)
87
+
88
+ def nearest(self, y):
89
+ i = self.top + (y - self.inset()) // self.line_height()
90
+ return max(0, min(i, len(self.items) - 1))
91
+
92
+ # -- display
93
+
94
+ def display(self):
95
+ o = self.options
96
+ state = o.str("-state")
97
+ bg = o.color("-background")
98
+ fg = o.color("-disabledforeground") if state == "disabled" else o.color("-foreground")
99
+ res = [["fill", 0, 0, self.width, self.height, bg]]
100
+ inset = self.inset()
101
+ font = self.font()
102
+ lh = self.line_height()
103
+ sbw = o.pixels("-selectborderwidth")
104
+ res.append(["clip", inset, inset, self.width - 2 * inset, self.height - 2 * inset])
105
+ n = self.visible_lines() + 1
106
+ css = font.css()
107
+ justify = o.str("-justify")
108
+ for k in range(n):
109
+ i = self.top + k
110
+ if i >= len(self.items):
111
+ break
112
+ y = inset + k * lh
113
+ iopts = self.item_options.get(i, {})
114
+ text = self.items[i]
115
+ color = iopts.get("-foreground") or fg
116
+ ibg = iopts.get("-background")
117
+ if ibg:
118
+ res.append(["fill", inset, y, self.width - 2 * inset, lh, ibg])
119
+ if i in self.selection:
120
+ sel_bg = iopts.get("-selectbackground") or o.color("-selectbackground")
121
+ res.append(["fill", inset, y, self.width - 2 * inset, lh, sel_bg])
122
+ if sbw > 0:
123
+ res.append(["border", inset, y, self.width - 2 * inset, lh, sbw, "raised", sel_bg])
124
+ color = iopts.get("-selectforeground") or o.color("-selectforeground") or color
125
+ tw = font.measure(text)
126
+ avail = self.width - 2 * inset - 2 * sbw
127
+ if justify == "center":
128
+ x = inset + sbw + (avail - tw) // 2
129
+ elif justify == "right":
130
+ x = inset + sbw + avail - tw - 1
131
+ else:
132
+ x = inset + sbw + 1
133
+ res.append(["text", x - self.xoffset, y + sbw + font.ascent, text, css, color, -1])
134
+ if i == self.active and self.has_focus and state != "disabled":
135
+ style = o.str("-activestyle")
136
+ if style == "dotbox":
137
+ res.append(["dotbox", inset, y, self.width - 2 * inset, lh, color])
138
+ elif style == "underline":
139
+ res.append(["hline", x - self.xoffset, y + sbw + font.ascent + 1, tw, color])
140
+ res.append(["unclip"])
141
+ res.append(["border", o.pixels("-highlightthickness"), o.pixels("-highlightthickness"),
142
+ self.width - 2 * o.pixels("-highlightthickness"),
143
+ self.height - 2 * o.pixels("-highlightthickness"),
144
+ o.pixels("-borderwidth"), o.str("-relief"), bg])
145
+ res.extend(self.highlight_display())
146
+ return res
147
+
148
+ # -- scrolling
149
+
150
+ def _clamp_top(self):
151
+ n = len(self.items)
152
+ self.top = max(0, min(self.top, max(n - self.visible_lines(), 0)))
153
+
154
+ def geometry_changed(self):
155
+ self._clamp_top()
156
+ self._yscroll()
157
+
158
+ def yview_fractions(self):
159
+ n = len(self.items)
160
+ if n == 0:
161
+ return 0.0, 1.0
162
+ first = self.top / n
163
+ last = min((self.top + self.visible_lines()) / n, 1.0)
164
+ return first, last
165
+
166
+ def _yscroll(self):
167
+ cmd = self.options.str("-yscrollcommand")
168
+ if cmd:
169
+ first, last = self.yview_fractions()
170
+ self.interp.renderer.scroll_command(self, cmd, first, last)
171
+
172
+ def see(self, i):
173
+ vis = self.visible_lines()
174
+ if i < self.top:
175
+ self.top = i
176
+ elif i >= self.top + vis:
177
+ self.top = i - vis + 1
178
+ self._clamp_top()
179
+ self.mark_dirty()
180
+ self._yscroll()
181
+
182
+ # -- indices
183
+
184
+ def index(self, value, end_ok=True):
185
+ v = to_str(value)
186
+ n = len(self.items)
187
+ if v == "end":
188
+ return n if end_ok else max(n - 1, 0)
189
+ if v == "active":
190
+ return self.active
191
+ if v == "anchor":
192
+ return self.anchor
193
+ if v.startswith("@"):
194
+ try:
195
+ _, y = (int(p) for p in v[1:].split(","))
196
+ except ValueError:
197
+ raise TclError(f'bad listbox index "{v}": must be active, anchor, end, @x,y, or a number') from None
198
+ return self.nearest(y)
199
+ try:
200
+ i = get_int(v)
201
+ except TclError:
202
+ raise TclError(f'bad listbox index "{v}": must be active, anchor, end, @x,y, or a number') from None
203
+ return i
204
+
205
+ def _range(self, args):
206
+ first = self.index(args[0])
207
+ last = self.index(args[1]) if len(args) > 1 else first
208
+ if to_str(args[1] if len(args) > 1 else args[0]) == "end":
209
+ last = len(self.items) - 1
210
+ if to_str(args[0]) == "end":
211
+ first = len(self.items) - 1
212
+ return first, last
213
+
214
+ # -- widget command
215
+
216
+ def subcommand(self, sub, args):
217
+ n = len(self.items)
218
+ if sub == "insert":
219
+ i = self.index(args[0])
220
+ i = max(0, min(i, n))
221
+ new = [to_str(a) for a in args[1:]]
222
+ self.items[i:i] = new
223
+ self.selection = {s + len(new) if s >= i else s for s in self.selection}
224
+ self.item_options = {k + len(new) if k >= i else k: v for k, v in self.item_options.items()}
225
+ if self.active >= i and n:
226
+ self.active += len(new)
227
+ self._sync_var()
228
+ self.mark_dirty(layout=True)
229
+ self._yscroll()
230
+ return ""
231
+ if sub == "delete":
232
+ first, last = self._range(args)
233
+ first = max(first, 0)
234
+ last = min(last, n - 1)
235
+ if first > last:
236
+ return ""
237
+ cnt = last - first + 1
238
+ del self.items[first : last + 1]
239
+
240
+ def shift(i):
241
+ return i - cnt if i > last else i
242
+
243
+ self.selection = {shift(s) for s in self.selection if not first <= s <= last}
244
+ self.item_options = {shift(k): v for k, v in self.item_options.items()
245
+ if not first <= k <= last}
246
+ if self.active > last:
247
+ self.active -= cnt
248
+ elif self.active >= first:
249
+ self.active = min(first, max(len(self.items) - 1, 0))
250
+ self._clamp_top()
251
+ self._sync_var()
252
+ self.mark_dirty(layout=True)
253
+ self._yscroll()
254
+ return ""
255
+ if sub == "get":
256
+ first, last = self._range(args)
257
+ if len(args) == 1:
258
+ return self.items[first] if 0 <= first < n else ""
259
+ first = max(first, 0)
260
+ return tuple(self.items[first : last + 1])
261
+ if sub == "size":
262
+ return n
263
+ if sub == "index":
264
+ return self.index(args[0])
265
+ if sub == "curselection":
266
+ return tuple(sorted(self.selection))
267
+ if sub in ("selection", "select"):
268
+ op = to_str(args[0])
269
+ if op == "includes":
270
+ return int(self.index(args[1]) in self.selection)
271
+ if op == "anchor":
272
+ self.anchor = max(0, min(self.index(args[1]), n - 1))
273
+ return ""
274
+ first, last = self._range(args[1:])
275
+ first, last = min(first, last), max(first, last)
276
+ rng = {i for i in range(max(first, 0), min(last, n - 1) + 1)}
277
+ if op == "set":
278
+ self.selection |= rng
279
+ elif op == "clear":
280
+ self.selection -= rng
281
+ else:
282
+ raise TclError(f'bad option "{op}": must be anchor, clear, includes, or set')
283
+ self.mark_dirty()
284
+ return ""
285
+ if sub == "activate":
286
+ i = self.index(args[0])
287
+ self.active = max(0, min(i, n - 1)) if n else 0
288
+ self.mark_dirty()
289
+ return ""
290
+ if sub == "see":
291
+ i = self.index(args[0])
292
+ if 0 <= i < n:
293
+ self.see(i)
294
+ return ""
295
+ if sub == "nearest":
296
+ return self.nearest(get_int(args[0]))
297
+ if sub == "bbox":
298
+ i = self.index(args[0])
299
+ if not self.top <= i < self.top + self.visible_lines() + 1 or i >= n:
300
+ return ""
301
+ font = self.font()
302
+ inset = self.inset()
303
+ y = inset + (i - self.top) * self.line_height()
304
+ return (inset + 1, y, font.measure(self.items[i]), font.linespace)
305
+ if sub == "yview":
306
+ return self._yview(args)
307
+ if sub == "xview":
308
+ if not args:
309
+ return (0.0, 1.0)
310
+ return ""
311
+ if sub in ("itemconfigure", "itemcget"):
312
+ i = self.index(args[0])
313
+ opts = self.item_options.setdefault(i, {})
314
+ if sub == "itemcget":
315
+ return opts.get(to_str(args[1]), "")
316
+ if len(args) == 1:
317
+ return tuple((k, "", "", "", opts.get(k, "")) for k, *_ in _ITEM_SPECS)
318
+ if len(args) == 2:
319
+ k = to_str(args[1])
320
+ return (k, "", "", "", opts.get(k, ""))
321
+ from ..options import parse_color
322
+ for k, v in zip(args[1::2], args[2::2]):
323
+ k = to_str(k)
324
+ if k in ("-bg",):
325
+ k = "-background"
326
+ if k in ("-fg",):
327
+ k = "-foreground"
328
+ opts[k] = parse_color(v) if to_str(v) else ""
329
+ self.mark_dirty()
330
+ return ""
331
+ if sub == "scan":
332
+ return ""
333
+ raise TclError(f'bad option "{sub}": must be activate, bbox, cget, configure, '
334
+ 'curselection, delete, get, index, insert, itemcget, '
335
+ 'itemconfigure, nearest, scan, see, selection, size, xview, or yview')
336
+
337
+ def _yview(self, args):
338
+ if not args:
339
+ return self.yview_fractions()
340
+ sub = to_str(args[0])
341
+ n = len(self.items)
342
+ if sub == "moveto":
343
+ self.top = int(get_double(args[1]) * n + 0.5)
344
+ elif sub == "scroll":
345
+ k = get_int(args[1])
346
+ if to_str(args[2]).startswith("p"):
347
+ k *= max(self.visible_lines() - 1, 1)
348
+ self.top += k
349
+ else:
350
+ self.top = self.index(args[0])
351
+ self._clamp_top()
352
+ self.mark_dirty()
353
+ self._yscroll()
354
+ return ""
355
+
356
+ # -- class bindings (listbox.tcl)
357
+
358
+ def _select_event(self):
359
+ self.interp.queue_event(Event("VirtualEvent", self, detail="ListboxSelect"))
360
+
361
+ def class_event(self, event):
362
+ o = self.options
363
+ if o.str("-state") == "disabled":
364
+ return None
365
+ t = event.type
366
+ mode = o.str("-selectmode")
367
+ n = len(self.items)
368
+ if t == "ButtonPress" and event.button == 1 and n:
369
+ if self.interp.takes_focus_on_click(self):
370
+ self.interp.set_focus(self)
371
+ i = self.nearest(event.y)
372
+ old = set(self.selection)
373
+ if mode in ("browse", "single"):
374
+ if mode == "single" and i in self.selection:
375
+ self.selection = set()
376
+ else:
377
+ self.selection = {i}
378
+ elif mode == "multiple":
379
+ self.selection ^= {i}
380
+ else: # extended
381
+ if event.state & 4:
382
+ self.selection ^= {i}
383
+ self.anchor = i
384
+ elif event.state & 1:
385
+ a = self.anchor
386
+ self.selection = set(range(min(a, i), max(a, i) + 1))
387
+ else:
388
+ self.selection = {i}
389
+ self.anchor = i
390
+ self.anchor = i if not event.state & 1 else self.anchor
391
+ self.active = i
392
+ self.drag_start = i
393
+ self.mark_dirty()
394
+ if self.selection != old:
395
+ self._select_event()
396
+ elif t == "Motion" and event.state & 256 and self.drag_start is not None and n:
397
+ i = self.nearest(event.y)
398
+ old = set(self.selection)
399
+ if mode == "browse":
400
+ self.selection = {i}
401
+ elif mode == "extended":
402
+ a = self.anchor
403
+ self.selection = set(range(min(a, i), max(a, i) + 1))
404
+ self.active = i
405
+ if event.y < 0 or event.y >= self.height:
406
+ self.see(i)
407
+ self.mark_dirty()
408
+ if self.selection != old:
409
+ self._select_event()
410
+ elif t == "ButtonRelease" and event.button == 1:
411
+ self.drag_start = None
412
+ elif t == "MouseWheel":
413
+ self._yview(("scroll", -5 if event.delta > 0 else 5, "units"))
414
+ return "break"
415
+ elif t == "KeyPress" and n:
416
+ k = event.keysym
417
+ step = {"Up": -1, "Down": 1, "Prior": -self.visible_lines(),
418
+ "Next": self.visible_lines(), "Home": -n, "End": n}.get(k)
419
+ if step is not None:
420
+ i = max(0, min(self.active + step, n - 1))
421
+ self.active = i
422
+ old = set(self.selection)
423
+ if mode == "browse":
424
+ self.selection = {i}
425
+ elif mode == "extended":
426
+ if event.state & 1:
427
+ a = self.anchor
428
+ self.selection = set(range(min(a, i), max(a, i) + 1))
429
+ else:
430
+ self.selection = {i}
431
+ self.anchor = i
432
+ self.see(i)
433
+ self.mark_dirty()
434
+ if self.selection != old:
435
+ self._select_event()
436
+ elif k == "space" and mode in ("single", "multiple", "extended"):
437
+ old = set(self.selection)
438
+ if mode == "multiple":
439
+ self.selection ^= {self.active}
440
+ else:
441
+ self.selection = {self.active}
442
+ self.anchor = self.active
443
+ self.mark_dirty()
444
+ if self.selection != old:
445
+ self._select_event()
446
+ elif event.state & 4 and k in ("a", "slash") and mode in ("multiple", "extended"):
447
+ self.selection = set(range(n))
448
+ self.mark_dirty()
449
+ self._select_event()
450
+ return None