tksheet 7.5.5__py3-none-any.whl → 7.5.8__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/functions.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  import csv
4
5
  import io
5
6
  import re
@@ -9,6 +10,7 @@ from collections import deque
9
10
  from collections.abc import Callable, Generator, Hashable, Iterable, Iterator, Sequence
10
11
  from difflib import SequenceMatcher
11
12
  from itertools import chain, islice, repeat
13
+ from types import ModuleType
12
14
  from typing import Any, Literal
13
15
 
14
16
  from .colors import color_map
@@ -16,7 +18,6 @@ from .constants import align_value_error, symbols_set
16
18
  from .formatters import to_bool
17
19
  from .other_classes import DotDict, EventDataDict, Highlight, Loc, Span
18
20
 
19
- lines_re = re.compile(r"[^\n]+")
20
21
  ORD_A = ord("A")
21
22
 
22
23
 
@@ -33,8 +34,8 @@ def wrap_text(
33
34
  line_width = 0
34
35
  if wrap == "c":
35
36
  current_line = []
36
- for match in lines_re.finditer(text):
37
- for char in match.group():
37
+ for line in text.split("\n"):
38
+ for char in line:
38
39
  try:
39
40
  char_width = widths[char]
40
41
  except KeyError:
@@ -75,8 +76,8 @@ def wrap_text(
75
76
  space_width = char_width_fn(" ")
76
77
  current_line = []
77
78
 
78
- for match in lines_re.finditer(text):
79
- for i, word in enumerate(match.group().split()):
79
+ for line in text.split("\n"):
80
+ for i, word in enumerate(line.split()):
80
81
  # if we're going to next word and
81
82
  # if a space fits on the end of the current line we add one
82
83
  if i and line_width + space_width < max_width:
@@ -155,10 +156,10 @@ def wrap_text(
155
156
  line_width = 0
156
157
 
157
158
  else:
158
- for match in lines_re.finditer(text):
159
+ for line in text.split("\n"):
159
160
  line_width = 0
160
161
  current_line = []
161
- for char in match.group():
162
+ for char in line:
162
163
  try:
163
164
  char_width = widths[char]
164
165
  except KeyError:
@@ -205,6 +206,16 @@ def get_data_from_clipboard(
205
206
  return [[data]]
206
207
 
207
208
 
209
+ def widget_descendants(widget: tk.Misc) -> list[tk.Misc]:
210
+ result = []
211
+ queue = deque([widget])
212
+ while queue:
213
+ current_widget = queue.popleft()
214
+ result.append(current_widget)
215
+ queue.extend(current_widget.winfo_children())
216
+ return result
217
+
218
+
208
219
  def recursive_bind(widget: tk.Misc, event: str, callback: Callable) -> None:
209
220
  widget.bind(event, callback)
210
221
  for child in widget.winfo_children():
@@ -461,6 +472,8 @@ def get_dropdown_kwargs(
461
472
  search_function: Callable = dropdown_search_function,
462
473
  validate_input: bool = True,
463
474
  text: None | str = None,
475
+ edit_data: bool = True,
476
+ default_value: Any = None,
464
477
  ) -> dict:
465
478
  return {
466
479
  "values": [] if values is None else values,
@@ -472,6 +485,8 @@ def get_dropdown_kwargs(
472
485
  "search_function": search_function,
473
486
  "validate_input": validate_input,
474
487
  "text": text,
488
+ "edit_data": edit_data,
489
+ "default_value": default_value,
475
490
  }
476
491
 
477
492
 
@@ -484,6 +499,7 @@ def get_dropdown_dict(**kwargs) -> dict:
484
499
  "validate_input": kwargs["validate_input"],
485
500
  "text": kwargs["text"],
486
501
  "state": kwargs["state"],
502
+ "default_value": kwargs["default_value"],
487
503
  }
488
504
 
489
505
 
@@ -881,50 +897,38 @@ def rounded_box_coords(
881
897
  x2: float,
882
898
  y2: float,
883
899
  radius: int = 5,
884
- ) -> tuple[float]:
900
+ ) -> tuple[float, ...]:
901
+ # Handle case where rectangle is too small for rounding
885
902
  if y2 - y1 < 2 or x2 - x1 < 2:
886
- return x1, y1, x2, y1, x2, y2, x1, y2
903
+ return (x1, y1, x2, y1, x2, y2, x1, y2, x1, y1)
904
+ # Coordinates for a closed rectangle with rounded corners
887
905
  return (
888
906
  x1 + radius,
889
- y1,
890
- x1 + radius,
891
- y1,
907
+ y1, # Top side start
892
908
  x2 - radius,
893
- y1,
894
- x2 - radius,
895
- y1,
909
+ y1, # Top side end
896
910
  x2,
897
- y1,
911
+ y1, # Top-right corner
898
912
  x2,
899
913
  y1 + radius,
900
914
  x2,
901
- y1 + radius,
915
+ y2 - radius, # Right side
902
916
  x2,
903
- y2 - radius,
904
- x2,
905
- y2 - radius,
906
- x2,
907
- y2,
917
+ y2, # Bottom-right corner
908
918
  x2 - radius,
909
919
  y2,
910
- x2 - radius,
911
- y2,
912
- x1 + radius,
913
- y2,
914
920
  x1 + radius,
915
- y2,
916
- x1,
917
- y2,
921
+ y2, # Bottom side
918
922
  x1,
919
- y2 - radius,
923
+ y2, # Bottom-left corner
920
924
  x1,
921
925
  y2 - radius,
922
926
  x1,
923
- y1 + radius,
924
- x1,
925
- y1 + radius,
927
+ y1 + radius, # Left side
926
928
  x1,
927
- y1,
929
+ y1, # Top-left corner
930
+ x1 + radius,
931
+ y1, # Close the shape
928
932
  )
929
933
 
930
934
 
@@ -1520,6 +1524,17 @@ def del_named_span_options_nested(
1520
1524
  del options[k][type_]
1521
1525
 
1522
1526
 
1527
+ def mod_note(options: dict, key: int | tuple[int, int], note: str | None, readonly: bool = True) -> dict:
1528
+ if note is not None:
1529
+ if key not in options:
1530
+ options[key] = {}
1531
+ options[key]["note"] = {"note": note, "readonly": readonly}
1532
+ else:
1533
+ if key in options and "note" in options[key]:
1534
+ del options[key]["note"]
1535
+ return options
1536
+
1537
+
1523
1538
  def add_highlight(
1524
1539
  options: dict,
1525
1540
  key: int | tuple[int, int],
@@ -1754,7 +1769,7 @@ def get_vertical_gridline_points(
1754
1769
  positions: list[float],
1755
1770
  start: int,
1756
1771
  end: int,
1757
- ) -> list[float]:
1772
+ ) -> list[int | float]:
1758
1773
  return list(
1759
1774
  chain.from_iterable(
1760
1775
  (
@@ -1770,3 +1785,44 @@ def get_vertical_gridline_points(
1770
1785
  for c in range(start, end)
1771
1786
  )
1772
1787
  )
1788
+
1789
+
1790
+ def safe_copy(value: Any) -> Any:
1791
+ """
1792
+ Attempts to create a deep copy of the input value. If copying fails,
1793
+ returns the original value.
1794
+
1795
+ Args:
1796
+ value: Any Python object to be copied
1797
+
1798
+ Returns:
1799
+ A deep copy of the value if possible, otherwise the original value
1800
+ """
1801
+ try:
1802
+ # Try deep copy first for most objects
1803
+ return copy.deepcopy(value)
1804
+ except Exception:
1805
+ try:
1806
+ # For types that deepcopy might fail on, try shallow copy
1807
+ return copy.copy(value)
1808
+ except Exception:
1809
+ try:
1810
+ # For built-in immutable types, return as-is
1811
+ if isinstance(value, (int, float, str, bool, bytes, tuple, frozenset)):
1812
+ return value
1813
+ # For None
1814
+ if value is None:
1815
+ return None
1816
+ # For functions, return same function
1817
+ if isinstance(value, Callable):
1818
+ return value
1819
+ # For modules
1820
+ if isinstance(value, ModuleType):
1821
+ return value
1822
+ # For classes
1823
+ if isinstance(value, type):
1824
+ return value
1825
+ except Exception:
1826
+ pass
1827
+ # If all copy attempts fail, return original value
1828
+ return value