tksheet 7.3.1__tar.gz → 7.3.2__tar.gz

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 (25) hide show
  1. {tksheet-7.3.1/tksheet.egg-info → tksheet-7.3.2}/PKG-INFO +10 -11
  2. {tksheet-7.3.1 → tksheet-7.3.2}/README.md +8 -9
  3. {tksheet-7.3.1 → tksheet-7.3.2}/pyproject.toml +1 -1
  4. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/__init__.py +2 -2
  5. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/column_headers.py +22 -36
  6. tksheet-7.3.2/tksheet/find_window.py +251 -0
  7. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/formatters.py +1 -1
  8. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/functions.py +60 -41
  9. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/main_table.py +436 -265
  10. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/other_classes.py +6 -9
  11. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/row_index.py +23 -35
  12. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/sheet.py +75 -21
  13. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/sheet_options.py +16 -1
  14. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/text_editor.py +1 -1
  15. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/top_left_rectangle.py +1 -1
  16. {tksheet-7.3.1 → tksheet-7.3.2/tksheet.egg-info}/PKG-INFO +10 -11
  17. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet.egg-info/SOURCES.txt +2 -1
  18. {tksheet-7.3.1 → tksheet-7.3.2}/LICENSE.txt +0 -0
  19. {tksheet-7.3.1 → tksheet-7.3.2}/setup.cfg +0 -0
  20. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/colors.py +0 -0
  21. /tksheet-7.3.1/tksheet/vars.py → /tksheet-7.3.2/tksheet/constants.py +0 -0
  22. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/themes.py +0 -0
  23. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet/types.py +0 -0
  24. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet.egg-info/dependency_links.txt +0 -0
  25. {tksheet-7.3.1 → tksheet-7.3.2}/tksheet.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.3.1
3
+ Version: 7.3.2
4
4
  Summary: Tkinter table / sheet widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
@@ -95,22 +95,21 @@ This library is maintained with the help of **[others](https://github.com/ragard
95
95
 
96
96
  ## **Features**
97
97
 
98
- - Display and modify tabular data
99
- - Stores its display data as a Python list of lists, sublists being rows
100
- - Runs smoothly even with millions of rows/columns
101
- - Edit cells directly
98
+ - Smoothly display and modify tabular data
99
+ - [Edit cells directly](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
102
100
  - Cell values can potentially be [any class](https://github.com/ragardner/tksheet/wiki/Version-7#data-formatting), the default is any class with a `__str__` method
103
- - Drag and drop columns and rows
101
+ - [Drag and drop columns and rows](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
104
102
  - Multiple line header and index cells
105
- - Expand row heights and column widths
106
- - Change fonts and font size (not for individual cells)
107
- - Change any colors in the sheet
103
+ - [Expand row heights and column widths](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
104
+ - [Change fonts and font size (not for individual cells)](https://github.com/ragardner/tksheet/wiki/Version-7#text-font-and-alignment)
105
+ - [Change any colors in the sheet](https://github.com/ragardner/tksheet/wiki/Version-7#sheet-appearance)
108
106
  - [Treeview mode](https://github.com/ragardner/tksheet/wiki/Version-7#treeview-mode)
109
107
  - [Dropdown boxes](https://github.com/ragardner/tksheet/wiki/Version-7#dropdown-boxes)
110
108
  - [Check boxes](https://github.com/ragardner/tksheet/wiki/Version-7#check-boxes)
111
109
  - [Progress bars](https://github.com/ragardner/tksheet/wiki/Version-7#progress-bars)
112
110
  - [Hide rows and/or columns](https://github.com/ragardner/tksheet/wiki/Version-7#example-header-dropdown-boxes-and-row-filtering)
113
- - Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column
111
+ - [Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column](https://github.com/ragardner/tksheet/wiki/Version-7#text-font-and-alignment)
112
+ - [Optional built-in find window](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
114
113
 
115
114
  ```python
116
115
  """
@@ -52,22 +52,21 @@ This library is maintained with the help of **[others](https://github.com/ragard
52
52
 
53
53
  ## **Features**
54
54
 
55
- - Display and modify tabular data
56
- - Stores its display data as a Python list of lists, sublists being rows
57
- - Runs smoothly even with millions of rows/columns
58
- - Edit cells directly
55
+ - Smoothly display and modify tabular data
56
+ - [Edit cells directly](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
59
57
  - Cell values can potentially be [any class](https://github.com/ragardner/tksheet/wiki/Version-7#data-formatting), the default is any class with a `__str__` method
60
- - Drag and drop columns and rows
58
+ - [Drag and drop columns and rows](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
61
59
  - Multiple line header and index cells
62
- - Expand row heights and column widths
63
- - Change fonts and font size (not for individual cells)
64
- - Change any colors in the sheet
60
+ - [Expand row heights and column widths](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
61
+ - [Change fonts and font size (not for individual cells)](https://github.com/ragardner/tksheet/wiki/Version-7#text-font-and-alignment)
62
+ - [Change any colors in the sheet](https://github.com/ragardner/tksheet/wiki/Version-7#sheet-appearance)
65
63
  - [Treeview mode](https://github.com/ragardner/tksheet/wiki/Version-7#treeview-mode)
66
64
  - [Dropdown boxes](https://github.com/ragardner/tksheet/wiki/Version-7#dropdown-boxes)
67
65
  - [Check boxes](https://github.com/ragardner/tksheet/wiki/Version-7#check-boxes)
68
66
  - [Progress bars](https://github.com/ragardner/tksheet/wiki/Version-7#progress-bars)
69
67
  - [Hide rows and/or columns](https://github.com/ragardner/tksheet/wiki/Version-7#example-header-dropdown-boxes-and-row-filtering)
70
- - Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column
68
+ - [Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column](https://github.com/ragardner/tksheet/wiki/Version-7#text-font-and-alignment)
69
+ - [Optional built-in find window](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
71
70
 
72
71
  ```python
73
72
  """
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "tksheet"
7
7
  description = "Tkinter table / sheet widget"
8
8
  readme = "README.md"
9
- version = "7.3.1"
9
+ version = "7.3.2"
10
10
  authors = [{ name = "ragardner", email = "github@ragardner.simplelogin.com" }]
11
11
  requires-python = ">=3.8"
12
12
  license = {file = "LICENSE.txt"}
@@ -4,7 +4,7 @@
4
4
  tksheet - A Python tkinter table widget
5
5
  """
6
6
 
7
- __version__ = "7.3.1"
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 .vars import (
99
+ from .constants import (
100
100
  USER_OS,
101
101
  ctrl_key,
102
102
  emitted_events,
@@ -23,10 +23,19 @@ 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,
31
40
  int_x_tuple,
32
41
  is_contiguous,
@@ -47,14 +56,6 @@ from .text_editor import (
47
56
  from .types import (
48
57
  AnyIter,
49
58
  )
50
- from .vars import (
51
- USER_OS,
52
- rc_binding,
53
- symbols_set,
54
- text_editor_close_bindings,
55
- text_editor_newline_bindings,
56
- text_editor_to_unbind,
57
- )
58
59
 
59
60
 
60
61
  class ColumnHeaders(tk.Canvas):
@@ -1672,8 +1673,8 @@ class ColumnHeaders(tk.Canvas):
1672
1673
 
1673
1674
  def get_redraw_selections(self, startc: int, endc: int) -> dict[str, set[int]]:
1674
1675
  d = defaultdict(set)
1675
- for item, box in self.MT.get_selection_items():
1676
- r1, c1, r2, c2 = box.coords
1676
+ for _, box in self.MT.get_selection_items():
1677
+ _, c1, _, c2 = box.coords
1677
1678
  for c in range(startc, endc):
1678
1679
  if c1 <= c and c2 > c:
1679
1680
  d[box.type_ if box.type_ != "rows" else "cells"].add(c)
@@ -1689,7 +1690,7 @@ class ColumnHeaders(tk.Canvas):
1689
1690
  if self.get_cell_kwargs(datacn, key="readonly"):
1690
1691
  return
1691
1692
  elif self.get_cell_kwargs(datacn, key="dropdown") or self.get_cell_kwargs(datacn, key="checkbox"):
1692
- if self.MT.event_opens_dropdown_or_checkbox(event):
1693
+ if event_opens_dropdown_or_checkbox(event):
1693
1694
  if self.get_cell_kwargs(datacn, key="dropdown"):
1694
1695
  self.open_dropdown_window(c, event=event)
1695
1696
  elif self.get_cell_kwargs(datacn, key="checkbox"):
@@ -1715,28 +1716,16 @@ class ColumnHeaders(tk.Canvas):
1715
1716
  state: str = "normal",
1716
1717
  dropdown: bool = False,
1717
1718
  ) -> bool:
1718
- text = None
1719
+ text = f"{self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)}"
1719
1720
  extra_func_key = "??"
1720
- if event is None or self.MT.event_opens_dropdown_or_checkbox(event):
1721
- if event is not None:
1722
- if hasattr(event, "keysym") and event.keysym == "Return":
1723
- extra_func_key = "Return"
1724
- elif hasattr(event, "keysym") and event.keysym == "F2":
1725
- extra_func_key = "F2"
1726
- text = self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)
1727
- elif event is not None and (
1728
- (hasattr(event, "keysym") and event.keysym == "BackSpace") or event.keycode in (8, 855638143)
1729
- ):
1730
- extra_func_key = "BackSpace"
1731
- text = ""
1732
- elif event is not None and (
1733
- (hasattr(event, "char") and event.char.isalpha())
1734
- or (hasattr(event, "char") and event.char.isdigit())
1735
- or (hasattr(event, "char") and event.char in symbols_set)
1736
- ):
1737
- extra_func_key = event.char
1738
- text = event.char
1739
- 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:
1740
1729
  return False
1741
1730
  if self.extra_begin_edit_cell_func:
1742
1731
  try:
@@ -1758,14 +1747,13 @@ class ColumnHeaders(tk.Canvas):
1758
1747
  return False
1759
1748
  else:
1760
1749
  text = text if isinstance(text, str) else f"{text}"
1761
- text = "" if text is None else text
1762
1750
  if self.PAR.ops.cell_auto_resize_enabled:
1763
1751
  if self.height_resizing_enabled:
1764
1752
  self.set_height_of_header_to_text(text)
1765
1753
  self.set_col_width_run_binding(c)
1766
1754
  if self.text_editor.open and c == self.text_editor.column:
1767
1755
  self.text_editor.set_text(self.text_editor.get() + "" if not isinstance(text, str) else text)
1768
- return
1756
+ return False
1769
1757
  self.hide_text_editor()
1770
1758
  if not self.MT.see(r=0, c=c, keep_yscroll=True, check_cell_visibility=True):
1771
1759
  self.MT.refresh()
@@ -1773,8 +1761,6 @@ class ColumnHeaders(tk.Canvas):
1773
1761
  y = 0
1774
1762
  w = self.MT.col_positions[c + 1] - x
1775
1763
  h = self.current_height + 1
1776
- if text is None:
1777
- text = self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)
1778
1764
  kwargs = {
1779
1765
  "menu_kwargs": DotDict(
1780
1766
  {
@@ -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
+ )
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
4
 
5
- from .vars import falsy, nonelike, truthy
5
+ from .constants import falsy, nonelike, truthy
6
6
 
7
7
 
8
8
  def is_none_like(o: object) -> bool:
@@ -35,6 +35,9 @@ from .other_classes import (
35
35
  from .types import (
36
36
  AnyIter,
37
37
  )
38
+ from .constants import (
39
+ symbols_set,
40
+ )
38
41
 
39
42
  unpickle_obj = pickle.loads
40
43
 
@@ -67,9 +70,15 @@ def get_data_from_clipboard(
67
70
  return [[data]]
68
71
 
69
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
+
70
79
  def tksheet_type_error(kwarg: str, valid_types: list[str], not_type: object) -> str:
71
80
  valid_types = ", ".join(f"{type_}" for type_ in valid_types)
72
- return f"Argument '{kwarg}' must be one of the following types: {valid_types}, " f"not {type(not_type)}."
81
+ return f"Argument '{kwarg}' must be one of the following types: {valid_types}, not {type(not_type)}."
73
82
 
74
83
 
75
84
  def new_tk_event(keysym: str) -> tk.Event:
@@ -78,6 +87,25 @@ def new_tk_event(keysym: str) -> tk.Event:
78
87
  return event
79
88
 
80
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
+
81
109
  def dropdown_search_function(
82
110
  search_for: object,
83
111
  data: Sequence[object],
@@ -230,6 +258,16 @@ def b_index(sorted_seq: Sequence[int], num_to_index: int) -> int:
230
258
  return idx
231
259
 
232
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
+
233
271
  def get_dropdown_kwargs(
234
272
  values: list = [],
235
273
  set_value: object = None,
@@ -546,35 +584,23 @@ def move_elements_by_mapping(
546
584
  new_idxs: dict[int, int],
547
585
  old_idxs: dict[int, int] | None = None,
548
586
  ) -> list[object]:
549
- # move elements of a list around, displacing
550
- # other elements based on mapping
551
- # of {old index: new index, ...}
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, ...}
552
591
  if old_idxs is None:
553
592
  old_idxs = dict(zip(new_idxs.values(), new_idxs))
554
593
 
555
- # create dummy list
556
594
  res = [0] * len(seq)
557
595
 
558
- # create generator of values yet to be put into res
559
- remaining = (e for i, e in enumerate(seq) if i not in new_idxs)
560
-
561
- # goes over res twice:
562
- # once to put elements being moved in new spots
563
- # then to fill remaining spots with remaining elements
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)
564
602
 
565
- # fill new indexes in res
566
- if len(new_idxs) > int(len(seq) / 2) - 1:
567
- # if moving a lot of items better to do comprehension
568
- return [
569
- next(remaining) if i_ not in old_idxs else e_
570
- for i_, e_ in enumerate(seq[old_idxs[i]] if i in old_idxs else e for i, e in enumerate(res))
571
- ]
572
- else:
573
- # if just moving a few items assignments are fine
574
- for old, new in new_idxs.items():
575
- res[new] = seq[old]
576
- # fill remaining indexes
577
- return [next(remaining) if i not in old_idxs else e for i, e in enumerate(res)]
603
+ return res
578
604
 
579
605
 
580
606
  def move_elements_to(
@@ -609,23 +635,15 @@ def get_new_indexes(
609
635
 
610
636
 
611
637
  def insert_items(
612
- seq: list[object] | tuple[object],
638
+ seq: list[object],
613
639
  to_insert: dict[int, object],
614
640
  seq_len_func: Callable | None = None,
615
- ) -> list:
616
- # inserts many items into a list using a dict of reverse sorted order of
617
- # {index: value, index: value, ...}
618
- res = []
619
- extended = 0
620
- for i, (idx, v) in enumerate(reversed(to_insert.items())):
621
- # need to extend seq if it's not long enough
622
- if seq_len_func and idx - i > len(seq):
623
- seq_len_func(idx - i - 1)
624
- res.extend(seq[extended : idx - i])
625
- extended += idx - i - extended
626
- res.append(v)
627
- res.extend(seq[extended:])
628
- 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]
629
647
  return seq
630
648
 
631
649
 
@@ -633,8 +651,7 @@ def data_to_displayed_idxs(
633
651
  to_convert: list[int],
634
652
  displayed: list[int],
635
653
  ) -> list[int]:
636
- data_indexes = set(to_convert)
637
- 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)]
638
655
 
639
656
 
640
657
  def displayed_to_data_idxs(
@@ -651,6 +668,8 @@ def rounded_box_coords(
651
668
  y2: float,
652
669
  radius: int = 5,
653
670
  ) -> tuple[float]:
671
+ if y2 - y1 < 2 or x2 - x1 < 2:
672
+ return x1, y1, x2, y1, x2, y2, x1, y2
654
673
  return (
655
674
  x1 + radius,
656
675
  y1,