tksheet 7.4.3__tar.gz → 7.4.5__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 (27) hide show
  1. {tksheet-7.4.3/tksheet.egg-info → tksheet-7.4.5}/PKG-INFO +4 -2
  2. {tksheet-7.4.3 → tksheet-7.4.5}/pyproject.toml +8 -4
  3. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/__init__.py +2 -2
  4. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/column_headers.py +74 -69
  5. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/constants.py +2 -2
  6. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/formatters.py +20 -36
  7. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/functions.py +37 -47
  8. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/main_table.py +239 -376
  9. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/other_classes.py +10 -16
  10. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/row_index.py +116 -83
  11. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/sheet.py +133 -152
  12. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/sheet_options.py +2 -0
  13. tksheet-7.4.5/tksheet/sorting.py +531 -0
  14. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/text_editor.py +9 -3
  15. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/top_left_rectangle.py +5 -6
  16. {tksheet-7.4.3 → tksheet-7.4.5/tksheet.egg-info}/PKG-INFO +4 -2
  17. tksheet-7.4.3/tksheet/sorting.py +0 -318
  18. {tksheet-7.4.3 → tksheet-7.4.5}/LICENSE.txt +0 -0
  19. {tksheet-7.4.3 → tksheet-7.4.5}/README.md +0 -0
  20. {tksheet-7.4.3 → tksheet-7.4.5}/setup.cfg +0 -0
  21. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/colors.py +0 -0
  22. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/find_window.py +0 -0
  23. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/themes.py +0 -0
  24. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet/tksheet_types.py +0 -0
  25. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet.egg-info/SOURCES.txt +0 -0
  26. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet.egg-info/dependency_links.txt +0 -0
  27. {tksheet-7.4.3 → tksheet-7.4.5}/tksheet.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.3
3
+ Version: 7.4.5
4
4
  Summary: Tkinter table / sheet and treeview widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
@@ -24,7 +24,9 @@ License: Copyright (c) 2019 ragardner and open source contributors
24
24
  SOFTWARE.
25
25
 
26
26
  Project-URL: Homepage, https://github.com/ragardner/tksheet
27
- Project-URL: Bug Reports, https://github.com/ragardner/tksheet/issues
27
+ Project-URL: Documentation, https://github.com/ragardner/tksheet/wiki/Version-7
28
+ Project-URL: Changelog, https://github.com/ragardner/tksheet/blob/master/docs/CHANGELOG.md
29
+ Project-URL: Issues, https://github.com/ragardner/tksheet/issues
28
30
  Project-URL: Funding, https://github.com/ragardner
29
31
  Keywords: tkinter,table,widget,tree,treeview,sheet,grid,tk
30
32
  Classifier: Development Status :: 5 - Production/Stable
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "tksheet"
7
7
  description = "Tkinter table / sheet and treeview widget"
8
8
  readme = "README.md"
9
- version = "7.4.3"
9
+ version = "7.4.5"
10
10
  authors = [{ name = "ragardner", email = "github@ragardner.simplelogin.com" }]
11
11
  requires-python = ">=3.8"
12
12
  license = {file = "LICENSE.txt"}
@@ -25,9 +25,13 @@ classifiers = [
25
25
  ]
26
26
 
27
27
  [project.urls]
28
- "Homepage" = "https://github.com/ragardner/tksheet"
29
- "Bug Reports" = "https://github.com/ragardner/tksheet/issues"
30
- "Funding" = "https://github.com/ragardner"
28
+ Homepage = "https://github.com/ragardner/tksheet"
29
+ Documentation = "https://github.com/ragardner/tksheet/wiki/Version-7"
30
+ Changelog = "https://github.com/ragardner/tksheet/blob/master/docs/CHANGELOG.md"
31
+ Issues = "https://github.com/ragardner/tksheet/issues"
32
+ Funding = "https://github.com/ragardner"
31
33
 
32
34
  [tool.ruff]
33
35
  line-length = 120
36
+ extend-select = ["I", "F", "C4", "E", "B", "SIM"]
37
+ fix = true
@@ -4,7 +4,7 @@
4
4
  tksheet - A Python tkinter table widget
5
5
  """
6
6
 
7
- __version__ = "7.4.3"
7
+ __version__ = "7.4.5"
8
8
 
9
9
  from .colors import (
10
10
  color_map,
@@ -93,7 +93,7 @@ from .other_classes import (
93
93
  from .row_index import RowIndex
94
94
  from .sheet import Dropdown, Sheet
95
95
  from .sheet_options import new_sheet_options
96
- from .sorting import natural_sort_key
96
+ from .sorting import fast_sort_key, natural_sort_key, version_sort_key
97
97
  from .text_editor import (
98
98
  TextEditor,
99
99
  TextEditorTkText,
@@ -89,7 +89,7 @@ class ColumnHeaders(tk.Canvas):
89
89
  self.edit_cell_enabled = False
90
90
  self.dragged_col = None
91
91
  self.visible_col_dividers = {}
92
- self.col_height_resize_bbox = tuple()
92
+ self.col_height_resize_bbox = ()
93
93
  self.cell_options = {}
94
94
  self.rsz_w = None
95
95
  self.rsz_h = None
@@ -255,28 +255,32 @@ class ColumnHeaders(tk.Canvas):
255
255
  def shift_b1_press(self, event: object) -> None:
256
256
  self.mouseclick_outside_editor_or_dropdown_all_canvases(inside=True)
257
257
  c = self.MT.identify_col(x=event.x)
258
- if (self.drag_and_drop_enabled or self.col_selection_enabled) and self.rsz_h is None and self.rsz_w is None:
259
- if c < len(self.MT.col_positions) - 1:
260
- c_selected = self.MT.col_selected(c)
261
- if not c_selected and self.col_selection_enabled:
262
- if self.MT.selected and self.MT.selected.type_ == "columns":
263
- r_to_sel, c_to_sel = self.MT.selected.row, self.MT.selected.column
264
- self.MT.deselect("all", redraw=False)
265
- self.being_drawn_item = self.MT.create_selection_box(
266
- *self.get_shift_select_box(c, c_to_sel), "columns"
267
- )
268
- self.MT.set_currently_selected(r_to_sel, c_to_sel, self.being_drawn_item)
269
- else:
270
- self.being_drawn_item = self.select_col(c, run_binding_func=False)
271
- self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
272
- sel_event = self.MT.get_select_event(being_drawn_item=self.being_drawn_item)
273
- try_binding(self.shift_selection_binding_func, sel_event)
274
- self.PAR.emit_event("<<SheetSelect>>", data=sel_event)
275
- elif c_selected:
276
- self.dragged_col = DraggedRowColumn(
277
- dragged=c,
278
- to_move=sorted(self.MT.get_selected_cols()),
258
+ if (
259
+ (self.drag_and_drop_enabled or self.col_selection_enabled)
260
+ and self.rsz_h is None
261
+ and self.rsz_w is None
262
+ and c < len(self.MT.col_positions) - 1
263
+ ):
264
+ c_selected = self.MT.col_selected(c)
265
+ if not c_selected and self.col_selection_enabled:
266
+ if self.MT.selected and self.MT.selected.type_ == "columns":
267
+ r_to_sel, c_to_sel = self.MT.selected.row, self.MT.selected.column
268
+ self.MT.deselect("all", redraw=False)
269
+ self.being_drawn_item = self.MT.create_selection_box(
270
+ *self.get_shift_select_box(c, c_to_sel), "columns"
279
271
  )
272
+ self.MT.set_currently_selected(r_to_sel, c_to_sel, self.being_drawn_item)
273
+ else:
274
+ self.being_drawn_item = self.select_col(c, run_binding_func=False)
275
+ self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
276
+ sel_event = self.MT.get_select_event(being_drawn_item=self.being_drawn_item)
277
+ try_binding(self.shift_selection_binding_func, sel_event)
278
+ self.PAR.emit_event("<<SheetSelect>>", data=sel_event)
279
+ elif c_selected:
280
+ self.dragged_col = DraggedRowColumn(
281
+ dragged=c,
282
+ to_move=sorted(self.MT.get_selected_cols()),
283
+ )
280
284
 
281
285
  def get_shift_select_box(self, c: int, min_c: int) -> tuple[int, int, int, int, str]:
282
286
  if c >= min_c:
@@ -351,12 +355,11 @@ class ColumnHeaders(tk.Canvas):
351
355
  self.rsz_h = None
352
356
  except Exception:
353
357
  self.rsz_h = None
354
- if not mouse_over_resize:
355
- if self.MT.col_selected(self.MT.identify_col(event, allow_end=False)):
356
- mouse_over_selected = True
357
- if self.MT.current_cursor != "hand2":
358
- self.config(cursor="hand2")
359
- self.MT.current_cursor = "hand2"
358
+ if not mouse_over_resize and self.MT.col_selected(self.MT.identify_col(event, allow_end=False)):
359
+ mouse_over_selected = True
360
+ if self.MT.current_cursor != "hand2":
361
+ self.config(cursor="hand2")
362
+ self.MT.current_cursor = "hand2"
360
363
  if not mouse_over_resize and not mouse_over_selected:
361
364
  self.MT.reset_mouse_motion_creations()
362
365
  try_binding(self.extra_motion_func, event)
@@ -718,23 +721,19 @@ class ColumnHeaders(tk.Canvas):
718
721
  self.MT.set_xviews("moveto", 1)
719
722
 
720
723
  def event_over_dropdown(self, c: int, datacn: int, event: object, canvasx: float) -> bool:
721
- if (
724
+ return (
722
725
  event.y < self.MT.header_txt_height + 5
723
726
  and self.get_cell_kwargs(datacn, key="dropdown")
724
727
  and canvasx < self.MT.col_positions[c + 1]
725
728
  and canvasx > self.MT.col_positions[c + 1] - self.MT.header_txt_height - 4
726
- ):
727
- return True
728
- return False
729
+ )
729
730
 
730
731
  def event_over_checkbox(self, c: int, datacn: int, event: object, canvasx: float) -> bool:
731
- if (
732
+ return (
732
733
  event.y < self.MT.header_txt_height + 5
733
734
  and self.get_cell_kwargs(datacn, key="checkbox")
734
735
  and canvasx < self.MT.col_positions[c] + self.MT.header_txt_height + 4
735
- ):
736
- return True
737
- return False
736
+ )
738
737
 
739
738
  def drag_width_resize(self) -> None:
740
739
  new_col_pos = int(self.coords("rwl")[0])
@@ -874,6 +873,8 @@ class ColumnHeaders(tk.Canvas):
874
873
  columns = list(range(0, len(self.MT.col_positions) - 1))
875
874
  event_data = self.MT.new_event_dict("edit_table")
876
875
  try_binding(self.MT.extra_begin_sort_cells_func, event_data)
876
+ if key is None:
877
+ key = self.PAR.ops.sort_key
877
878
  for c in columns:
878
879
  datacn = self.MT.datacn(c)
879
880
  for r, val in enumerate(
@@ -923,6 +924,8 @@ class ColumnHeaders(tk.Canvas):
923
924
  return event_data
924
925
  column = self.MT.selected.column
925
926
  if try_binding(self.ch_extra_begin_sort_rows_func, event_data, "begin_move_rows"):
927
+ if key is None:
928
+ key = self.PAR.ops.sort_key
926
929
  disp_new_idxs, disp_row_ctr = {}, 0
927
930
  if self.ops.treeview:
928
931
  new_nodes_order, data_new_idxs = sort_tree_view(
@@ -1179,10 +1182,7 @@ class ColumnHeaders(tk.Canvas):
1179
1182
  # table
1180
1183
  if self.MT.data:
1181
1184
  if self.MT.all_rows_displayed:
1182
- if visible_only:
1183
- iterable = range(*self.MT.visible_text_rows)
1184
- else:
1185
- iterable = range(0, len(self.MT.data))
1185
+ iterable = range(*self.MT.visible_text_rows) if visible_only else range(0, len(self.MT.data))
1186
1186
  else:
1187
1187
  if visible_only:
1188
1188
  start_row, end_row = self.MT.visible_text_rows
@@ -1199,11 +1199,13 @@ class ColumnHeaders(tk.Canvas):
1199
1199
  qconf(qtxtm, text=txt, font=qfont)
1200
1200
  b = qbbox(qtxtm)
1201
1201
  if (
1202
- self.MT.get_cell_kwargs(datarn, datacn, key="dropdown")
1203
- or self.MT.get_cell_kwargs(datarn, datacn, key="checkbox")
1204
- ) and (tw := b[2] - b[0] + qtxth + 7) > w:
1205
- w = tw
1206
- elif (tw := b[2] - b[0] + 7) > w:
1202
+ (
1203
+ self.MT.get_cell_kwargs(datarn, datacn, key="dropdown")
1204
+ or self.MT.get_cell_kwargs(datarn, datacn, key="checkbox")
1205
+ )
1206
+ and (tw := b[2] - b[0] + qtxth + 7) > w
1207
+ or (tw := b[2] - b[0] + 7) > w
1208
+ ):
1207
1209
  w = tw
1208
1210
  if hw > w:
1209
1211
  w = hw
@@ -1512,9 +1514,8 @@ class ColumnHeaders(tk.Canvas):
1512
1514
  col_pos_exists: bool,
1513
1515
  set_scrollregion: bool,
1514
1516
  ) -> bool:
1515
- if set_scrollregion:
1516
- if not self.configure_scrollregion(last_col_line_pos=last_col_line_pos):
1517
- return False
1517
+ if set_scrollregion and not self.configure_scrollregion(last_col_line_pos=last_col_line_pos):
1518
+ return False
1518
1519
  self.hidd_text.update(self.disp_text)
1519
1520
  self.disp_text = {}
1520
1521
  self.hidd_high.update(self.disp_high)
@@ -1630,7 +1631,7 @@ class ColumnHeaders(tk.Canvas):
1630
1631
  if not text:
1631
1632
  continue
1632
1633
  max_lines = int((self.current_height - top - 2) / txt_h)
1633
- for text in wrap_text(
1634
+ for wrapped in wrap_text(
1634
1635
  text=text,
1635
1636
  max_width=max_width,
1636
1637
  max_lines=max_lines,
@@ -1644,7 +1645,7 @@ class ColumnHeaders(tk.Canvas):
1644
1645
  if showing:
1645
1646
  self.itemconfig(
1646
1647
  iid,
1647
- text=text,
1648
+ text=wrapped,
1648
1649
  fill=fill,
1649
1650
  font=font,
1650
1651
  anchor=align,
@@ -1652,7 +1653,7 @@ class ColumnHeaders(tk.Canvas):
1652
1653
  else:
1653
1654
  self.itemconfig(
1654
1655
  iid,
1655
- text=text,
1656
+ text=wrapped,
1656
1657
  fill=fill,
1657
1658
  font=font,
1658
1659
  anchor=align,
@@ -1663,7 +1664,7 @@ class ColumnHeaders(tk.Canvas):
1663
1664
  iid = self.create_text(
1664
1665
  draw_x,
1665
1666
  draw_y,
1666
- text=text,
1667
+ text=wrapped,
1667
1668
  fill=fill,
1668
1669
  font=font,
1669
1670
  anchor=align,
@@ -1791,7 +1792,7 @@ class ColumnHeaders(tk.Canvas):
1791
1792
  self.text_editor.set_text(self.text_editor.get() + "" if not isinstance(text, str) else text)
1792
1793
  return False
1793
1794
  self.hide_text_editor()
1794
- if not self.MT.see(r=0, c=c, keep_yscroll=True, check_cell_visibility=True):
1795
+ if not self.MT.see(0, c, keep_yscroll=True):
1795
1796
  self.MT.main_table_redraw_grid_and_text(True, True)
1796
1797
  x = self.MT.col_positions[c] + 1
1797
1798
  y = 0
@@ -2239,16 +2240,18 @@ class ColumnHeaders(tk.Canvas):
2239
2240
  self.MT._headers[datacn] = value
2240
2241
 
2241
2242
  def input_valid_for_cell(self, datacn: int, value: object, check_readonly: bool = True) -> bool:
2242
- if check_readonly and self.get_cell_kwargs(datacn, key="readonly"):
2243
+ kwargs = self.get_cell_kwargs(datacn, key=None)
2244
+ if check_readonly and "readonly" in kwargs:
2243
2245
  return False
2244
- if self.get_cell_kwargs(datacn, key="checkbox"):
2246
+ elif "checkbox" in kwargs:
2245
2247
  return is_bool_like(value)
2246
- if self.cell_equal_to(datacn, value):
2247
- return False
2248
- kwargs = self.get_cell_kwargs(datacn, key="dropdown")
2249
- if kwargs and kwargs["validate_input"] and value not in kwargs["values"]:
2250
- return False
2251
- return True
2248
+ else:
2249
+ return not (
2250
+ self.cell_equal_to(datacn, value)
2251
+ or (kwargs := kwargs.get("dropdown", {}))
2252
+ and kwargs["validate_input"]
2253
+ and value not in kwargs["values"]
2254
+ )
2252
2255
 
2253
2256
  def cell_equal_to(self, datacn: int, value: object) -> bool:
2254
2257
  self.fix_header(datacn)
@@ -2303,12 +2306,13 @@ class ColumnHeaders(tk.Canvas):
2303
2306
  return value
2304
2307
 
2305
2308
  def get_value_for_empty_cell(self, datacn: int, c_ops: bool = True) -> object:
2306
- if self.get_cell_kwargs(datacn, key="checkbox", cell=c_ops):
2309
+ kwargs = self.get_cell_kwargs(datacn, key=None, cell=c_ops)
2310
+ if "checkbox" in kwargs:
2307
2311
  return False
2308
- kwargs = self.get_cell_kwargs(datacn, key="dropdown", cell=c_ops)
2309
- if kwargs and kwargs["validate_input"] and kwargs["values"]:
2312
+ elif (kwargs := kwargs.get("dropdown", {})) and kwargs["validate_input"] and kwargs["values"]:
2310
2313
  return kwargs["values"][0]
2311
- return ""
2314
+ else:
2315
+ return ""
2312
2316
 
2313
2317
  def get_empty_header_seq(self, end: int, start: int = 0, c_ops: bool = True) -> list[object]:
2314
2318
  return [self.get_value_for_empty_cell(datacn, c_ops=c_ops) for datacn in range(start, end)]
@@ -2376,7 +2380,8 @@ class ColumnHeaders(tk.Canvas):
2376
2380
  if redraw:
2377
2381
  self.MT.refresh()
2378
2382
 
2379
- def get_cell_kwargs(self, datacn: int, key: Hashable = "dropdown", cell: bool = True) -> dict:
2380
- if cell and datacn in self.cell_options and key in self.cell_options[datacn]:
2381
- return self.cell_options[datacn][key]
2382
- return {}
2383
+ def get_cell_kwargs(self, datacn: int, key: Hashable | None = "dropdown", cell: bool = True) -> dict:
2384
+ if cell and datacn in self.cell_options:
2385
+ return self.cell_options[datacn] if key is None else self.cell_options[datacn].get(key, {})
2386
+ else:
2387
+ return {}
@@ -7,8 +7,8 @@ ctrl_key: str = "Command" if USER_OS == "darwin" else "Control"
7
7
  rc_binding: str = "<2>" if USER_OS == "darwin" else "<3>"
8
8
  symbols_set: set[str] = set("""!#$%&'()*+,-./:;"@[]^_`{|}~>?= \\""")
9
9
  nonelike: set[object] = {None, "none", ""}
10
- truthy: set[object] = {True, "true", "t", "yes", "y", "on", "1", 1, 1.0}
11
- falsy: set[object] = {False, "false", "f", "no", "n", "off", "0", 0, 0.0}
10
+ truthy: set[object] = {True, "true", "t", "yes", "y", "on", "1"}
11
+ falsy: set[object] = {False, "false", "f", "no", "n", "off", "0"}
12
12
  _test_str: str = "aiW_-|"
13
13
 
14
14
  val_modifying_options: set[str] = {"checkbox", "format", "dropdown"}
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
+ from contextlib import suppress
4
5
 
5
6
  from .constants import falsy, nonelike, truthy
6
7
 
7
8
 
8
9
  def is_none_like(o: object) -> bool:
9
- if (isinstance(o, str) and o.lower().replace(" ", "") in nonelike) or o in nonelike:
10
- return True
11
- return False
10
+ return (isinstance(o, str) and o.lower().replace(" ", "") in nonelike) or o in nonelike
12
11
 
13
12
 
14
13
  def to_int(o: object, **kwargs) -> int:
@@ -42,18 +41,9 @@ def alt_to_percentage(o: object, **kwargs) -> float:
42
41
  def to_bool(val: object, **kwargs) -> bool:
43
42
  if isinstance(val, bool):
44
43
  return val
45
- if isinstance(val, str):
46
- v = val.lower()
47
- else:
48
- v = val
49
- if "truthy" in kwargs:
50
- _truthy = kwargs["truthy"]
51
- else:
52
- _truthy = truthy
53
- if "falsy" in kwargs:
54
- _falsy = kwargs["falsy"]
55
- else:
56
- _falsy = falsy
44
+ v = val.lower() if isinstance(val, str) else val
45
+ _truthy = kwargs.get("truthy", truthy)
46
+ _falsy = kwargs.get("falsy", falsy)
57
47
  if v in _truthy:
58
48
  return True
59
49
  elif v in _falsy:
@@ -191,16 +181,16 @@ def formatter(
191
181
  **kwargs,
192
182
  ) -> dict:
193
183
  return {
194
- **dict(
195
- datatypes=datatypes,
196
- format_function=format_function,
197
- to_str_function=to_str_function,
198
- invalid_value=invalid_value,
199
- nullable=nullable,
200
- pre_format_function=pre_format_function,
201
- post_format_function=post_format_function,
202
- clipboard_function=clipboard_function,
203
- ),
184
+ **{
185
+ "datatypes": datatypes,
186
+ "format_function": format_function,
187
+ "to_str_function": to_str_function,
188
+ "invalid_value": invalid_value,
189
+ "nullable": nullable,
190
+ "pre_format_function": pre_format_function,
191
+ "post_format_function": post_format_function,
192
+ "clipboard_function": clipboard_function,
193
+ },
204
194
  **kwargs,
205
195
  }
206
196
 
@@ -219,10 +209,8 @@ def format_data(
219
209
  if nullable and is_none_like(value):
220
210
  value = None
221
211
  else:
222
- try:
212
+ with suppress(Exception):
223
213
  value = format_function(value, **kwargs)
224
- except Exception:
225
- pass
226
214
  if post_format_function and isinstance(value, datatypes):
227
215
  value = post_format_function(value)
228
216
  return value
@@ -243,7 +231,7 @@ def data_to_str(
243
231
  return to_str_function(value, **kwargs)
244
232
 
245
233
 
246
- def get_data_with_valid_check(value="", datatypes: tuple[()] | tuple[object] | object = tuple(), invalid_value="NA"):
234
+ def get_data_with_valid_check(value="", datatypes: tuple[()] | tuple[object] | object = (), invalid_value="NA"):
247
235
  if isinstance(value, datatypes):
248
236
  return value
249
237
  return invalid_value
@@ -274,12 +262,10 @@ class Formatter:
274
262
  ) -> None:
275
263
  if nullable:
276
264
  if isinstance(datatypes, (list, tuple)):
277
- datatypes = tuple({type_ for type_ in datatypes} | {type(None)})
265
+ datatypes = tuple(set(datatypes) | {type(None)})
278
266
  else:
279
267
  datatypes = (datatypes, type(None))
280
- elif isinstance(datatypes, (list, tuple)) and type(None) in datatypes:
281
- raise TypeError("Non-nullable cells cannot have NoneType as a datatype.")
282
- elif datatypes is type(None):
268
+ elif isinstance(datatypes, (list, tuple)) and type(None) in datatypes or datatypes is type(None):
283
269
  raise TypeError("Non-nullable cells cannot have NoneType as a datatype.")
284
270
  self.kwargs = kwargs
285
271
  self.valid_datatypes = datatypes
@@ -305,9 +291,7 @@ class Formatter:
305
291
  def valid(self, value: object = None) -> bool:
306
292
  if value is None:
307
293
  value = self.value
308
- if isinstance(value, self.valid_datatypes):
309
- return True
310
- return False
294
+ return isinstance(value, self.valid_datatypes)
311
295
 
312
296
  def format_data(self, value: object) -> object:
313
297
  if self.pre_format_function:
@@ -15,7 +15,7 @@ from typing import Literal
15
15
  from .colors import color_map
16
16
  from .constants import align_value_error, symbols_set
17
17
  from .formatters import to_bool
18
- from .other_classes import Box_nt, DotDict, EventDataDict, Highlight, Loc, Span
18
+ from .other_classes import DotDict, EventDataDict, Highlight, Loc, Span
19
19
  from .tksheet_types import AnyIter
20
20
 
21
21
  unpickle_obj = pickle.loads
@@ -279,10 +279,6 @@ def float_to_int(f: int | float) -> int | float:
279
279
  return int(f)
280
280
 
281
281
 
282
- def selection_box_tup_to_dict(box: tuple) -> dict:
283
- return {Box_nt(*box[:-1]): box[-1]}
284
-
285
-
286
282
  def event_dict(
287
283
  name: str = None,
288
284
  sheet: object = None,
@@ -334,15 +330,13 @@ def event_dict(
334
330
  ),
335
331
  named_spans=DotDict() if named_spans is None else named_spans,
336
332
  options=DotDict(),
337
- selection_boxes=(
338
- {} if boxes is None else selection_box_tup_to_dict(boxes) if isinstance(boxes, tuple) else boxes
339
- ),
340
- selected=tuple() if selected is None else selected,
341
- being_selected=tuple() if being_selected is None else being_selected,
333
+ selection_boxes={} if boxes is None else boxes,
334
+ selected=() if selected is None else selected,
335
+ being_selected=() if being_selected is None else being_selected,
342
336
  data=[] if data is None else data,
343
337
  key="" if key is None else key,
344
338
  value=None if value is None else value,
345
- loc=tuple() if loc is None else loc,
339
+ loc=() if loc is None else loc,
346
340
  row=row,
347
341
  column=column,
348
342
  resized=DotDict(
@@ -424,7 +418,7 @@ def push_n(num: int, sorted_seq: Sequence[int]) -> int:
424
418
 
425
419
 
426
420
  def get_dropdown_kwargs(
427
- values: list = [],
421
+ values: list[object] | None = None,
428
422
  set_value: object = None,
429
423
  state: str = "normal",
430
424
  redraw: bool = True,
@@ -435,7 +429,7 @@ def get_dropdown_kwargs(
435
429
  text: None | str = None,
436
430
  ) -> dict:
437
431
  return {
438
- "values": values,
432
+ "values": [] if values is None else values,
439
433
  "set_value": set_value,
440
434
  "state": state,
441
435
  "redraw": redraw,
@@ -753,6 +747,17 @@ def move_elements_by_mapping(
753
747
  return [seq[old_idxs[i]] if i in old_idxs else next(remaining_values) for i in range(len(seq))]
754
748
 
755
749
 
750
+ def move_elements_by_mapping_gen(
751
+ seq: list[object],
752
+ new_idxs: dict[int, int],
753
+ old_idxs: dict[int, int] | None = None,
754
+ ) -> Generator[object]:
755
+ if old_idxs is None:
756
+ old_idxs = dict(zip(new_idxs.values(), new_idxs))
757
+ remaining_values = (e for i, e in enumerate(seq) if i not in new_idxs)
758
+ return (seq[old_idxs[i]] if i in old_idxs else next(remaining_values) for i in range(len(seq)))
759
+
760
+
756
761
  def move_elements_to(
757
762
  seq: list[object],
758
763
  move_to: int,
@@ -770,15 +775,16 @@ def move_elements_to(
770
775
 
771
776
  def get_new_indexes(
772
777
  move_to: int,
773
- to_move: list[int],
778
+ to_move: Iterable[int],
774
779
  get_inverse: bool = False,
775
- ) -> tuple[dict]:
780
+ ) -> tuple[dict[int, int]] | dict[int, int]:
776
781
  """
782
+ move_to: A positive int, could possibly be the same as an element of to_move
783
+ to_move: An iterable of ints, could be a dict, could be in any order
777
784
  returns {old idx: new idx, ...}
778
785
  """
779
786
  offset = sum(1 for i in to_move if i < move_to)
780
- new_idxs = range(move_to - offset, move_to - offset + len(to_move))
781
- new_idxs = {old: new for old, new in zip(to_move, new_idxs)}
787
+ new_idxs = dict(zip(to_move, range(move_to - offset, move_to - offset + len(to_move))))
782
788
  if get_inverse:
783
789
  return new_idxs, dict(zip(new_idxs.values(), new_idxs))
784
790
  return new_idxs
@@ -789,6 +795,11 @@ def insert_items(
789
795
  to_insert: dict[int, object],
790
796
  seq_len_func: Callable | None = None,
791
797
  ) -> list[object]:
798
+ """
799
+ seq: list[object]
800
+ to_insert: keys are ints sorted in reverse, representing list indexes to insert items.
801
+ Values are any object, e.g. {1: 200, 0: 200}
802
+ """
792
803
  if to_insert:
793
804
  if seq_len_func and next(iter(to_insert)) >= len(seq) + len(to_insert):
794
805
  seq_len_func(next(iter(to_insert)) - len(to_insert))
@@ -801,7 +812,7 @@ def del_placeholder_dict_key(
801
812
  d: dict[Hashable, object],
802
813
  k: Hashable,
803
814
  v: object,
804
- p: tuple = tuple(),
815
+ p: tuple = (),
805
816
  ) -> dict[Hashable, object]:
806
817
  if p in d:
807
818
  del d[p]
@@ -876,24 +887,12 @@ def rounded_box_coords(
876
887
  )
877
888
 
878
889
 
879
- def diff_list(seq: list[float]) -> list[int]:
880
- return [
881
- int(b - a)
882
- for a, b in zip(
883
- seq,
884
- islice(seq, 1, None),
885
- )
886
- ]
887
-
888
-
889
890
  def diff_gen(seq: list[float]) -> Generator[int]:
890
- return (
891
- int(b - a)
892
- for a, b in zip(
893
- seq,
894
- islice(seq, 1, None),
895
- )
896
- )
891
+ it = iter(seq)
892
+ a = next(it)
893
+ for b in it:
894
+ yield int(b - a)
895
+ a = b
897
896
 
898
897
 
899
898
  def gen_coords(
@@ -1505,14 +1504,8 @@ def span_ranges(
1505
1504
  ) -> tuple[Generator[int], Generator[int]]:
1506
1505
  rng_from_r = 0 if span.from_r is None else span.from_r
1507
1506
  rng_from_c = 0 if span.from_c is None else span.from_c
1508
- if span.upto_r is None:
1509
- rng_upto_r = totalrows() if isinstance(totalrows, Callable) else totalrows
1510
- else:
1511
- rng_upto_r = span.upto_r
1512
- if span.upto_c is None:
1513
- rng_upto_c = totalcols() if isinstance(totalcols, Callable) else totalcols
1514
- else:
1515
- rng_upto_c = span.upto_c
1507
+ rng_upto_r = (totalrows() if isinstance(totalrows, Callable) else totalrows) if span.upto_r is None else span.upto_r
1508
+ rng_upto_c = (totalcols() if isinstance(totalcols, Callable) else totalcols) if span.upto_c is None else span.upto_c
1516
1509
  return range(rng_from_r, rng_upto_r), range(rng_from_c, rng_upto_c)
1517
1510
 
1518
1511
 
@@ -1674,10 +1667,7 @@ def span_idxs_post_move(
1674
1667
  newupto_colrange = newupto
1675
1668
  else:
1676
1669
  oldfrom = int(span[f"from_{axis}"])
1677
- if not oldfrom:
1678
- newfrom = 0
1679
- else:
1680
- newfrom = full_new_idxs[oldfrom]
1670
+ newfrom = 0 if not oldfrom else full_new_idxs[oldfrom]
1681
1671
  newupto = None
1682
1672
  oldupto_colrange = total
1683
1673
  newupto_colrange = oldupto_colrange