tksheet 7.3.0__py3-none-any.whl → 7.3.2__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.
- tksheet/__init__.py +2 -2
- tksheet/column_headers.py +41 -40
- tksheet/find_window.py +251 -0
- tksheet/formatters.py +1 -1
- tksheet/functions.py +80 -50
- tksheet/main_table.py +531 -286
- tksheet/other_classes.py +6 -9
- tksheet/row_index.py +43 -40
- tksheet/sheet.py +165 -75
- tksheet/sheet_options.py +30 -1
- tksheet/text_editor.py +1 -1
- tksheet/top_left_rectangle.py +1 -1
- tksheet/types.py +8 -0
- {tksheet-7.3.0.dist-info → tksheet-7.3.2.dist-info}/METADATA +10 -11
- tksheet-7.3.2.dist-info/RECORD +21 -0
- {tksheet-7.3.0.dist-info → tksheet-7.3.2.dist-info}/WHEEL +1 -1
- tksheet-7.3.0.dist-info/RECORD +0 -20
- /tksheet/{vars.py → constants.py} +0 -0
- {tksheet-7.3.0.dist-info → tksheet-7.3.2.dist-info}/LICENSE.txt +0 -0
- {tksheet-7.3.0.dist-info → tksheet-7.3.2.dist-info}/top_level.txt +0 -0
tksheet/__init__.py
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
tksheet - A Python tkinter table widget
|
5
5
|
"""
|
6
6
|
|
7
|
-
__version__ = "7.3.
|
7
|
+
__version__ = "7.3.2"
|
8
8
|
|
9
9
|
from .colors import (
|
10
10
|
color_map,
|
@@ -96,7 +96,7 @@ from .themes import (
|
|
96
96
|
theme_light_green,
|
97
97
|
)
|
98
98
|
from .top_left_rectangle import TopLeftRectangle
|
99
|
-
from .
|
99
|
+
from .constants import (
|
100
100
|
USER_OS,
|
101
101
|
ctrl_key,
|
102
102
|
emitted_events,
|
tksheet/column_headers.py
CHANGED
@@ -23,15 +23,25 @@ from typing import Literal
|
|
23
23
|
from .colors import (
|
24
24
|
color_map,
|
25
25
|
)
|
26
|
+
from .constants import (
|
27
|
+
USER_OS,
|
28
|
+
rc_binding,
|
29
|
+
text_editor_close_bindings,
|
30
|
+
text_editor_newline_bindings,
|
31
|
+
text_editor_to_unbind,
|
32
|
+
)
|
26
33
|
from .formatters import is_bool_like, try_to_bool
|
27
34
|
from .functions import (
|
28
35
|
consecutive_ranges,
|
29
36
|
event_dict,
|
37
|
+
event_has_char_key,
|
38
|
+
event_opens_dropdown_or_checkbox,
|
30
39
|
get_n2a,
|
40
|
+
int_x_tuple,
|
31
41
|
is_contiguous,
|
32
42
|
new_tk_event,
|
33
|
-
stored_event_dict,
|
34
43
|
rounded_box_coords,
|
44
|
+
stored_event_dict,
|
35
45
|
try_binding,
|
36
46
|
)
|
37
47
|
from .other_classes import (
|
@@ -43,13 +53,8 @@ from .other_classes import (
|
|
43
53
|
from .text_editor import (
|
44
54
|
TextEditor,
|
45
55
|
)
|
46
|
-
from .
|
47
|
-
|
48
|
-
rc_binding,
|
49
|
-
symbols_set,
|
50
|
-
text_editor_close_bindings,
|
51
|
-
text_editor_newline_bindings,
|
52
|
-
text_editor_to_unbind,
|
56
|
+
from .types import (
|
57
|
+
AnyIter,
|
53
58
|
)
|
54
59
|
|
55
60
|
|
@@ -928,20 +933,31 @@ class ColumnHeaders(tk.Canvas):
|
|
928
933
|
|
929
934
|
def select_col(
|
930
935
|
self,
|
931
|
-
c: int,
|
936
|
+
c: int | AnyIter[int],
|
932
937
|
redraw: bool = False,
|
933
938
|
run_binding_func: bool = True,
|
934
939
|
ext: bool = False,
|
935
|
-
) -> int:
|
940
|
+
) -> int | list[int]:
|
936
941
|
boxes_to_hide = tuple(self.MT.selection_boxes)
|
937
|
-
|
942
|
+
fill_iids = [
|
943
|
+
self.MT.create_selection_box(
|
944
|
+
0,
|
945
|
+
start,
|
946
|
+
len(self.MT.row_positions) - 1,
|
947
|
+
end,
|
948
|
+
"columns",
|
949
|
+
set_current=True,
|
950
|
+
ext=ext,
|
951
|
+
)
|
952
|
+
for start, end in consecutive_ranges(int_x_tuple(c))
|
953
|
+
]
|
938
954
|
for iid in boxes_to_hide:
|
939
955
|
self.MT.hide_selection_box(iid)
|
940
956
|
if redraw:
|
941
957
|
self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
|
942
958
|
if run_binding_func:
|
943
959
|
self.MT.run_selection_binding("columns")
|
944
|
-
return
|
960
|
+
return fill_iids[0] if len(fill_iids) == 1 else fill_iids
|
945
961
|
|
946
962
|
def add_selection(
|
947
963
|
self,
|
@@ -1657,8 +1673,8 @@ class ColumnHeaders(tk.Canvas):
|
|
1657
1673
|
|
1658
1674
|
def get_redraw_selections(self, startc: int, endc: int) -> dict[str, set[int]]:
|
1659
1675
|
d = defaultdict(set)
|
1660
|
-
for
|
1661
|
-
|
1676
|
+
for _, box in self.MT.get_selection_items():
|
1677
|
+
_, c1, _, c2 = box.coords
|
1662
1678
|
for c in range(startc, endc):
|
1663
1679
|
if c1 <= c and c2 > c:
|
1664
1680
|
d[box.type_ if box.type_ != "rows" else "cells"].add(c)
|
@@ -1674,7 +1690,7 @@ class ColumnHeaders(tk.Canvas):
|
|
1674
1690
|
if self.get_cell_kwargs(datacn, key="readonly"):
|
1675
1691
|
return
|
1676
1692
|
elif self.get_cell_kwargs(datacn, key="dropdown") or self.get_cell_kwargs(datacn, key="checkbox"):
|
1677
|
-
if
|
1693
|
+
if event_opens_dropdown_or_checkbox(event):
|
1678
1694
|
if self.get_cell_kwargs(datacn, key="dropdown"):
|
1679
1695
|
self.open_dropdown_window(c, event=event)
|
1680
1696
|
elif self.get_cell_kwargs(datacn, key="checkbox"):
|
@@ -1700,28 +1716,16 @@ class ColumnHeaders(tk.Canvas):
|
|
1700
1716
|
state: str = "normal",
|
1701
1717
|
dropdown: bool = False,
|
1702
1718
|
) -> bool:
|
1703
|
-
text =
|
1719
|
+
text = f"{self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)}"
|
1704
1720
|
extra_func_key = "??"
|
1705
|
-
if
|
1706
|
-
if event
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
elif event is not None
|
1713
|
-
(hasattr(event, "keysym") and event.keysym == "BackSpace") or event.keycode in (8, 855638143)
|
1714
|
-
):
|
1715
|
-
extra_func_key = "BackSpace"
|
1716
|
-
text = ""
|
1717
|
-
elif event is not None and (
|
1718
|
-
(hasattr(event, "char") and event.char.isalpha())
|
1719
|
-
or (hasattr(event, "char") and event.char.isdigit())
|
1720
|
-
or (hasattr(event, "char") and event.char in symbols_set)
|
1721
|
-
):
|
1722
|
-
extra_func_key = event.char
|
1723
|
-
text = event.char
|
1724
|
-
else:
|
1721
|
+
if event_opens_dropdown_or_checkbox(event):
|
1722
|
+
if hasattr(event, "keysym") and event.keysym in ("Return", "F2", "BackSpace"):
|
1723
|
+
extra_func_key = event.keysym
|
1724
|
+
if event.keysym == "BackSpace":
|
1725
|
+
text = ""
|
1726
|
+
elif event_has_char_key(event):
|
1727
|
+
extra_func_key = text = event.char
|
1728
|
+
elif event is not None:
|
1725
1729
|
return False
|
1726
1730
|
if self.extra_begin_edit_cell_func:
|
1727
1731
|
try:
|
@@ -1743,14 +1747,13 @@ class ColumnHeaders(tk.Canvas):
|
|
1743
1747
|
return False
|
1744
1748
|
else:
|
1745
1749
|
text = text if isinstance(text, str) else f"{text}"
|
1746
|
-
text = "" if text is None else text
|
1747
1750
|
if self.PAR.ops.cell_auto_resize_enabled:
|
1748
1751
|
if self.height_resizing_enabled:
|
1749
1752
|
self.set_height_of_header_to_text(text)
|
1750
1753
|
self.set_col_width_run_binding(c)
|
1751
1754
|
if self.text_editor.open and c == self.text_editor.column:
|
1752
1755
|
self.text_editor.set_text(self.text_editor.get() + "" if not isinstance(text, str) else text)
|
1753
|
-
return
|
1756
|
+
return False
|
1754
1757
|
self.hide_text_editor()
|
1755
1758
|
if not self.MT.see(r=0, c=c, keep_yscroll=True, check_cell_visibility=True):
|
1756
1759
|
self.MT.refresh()
|
@@ -1758,8 +1761,6 @@ class ColumnHeaders(tk.Canvas):
|
|
1758
1761
|
y = 0
|
1759
1762
|
w = self.MT.col_positions[c + 1] - x
|
1760
1763
|
h = self.current_height + 1
|
1761
|
-
if text is None:
|
1762
|
-
text = self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)
|
1763
1764
|
kwargs = {
|
1764
1765
|
"menu_kwargs": DotDict(
|
1765
1766
|
{
|
tksheet/find_window.py
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import tkinter as tk
|
4
|
+
from collections.abc import (
|
5
|
+
Callable,
|
6
|
+
)
|
7
|
+
from typing import Literal
|
8
|
+
|
9
|
+
from .other_classes import (
|
10
|
+
DotDict,
|
11
|
+
)
|
12
|
+
from .constants import (
|
13
|
+
ctrl_key,
|
14
|
+
rc_binding,
|
15
|
+
)
|
16
|
+
from .functions import (
|
17
|
+
recursive_bind,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
class FindWindowTkText(tk.Text):
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
parent: tk.Misc,
|
25
|
+
) -> None:
|
26
|
+
super().__init__(
|
27
|
+
parent,
|
28
|
+
spacing1=0,
|
29
|
+
spacing2=1,
|
30
|
+
spacing3=0,
|
31
|
+
bd=0,
|
32
|
+
highlightthickness=0,
|
33
|
+
undo=True,
|
34
|
+
maxundo=30,
|
35
|
+
)
|
36
|
+
self.parent = parent
|
37
|
+
self.rc_popup_menu = tk.Menu(self, tearoff=0)
|
38
|
+
self.bind("<1>", lambda event: self.focus_set())
|
39
|
+
self.bind(rc_binding, self.rc)
|
40
|
+
self.bind(f"<{ctrl_key}-a>", self.select_all)
|
41
|
+
self.bind(f"<{ctrl_key}-A>", self.select_all)
|
42
|
+
self.bind("<Delete>", self.delete_key)
|
43
|
+
|
44
|
+
def reset(
|
45
|
+
self,
|
46
|
+
menu_kwargs: dict,
|
47
|
+
sheet_ops: dict,
|
48
|
+
font: tuple,
|
49
|
+
bg: str,
|
50
|
+
fg: str,
|
51
|
+
select_bg: str,
|
52
|
+
select_fg: str,
|
53
|
+
) -> None:
|
54
|
+
self.config(
|
55
|
+
font=font,
|
56
|
+
background=bg,
|
57
|
+
foreground=fg,
|
58
|
+
insertbackground=fg,
|
59
|
+
selectbackground=select_bg,
|
60
|
+
selectforeground=select_fg,
|
61
|
+
)
|
62
|
+
self.editor_del_key = sheet_ops.editor_del_key
|
63
|
+
self.rc_popup_menu.delete(0, "end")
|
64
|
+
self.rc_popup_menu.add_command(
|
65
|
+
label=sheet_ops.select_all_label,
|
66
|
+
accelerator=sheet_ops.select_all_accelerator,
|
67
|
+
command=self.select_all,
|
68
|
+
**menu_kwargs,
|
69
|
+
)
|
70
|
+
self.rc_popup_menu.add_command(
|
71
|
+
label=sheet_ops.cut_label,
|
72
|
+
accelerator=sheet_ops.cut_accelerator,
|
73
|
+
command=self.cut,
|
74
|
+
**menu_kwargs,
|
75
|
+
)
|
76
|
+
self.rc_popup_menu.add_command(
|
77
|
+
label=sheet_ops.copy_label,
|
78
|
+
accelerator=sheet_ops.copy_accelerator,
|
79
|
+
command=self.copy,
|
80
|
+
**menu_kwargs,
|
81
|
+
)
|
82
|
+
self.rc_popup_menu.add_command(
|
83
|
+
label=sheet_ops.paste_label,
|
84
|
+
accelerator=sheet_ops.paste_accelerator,
|
85
|
+
command=self.paste,
|
86
|
+
**menu_kwargs,
|
87
|
+
)
|
88
|
+
self.rc_popup_menu.add_command(
|
89
|
+
label=sheet_ops.undo_label,
|
90
|
+
accelerator=sheet_ops.undo_accelerator,
|
91
|
+
command=self.undo,
|
92
|
+
**menu_kwargs,
|
93
|
+
)
|
94
|
+
|
95
|
+
def rc(self, event: object) -> None:
|
96
|
+
self.focus_set()
|
97
|
+
self.rc_popup_menu.tk_popup(event.x_root, event.y_root)
|
98
|
+
|
99
|
+
def delete_key(self, event: object = None) -> None:
|
100
|
+
if self.editor_del_key == "forward":
|
101
|
+
return
|
102
|
+
elif not self.editor_del_key:
|
103
|
+
return "break"
|
104
|
+
elif self.editor_del_key == "backward":
|
105
|
+
if self.tag_ranges("sel"):
|
106
|
+
return
|
107
|
+
if self.index("insert") == "1.0":
|
108
|
+
return "break"
|
109
|
+
self.delete("insert-1c")
|
110
|
+
return "break"
|
111
|
+
|
112
|
+
def select_all(self, event: object = None) -> Literal["break"]:
|
113
|
+
self.tag_add(tk.SEL, "1.0", tk.END)
|
114
|
+
self.mark_set(tk.INSERT, tk.END)
|
115
|
+
# self.see(tk.INSERT)
|
116
|
+
return "break"
|
117
|
+
|
118
|
+
def cut(self, event: object = None) -> Literal["break"]:
|
119
|
+
self.event_generate(f"<{ctrl_key}-x>")
|
120
|
+
self.event_generate("<KeyRelease>")
|
121
|
+
return "break"
|
122
|
+
|
123
|
+
def copy(self, event: object = None) -> Literal["break"]:
|
124
|
+
self.event_generate(f"<{ctrl_key}-c>")
|
125
|
+
return "break"
|
126
|
+
|
127
|
+
def paste(self, event: object = None) -> Literal["break"]:
|
128
|
+
self.event_generate(f"<{ctrl_key}-v>")
|
129
|
+
self.event_generate("<KeyRelease>")
|
130
|
+
return "break"
|
131
|
+
|
132
|
+
def undo(self, event: object = None) -> Literal["break"]:
|
133
|
+
self.event_generate(f"<{ctrl_key}-z>")
|
134
|
+
self.event_generate("<KeyRelease>")
|
135
|
+
return "break"
|
136
|
+
|
137
|
+
|
138
|
+
class FindWindow(tk.Frame):
|
139
|
+
def __init__(
|
140
|
+
self,
|
141
|
+
parent: tk.Misc,
|
142
|
+
find_next_func: Callable,
|
143
|
+
find_prev_func: Callable,
|
144
|
+
close_func: Callable,
|
145
|
+
) -> None:
|
146
|
+
super().__init__(
|
147
|
+
parent,
|
148
|
+
width=0,
|
149
|
+
height=0,
|
150
|
+
bd=0,
|
151
|
+
)
|
152
|
+
self.grid_columnconfigure(0, weight=1)
|
153
|
+
self.grid_rowconfigure(0, weight=1)
|
154
|
+
self.grid_propagate(False)
|
155
|
+
self.parent = parent
|
156
|
+
self.tktext = FindWindowTkText(self)
|
157
|
+
self.tktext.grid(row=0, column=0, sticky="nswe")
|
158
|
+
self.bg = None
|
159
|
+
self.fg = None
|
160
|
+
|
161
|
+
self.find_previous_arrow = tk.Label(self, text="▲", cursor="hand2", highlightthickness=1)
|
162
|
+
self.find_previous_arrow.bind("<Button-1>", find_prev_func)
|
163
|
+
self.find_previous_arrow.grid(row=0, column=1)
|
164
|
+
|
165
|
+
self.find_next_arrow = tk.Label(self, text="▼", cursor="hand2", highlightthickness=1)
|
166
|
+
self.find_next_arrow.bind("<Button-1>", find_next_func)
|
167
|
+
self.find_next_arrow.grid(row=0, column=2)
|
168
|
+
|
169
|
+
self.find_in_selection = False
|
170
|
+
self.in_selection = tk.Label(self, text="🔎", cursor="hand2", highlightthickness=1)
|
171
|
+
self.in_selection.bind("<Button-1>", self.toggle_in_selection)
|
172
|
+
self.in_selection.grid(row=0, column=3)
|
173
|
+
|
174
|
+
self.close = tk.Label(self, text="✕", cursor="hand2", highlightthickness=1)
|
175
|
+
self.close.bind("<Button-1>", close_func)
|
176
|
+
self.close.grid(row=0, column=4)
|
177
|
+
|
178
|
+
for widget in (self.find_previous_arrow, self.find_next_arrow, self.in_selection, self.close):
|
179
|
+
widget.bind("<Enter>", lambda w, widget=widget: self.enter_label(widget=widget))
|
180
|
+
widget.bind("<Leave>", lambda w, widget=widget: self.leave_label(widget=widget))
|
181
|
+
|
182
|
+
for b in ("Option", "Alt"):
|
183
|
+
for c in ("l", "L"):
|
184
|
+
recursive_bind(self, f"<{b}-{c}>", self.toggle_in_selection)
|
185
|
+
|
186
|
+
def enter_label(self, widget: tk.Misc) -> None:
|
187
|
+
widget.config(
|
188
|
+
highlightbackground=self.fg,
|
189
|
+
highlightcolor=self.fg,
|
190
|
+
)
|
191
|
+
|
192
|
+
def leave_label(self, widget: tk.Misc) -> None:
|
193
|
+
if widget == self.in_selection and self.find_in_selection:
|
194
|
+
return
|
195
|
+
widget.config(
|
196
|
+
highlightbackground=self.bg,
|
197
|
+
highlightcolor=self.fg,
|
198
|
+
)
|
199
|
+
|
200
|
+
def toggle_in_selection(self, event: tk.Misc) -> None:
|
201
|
+
self.find_in_selection = not self.find_in_selection
|
202
|
+
self.enter_label(self.in_selection)
|
203
|
+
self.leave_label(self.in_selection)
|
204
|
+
|
205
|
+
def get(self) -> str:
|
206
|
+
return self.tktext.get("1.0", "end-1c")
|
207
|
+
|
208
|
+
def get_num_lines(self) -> int:
|
209
|
+
return int(self.tktext.index("end-1c").split(".")[0])
|
210
|
+
|
211
|
+
def set_text(self, text: str = "") -> None:
|
212
|
+
self.tktext.delete(1.0, "end")
|
213
|
+
self.tktext.insert(1.0, text)
|
214
|
+
|
215
|
+
def reset(
|
216
|
+
self,
|
217
|
+
border_color: str,
|
218
|
+
menu_kwargs: DotDict,
|
219
|
+
sheet_ops: DotDict,
|
220
|
+
bg: str,
|
221
|
+
fg: str,
|
222
|
+
select_bg: str,
|
223
|
+
select_fg: str,
|
224
|
+
) -> None:
|
225
|
+
self.bg = bg
|
226
|
+
self.fg = fg
|
227
|
+
self.tktext.reset(
|
228
|
+
menu_kwargs=menu_kwargs,
|
229
|
+
sheet_ops=sheet_ops,
|
230
|
+
font=menu_kwargs.font,
|
231
|
+
bg=bg,
|
232
|
+
fg=fg,
|
233
|
+
select_bg=select_bg,
|
234
|
+
select_fg=select_fg,
|
235
|
+
)
|
236
|
+
for widget in (self.find_previous_arrow, self.find_next_arrow, self.in_selection, self.close):
|
237
|
+
widget.config(
|
238
|
+
font=menu_kwargs.font,
|
239
|
+
bg=bg,
|
240
|
+
fg=fg,
|
241
|
+
highlightbackground=bg,
|
242
|
+
highlightcolor=fg,
|
243
|
+
)
|
244
|
+
if self.find_in_selection:
|
245
|
+
self.enter_label(self.in_selection)
|
246
|
+
self.config(
|
247
|
+
background=bg,
|
248
|
+
highlightbackground=border_color,
|
249
|
+
highlightcolor=border_color,
|
250
|
+
highlightthickness=1,
|
251
|
+
)
|
tksheet/formatters.py
CHANGED
tksheet/functions.py
CHANGED
@@ -12,6 +12,8 @@ from collections import deque
|
|
12
12
|
from collections.abc import (
|
13
13
|
Callable,
|
14
14
|
Generator,
|
15
|
+
Hashable,
|
16
|
+
Iterable,
|
15
17
|
Iterator,
|
16
18
|
Sequence,
|
17
19
|
)
|
@@ -30,6 +32,12 @@ from .other_classes import (
|
|
30
32
|
Loc,
|
31
33
|
Span,
|
32
34
|
)
|
35
|
+
from .types import (
|
36
|
+
AnyIter,
|
37
|
+
)
|
38
|
+
from .constants import (
|
39
|
+
symbols_set,
|
40
|
+
)
|
33
41
|
|
34
42
|
unpickle_obj = pickle.loads
|
35
43
|
|
@@ -62,9 +70,15 @@ def get_data_from_clipboard(
|
|
62
70
|
return [[data]]
|
63
71
|
|
64
72
|
|
73
|
+
def recursive_bind(widget: tk.Misc, event: str, callback: Callable) -> None:
|
74
|
+
widget.bind(event, callback)
|
75
|
+
for child in widget.winfo_children():
|
76
|
+
recursive_bind(child, event, callback)
|
77
|
+
|
78
|
+
|
65
79
|
def tksheet_type_error(kwarg: str, valid_types: list[str], not_type: object) -> str:
|
66
80
|
valid_types = ", ".join(f"{type_}" for type_ in valid_types)
|
67
|
-
return f"Argument '{kwarg}' must be one of the following types: {valid_types},
|
81
|
+
return f"Argument '{kwarg}' must be one of the following types: {valid_types}, not {type(not_type)}."
|
68
82
|
|
69
83
|
|
70
84
|
def new_tk_event(keysym: str) -> tk.Event:
|
@@ -73,6 +87,25 @@ def new_tk_event(keysym: str) -> tk.Event:
|
|
73
87
|
return event
|
74
88
|
|
75
89
|
|
90
|
+
def event_has_char_key(event: object) -> bool:
|
91
|
+
return (
|
92
|
+
event and hasattr(event, "char") and (event.char.isalpha() or event.char.isdigit() or event.char in symbols_set)
|
93
|
+
)
|
94
|
+
|
95
|
+
|
96
|
+
def event_opens_dropdown_or_checkbox(event=None) -> bool:
|
97
|
+
if event is None:
|
98
|
+
return False
|
99
|
+
elif event == "rc":
|
100
|
+
return True
|
101
|
+
return (
|
102
|
+
(hasattr(event, "keysym") and event.keysym in {"Return", "F2", "BackSpace"})
|
103
|
+
or (
|
104
|
+
hasattr(event, "keycode") and event.keycode == "??" and hasattr(event, "num") and event.num == 1
|
105
|
+
) # mouseclick
|
106
|
+
)
|
107
|
+
|
108
|
+
|
76
109
|
def dropdown_search_function(
|
77
110
|
search_for: object,
|
78
111
|
data: Sequence[object],
|
@@ -225,6 +258,16 @@ def b_index(sorted_seq: Sequence[int], num_to_index: int) -> int:
|
|
225
258
|
return idx
|
226
259
|
|
227
260
|
|
261
|
+
def bisect_in(sorted_seq: Sequence[int], num: int) -> bool:
|
262
|
+
"""
|
263
|
+
Faster than 'num in sorted_seq'
|
264
|
+
"""
|
265
|
+
try:
|
266
|
+
return sorted_seq[bisect_left(sorted_seq, num)] == num
|
267
|
+
except Exception:
|
268
|
+
return False
|
269
|
+
|
270
|
+
|
228
271
|
def get_dropdown_kwargs(
|
229
272
|
values: list = [],
|
230
273
|
set_value: object = None,
|
@@ -295,13 +338,19 @@ def is_iterable(o: object) -> bool:
|
|
295
338
|
return False
|
296
339
|
|
297
340
|
|
298
|
-
def int_x_iter(i:
|
341
|
+
def int_x_iter(i: AnyIter[int] | int) -> AnyIter[int]:
|
299
342
|
if isinstance(i, int):
|
300
343
|
return (i,)
|
301
344
|
return i
|
302
345
|
|
303
346
|
|
304
|
-
def
|
347
|
+
def int_x_tuple(i: AnyIter[int] | int) -> tuple[int]:
|
348
|
+
if isinstance(i, int):
|
349
|
+
return (i,)
|
350
|
+
return tuple(i)
|
351
|
+
|
352
|
+
|
353
|
+
def unpack(t: tuple[object] | tuple[AnyIter[object]]) -> tuple[object]:
|
305
354
|
if not len(t):
|
306
355
|
return t
|
307
356
|
if is_iterable(t[0]) and len(t) == 1:
|
@@ -427,7 +476,7 @@ def consecutive_ranges(seq: Sequence[int]) -> Generator[tuple[int, int]]:
|
|
427
476
|
yield seq[start], seq[-1] + 1
|
428
477
|
|
429
478
|
|
430
|
-
def is_contiguous(iterable:
|
479
|
+
def is_contiguous(iterable: Iterable[int]) -> bool:
|
431
480
|
itr = iter(iterable)
|
432
481
|
prev = next(itr)
|
433
482
|
return all(i == (prev := prev + 1) for i in itr)
|
@@ -500,7 +549,7 @@ def cell_right_within_box(
|
|
500
549
|
|
501
550
|
|
502
551
|
def get_last(
|
503
|
-
it:
|
552
|
+
it: AnyIter[object],
|
504
553
|
) -> object:
|
505
554
|
if hasattr(it, "__reversed__"):
|
506
555
|
try:
|
@@ -522,7 +571,7 @@ def index_exists(seq: Sequence[object], index: int) -> bool:
|
|
522
571
|
return False
|
523
572
|
|
524
573
|
|
525
|
-
def add_to_displayed(displayed: list[int], to_add:
|
574
|
+
def add_to_displayed(displayed: list[int], to_add: Iterable[int]) -> list[int]:
|
526
575
|
# assumes to_add is sorted in reverse
|
527
576
|
for i in reversed(to_add):
|
528
577
|
ins = bisect_left(displayed, i)
|
@@ -535,35 +584,23 @@ def move_elements_by_mapping(
|
|
535
584
|
new_idxs: dict[int, int],
|
536
585
|
old_idxs: dict[int, int] | None = None,
|
537
586
|
) -> list[object]:
|
538
|
-
# move elements of a list around
|
539
|
-
# other elements based on mapping
|
540
|
-
#
|
587
|
+
# move elements of a list around
|
588
|
+
# displacing other elements based on mapping
|
589
|
+
# new_idxs = {old index: new index, ...}
|
590
|
+
# old_idxs = {new index: old index, ...}
|
541
591
|
if old_idxs is None:
|
542
592
|
old_idxs = dict(zip(new_idxs.values(), new_idxs))
|
543
593
|
|
544
|
-
# create dummy list
|
545
594
|
res = [0] * len(seq)
|
546
595
|
|
547
|
-
|
548
|
-
|
596
|
+
remaining_values = (e for i, e in enumerate(seq) if i not in new_idxs)
|
597
|
+
for i in range(len(res)):
|
598
|
+
if i in old_idxs:
|
599
|
+
res[i] = seq[old_idxs[i]]
|
600
|
+
else:
|
601
|
+
res[i] = next(remaining_values)
|
549
602
|
|
550
|
-
|
551
|
-
# once to put elements being moved in new spots
|
552
|
-
# then to fill remaining spots with remaining elements
|
553
|
-
|
554
|
-
# fill new indexes in res
|
555
|
-
if len(new_idxs) > int(len(seq) / 2) - 1:
|
556
|
-
# if moving a lot of items better to do comprehension
|
557
|
-
return [
|
558
|
-
next(remaining) if i_ not in old_idxs else e_
|
559
|
-
for i_, e_ in enumerate(seq[old_idxs[i]] if i in old_idxs else e for i, e in enumerate(res))
|
560
|
-
]
|
561
|
-
else:
|
562
|
-
# if just moving a few items assignments are fine
|
563
|
-
for old, new in new_idxs.items():
|
564
|
-
res[new] = seq[old]
|
565
|
-
# fill remaining indexes
|
566
|
-
return [next(remaining) if i not in old_idxs else e for i, e in enumerate(res)]
|
603
|
+
return res
|
567
604
|
|
568
605
|
|
569
606
|
def move_elements_to(
|
@@ -598,23 +635,15 @@ def get_new_indexes(
|
|
598
635
|
|
599
636
|
|
600
637
|
def insert_items(
|
601
|
-
seq: list[object]
|
638
|
+
seq: list[object],
|
602
639
|
to_insert: dict[int, object],
|
603
640
|
seq_len_func: Callable | None = None,
|
604
|
-
) -> list:
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
# need to extend seq if it's not long enough
|
611
|
-
if seq_len_func and idx - i > len(seq):
|
612
|
-
seq_len_func(idx - i - 1)
|
613
|
-
res.extend(seq[extended : idx - i])
|
614
|
-
extended += idx - i - extended
|
615
|
-
res.append(v)
|
616
|
-
res.extend(seq[extended:])
|
617
|
-
seq = res
|
641
|
+
) -> list[object]:
|
642
|
+
if to_insert:
|
643
|
+
if seq_len_func and next(iter(to_insert)) >= len(seq) + len(to_insert):
|
644
|
+
seq_len_func(next(iter(to_insert)) - len(to_insert))
|
645
|
+
for idx, v in reversed(to_insert.items()):
|
646
|
+
seq[idx:idx] = [v]
|
618
647
|
return seq
|
619
648
|
|
620
649
|
|
@@ -622,8 +651,7 @@ def data_to_displayed_idxs(
|
|
622
651
|
to_convert: list[int],
|
623
652
|
displayed: list[int],
|
624
653
|
) -> list[int]:
|
625
|
-
|
626
|
-
return [i for i, e in enumerate(displayed) if e in data_indexes]
|
654
|
+
return [i for i, e in enumerate(displayed) if bisect_in(to_convert, e)]
|
627
655
|
|
628
656
|
|
629
657
|
def displayed_to_data_idxs(
|
@@ -640,6 +668,8 @@ def rounded_box_coords(
|
|
640
668
|
y2: float,
|
641
669
|
radius: int = 5,
|
642
670
|
) -> tuple[float]:
|
671
|
+
if y2 - y1 < 2 or x2 - x1 < 2:
|
672
|
+
return x1, y1, x2, y1, x2, y2, x1, y2
|
643
673
|
return (
|
644
674
|
x1 + radius,
|
645
675
|
y1,
|
@@ -704,7 +734,7 @@ def diff_gen(seq: list[float]) -> Generator[int]:
|
|
704
734
|
)
|
705
735
|
|
706
736
|
|
707
|
-
def zip_fill_2nd_value(x:
|
737
|
+
def zip_fill_2nd_value(x: AnyIter[object], o: object) -> Generator[object, object]:
|
708
738
|
return zip(x, repeat(o))
|
709
739
|
|
710
740
|
|
@@ -1233,13 +1263,13 @@ def span_froms(
|
|
1233
1263
|
return from_r, from_c
|
1234
1264
|
|
1235
1265
|
|
1236
|
-
def del_named_span_options(options: dict, itr:
|
1266
|
+
def del_named_span_options(options: dict, itr: AnyIter[Hashable], type_: str) -> None:
|
1237
1267
|
for k in itr:
|
1238
1268
|
if k in options and type_ in options[k]:
|
1239
1269
|
del options[k][type_]
|
1240
1270
|
|
1241
1271
|
|
1242
|
-
def del_named_span_options_nested(options: dict, itr1:
|
1272
|
+
def del_named_span_options_nested(options: dict, itr1: AnyIter[Hashable], itr2: AnyIter[Hashable], type_: str) -> None:
|
1243
1273
|
for k1 in itr1:
|
1244
1274
|
for k2 in itr2:
|
1245
1275
|
k = (k1, k2)
|
@@ -1320,7 +1350,7 @@ def set_align(
|
|
1320
1350
|
def del_from_options(
|
1321
1351
|
options: dict,
|
1322
1352
|
key: str,
|
1323
|
-
coords: int |
|
1353
|
+
coords: int | AnyIter[int | tuple[int, int]] | None = None,
|
1324
1354
|
) -> dict:
|
1325
1355
|
if isinstance(coords, int):
|
1326
1356
|
if coords in options and key in options[coords]:
|