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 CHANGED
@@ -4,7 +4,7 @@
4
4
  tksheet - A Python tkinter table widget
5
5
  """
6
6
 
7
- __version__ = "7.3.0"
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,
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 .vars import (
47
- USER_OS,
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
- fill_iid = self.MT.create_selection_box(0, c, len(self.MT.row_positions) - 1, c + 1, "columns", ext=ext)
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 fill_iid
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 item, box in self.MT.get_selection_items():
1661
- r1, c1, r2, c2 = box.coords
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 self.MT.event_opens_dropdown_or_checkbox(event):
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 = None
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 event is None or self.MT.event_opens_dropdown_or_checkbox(event):
1706
- if event is not None:
1707
- if hasattr(event, "keysym") and event.keysym == "Return":
1708
- extra_func_key = "Return"
1709
- elif hasattr(event, "keysym") and event.keysym == "F2":
1710
- extra_func_key = "F2"
1711
- text = self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)
1712
- elif event is not None and (
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
@@ -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:
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}, " f"not {type(not_type)}."
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: Iterator[int] | int) -> Iterator[int]:
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 unpack(t: tuple[object] | tuple[Iterator[object]]) -> tuple[object]:
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: Iterator[int]) -> bool:
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: Iterator,
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: Iterator[int]) -> list[int]:
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, displacing
539
- # other elements based on mapping
540
- # 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, ...}
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
- # create generator of values yet to be put into res
548
- remaining = (e for i, e in enumerate(seq) if i not in new_idxs)
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
- # goes over res twice:
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] | tuple[object],
638
+ seq: list[object],
602
639
  to_insert: dict[int, object],
603
640
  seq_len_func: Callable | None = None,
604
- ) -> list:
605
- # inserts many items into a list using a dict of reverse sorted order of
606
- # {index: value, index: value, ...}
607
- res = []
608
- extended = 0
609
- for i, (idx, v) in enumerate(reversed(to_insert.items())):
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
- data_indexes = set(to_convert)
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: Iterator, o: object) -> Generator[object, object]:
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: Iterator, type_: str) -> None:
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: Iterator, itr2: Iterator, type_: str) -> None:
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 | Iterator | None = None,
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]: