tksheet 7.4.5__py3-none-any.whl → 7.4.7__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 -1
- tksheet/column_headers.py +28 -28
- tksheet/constants.py +4 -3
- tksheet/find_window.py +8 -8
- tksheet/formatters.py +39 -39
- tksheet/functions.py +221 -323
- tksheet/main_table.py +207 -145
- tksheet/other_classes.py +11 -11
- tksheet/row_index.py +27 -27
- tksheet/sheet.py +114 -118
- tksheet/sheet_options.py +3 -1
- tksheet/sorting.py +19 -18
- tksheet/text_editor.py +9 -9
- tksheet/tksheet_types.py +187 -0
- tksheet/top_left_rectangle.py +13 -12
- {tksheet-7.4.5.dist-info → tksheet-7.4.7.dist-info}/METADATA +1 -1
- tksheet-7.4.7.dist-info/RECORD +22 -0
- {tksheet-7.4.5.dist-info → tksheet-7.4.7.dist-info}/WHEEL +1 -1
- tksheet-7.4.5.dist-info/RECORD +0 -22
- {tksheet-7.4.5.dist-info → tksheet-7.4.7.dist-info}/LICENSE.txt +0 -0
- {tksheet-7.4.5.dist-info → tksheet-7.4.7.dist-info}/top_level.txt +0 -0
tksheet/functions.py
CHANGED
@@ -10,7 +10,7 @@ from collections import deque
|
|
10
10
|
from collections.abc import Callable, Generator, Hashable, Iterable, Iterator, Sequence
|
11
11
|
from difflib import SequenceMatcher
|
12
12
|
from itertools import islice, repeat
|
13
|
-
from typing import Literal
|
13
|
+
from typing import Any, Literal
|
14
14
|
|
15
15
|
from .colors import color_map
|
16
16
|
from .constants import align_value_error, symbols_set
|
@@ -20,6 +20,7 @@ from .tksheet_types import AnyIter
|
|
20
20
|
|
21
21
|
unpickle_obj = pickle.loads
|
22
22
|
lines_re = re.compile(r"[^\n]+")
|
23
|
+
ORD_A = ord("A")
|
23
24
|
|
24
25
|
|
25
26
|
def wrap_text(
|
@@ -202,7 +203,7 @@ def recursive_bind(widget: tk.Misc, event: str, callback: Callable) -> None:
|
|
202
203
|
recursive_bind(child, event, callback)
|
203
204
|
|
204
205
|
|
205
|
-
def tksheet_type_error(kwarg: str, valid_types: list[str], not_type:
|
206
|
+
def tksheet_type_error(kwarg: str, valid_types: list[str], not_type: Any) -> str:
|
206
207
|
valid_types = ", ".join(f"{type_}" for type_ in valid_types)
|
207
208
|
return f"Argument '{kwarg}' must be one of the following types: {valid_types}, not {type(not_type)}."
|
208
209
|
|
@@ -213,7 +214,7 @@ def new_tk_event(keysym: str) -> tk.Event:
|
|
213
214
|
return event
|
214
215
|
|
215
216
|
|
216
|
-
def event_has_char_key(event:
|
217
|
+
def event_has_char_key(event: Any) -> bool:
|
217
218
|
return (
|
218
219
|
event and hasattr(event, "char") and (event.char.isalpha() or event.char.isdigit() or event.char in symbols_set)
|
219
220
|
)
|
@@ -232,7 +233,7 @@ def event_opens_dropdown_or_checkbox(event=None) -> bool:
|
|
232
233
|
)
|
233
234
|
|
234
235
|
|
235
|
-
def dropdown_search_function(search_for: str, data: Iterable[
|
236
|
+
def dropdown_search_function(search_for: str, data: Iterable[Any]) -> None | int:
|
236
237
|
search_for = search_for.lower()
|
237
238
|
search_len = len(search_for)
|
238
239
|
if not search_len:
|
@@ -281,16 +282,16 @@ def float_to_int(f: int | float) -> int | float:
|
|
281
282
|
|
282
283
|
def event_dict(
|
283
284
|
name: str = None,
|
284
|
-
sheet:
|
285
|
+
sheet: Any = None,
|
285
286
|
widget: tk.Canvas | None = None,
|
286
287
|
boxes: None | dict | tuple = None,
|
287
288
|
cells_table: None | dict = None,
|
288
289
|
cells_header: None | dict = None,
|
289
290
|
cells_index: None | dict = None,
|
290
291
|
selected: None | tuple = None,
|
291
|
-
data:
|
292
|
+
data: Any = None,
|
292
293
|
key: None | str = None,
|
293
|
-
value:
|
294
|
+
value: Any = None,
|
294
295
|
loc: None | int | tuple[int] = None,
|
295
296
|
row: None | int = None,
|
296
297
|
column: None | int = None,
|
@@ -366,7 +367,8 @@ def stored_event_dict(d: DotDict) -> DotDict:
|
|
366
367
|
def len_to_idx(n: int) -> int:
|
367
368
|
if n < 1:
|
368
369
|
return 0
|
369
|
-
|
370
|
+
else:
|
371
|
+
return n - 1
|
370
372
|
|
371
373
|
|
372
374
|
def b_index(sorted_seq: Sequence[int], num_to_index: int) -> int:
|
@@ -376,13 +378,15 @@ def b_index(sorted_seq: Sequence[int], num_to_index: int) -> int:
|
|
376
378
|
"""
|
377
379
|
if (idx := bisect_left(sorted_seq, num_to_index)) == len(sorted_seq) or sorted_seq[idx] != num_to_index:
|
378
380
|
raise ValueError(f"{num_to_index} is not in Sequence")
|
379
|
-
|
381
|
+
else:
|
382
|
+
return idx
|
380
383
|
|
381
384
|
|
382
385
|
def try_b_index(sorted_seq: Sequence[int], num_to_index: int) -> int | None:
|
383
386
|
if (idx := bisect_left(sorted_seq, num_to_index)) == len(sorted_seq) or sorted_seq[idx] != num_to_index:
|
384
387
|
return None
|
385
|
-
|
388
|
+
else:
|
389
|
+
return idx
|
386
390
|
|
387
391
|
|
388
392
|
def bisect_in(sorted_seq: Sequence[int], num: int) -> bool:
|
@@ -399,27 +403,20 @@ def push_n(num: int, sorted_seq: Sequence[int]) -> int:
|
|
399
403
|
if num < sorted_seq[0]:
|
400
404
|
return num
|
401
405
|
else:
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
while lo < hi:
|
412
|
-
mid = (lo + hi) // 2
|
413
|
-
if sorted_seq[mid] < num + mid + 1:
|
414
|
-
lo = mid + 1
|
415
|
-
else:
|
416
|
-
hi = mid
|
417
|
-
return num + lo
|
406
|
+
hi = len(sorted_seq)
|
407
|
+
lo = 0
|
408
|
+
while lo < hi:
|
409
|
+
mid = (lo + hi) // 2
|
410
|
+
if sorted_seq[mid] < num + mid + 1:
|
411
|
+
lo = mid + 1
|
412
|
+
else:
|
413
|
+
hi = mid
|
414
|
+
return num + lo
|
418
415
|
|
419
416
|
|
420
417
|
def get_dropdown_kwargs(
|
421
|
-
values: list[
|
422
|
-
set_value:
|
418
|
+
values: list[Any] | None = None,
|
419
|
+
set_value: Any = None,
|
423
420
|
state: str = "normal",
|
424
421
|
redraw: bool = True,
|
425
422
|
selection_function: Callable | None = None,
|
@@ -477,7 +474,7 @@ def get_checkbox_dict(**kwargs) -> dict:
|
|
477
474
|
}
|
478
475
|
|
479
476
|
|
480
|
-
def is_iterable(o:
|
477
|
+
def is_iterable(o: Any) -> bool:
|
481
478
|
if isinstance(o, str):
|
482
479
|
return False
|
483
480
|
try:
|
@@ -499,7 +496,7 @@ def int_x_tuple(i: AnyIter[int] | int) -> tuple[int]:
|
|
499
496
|
return tuple(i)
|
500
497
|
|
501
498
|
|
502
|
-
def unpack(t: tuple[
|
499
|
+
def unpack(t: tuple[Any] | tuple[AnyIter[Any]]) -> tuple[Any]:
|
503
500
|
if not len(t):
|
504
501
|
return t
|
505
502
|
if is_iterable(t[0]) and len(t) == 1:
|
@@ -507,11 +504,11 @@ def unpack(t: tuple[object] | tuple[AnyIter[object]]) -> tuple[object]:
|
|
507
504
|
return t
|
508
505
|
|
509
506
|
|
510
|
-
def is_type_int(o:
|
507
|
+
def is_type_int(o: Any) -> bool:
|
511
508
|
return isinstance(o, int) and not isinstance(o, bool)
|
512
509
|
|
513
510
|
|
514
|
-
def force_bool(o:
|
511
|
+
def force_bool(o: Any) -> bool:
|
515
512
|
try:
|
516
513
|
return to_bool(o)
|
517
514
|
except Exception:
|
@@ -521,9 +518,8 @@ def force_bool(o: object) -> bool:
|
|
521
518
|
def alpha2idx(a: str) -> int | None:
|
522
519
|
try:
|
523
520
|
n = 0
|
524
|
-
orda = ord("A")
|
525
521
|
for c in a.upper():
|
526
|
-
n = n * 26 + ord(c) -
|
522
|
+
n = n * 26 + ord(c) - ORD_A + 1
|
527
523
|
return n - 1
|
528
524
|
except Exception:
|
529
525
|
return None
|
@@ -702,8 +698,8 @@ def cell_right_within_box(
|
|
702
698
|
|
703
699
|
|
704
700
|
def get_last(
|
705
|
-
it: AnyIter[
|
706
|
-
) ->
|
701
|
+
it: AnyIter[Any],
|
702
|
+
) -> Any:
|
707
703
|
if hasattr(it, "__reversed__"):
|
708
704
|
try:
|
709
705
|
return next(reversed(it))
|
@@ -716,7 +712,7 @@ def get_last(
|
|
716
712
|
return None
|
717
713
|
|
718
714
|
|
719
|
-
def index_exists(seq: Sequence[
|
715
|
+
def index_exists(seq: Sequence[Any], index: int) -> bool:
|
720
716
|
try:
|
721
717
|
seq[index]
|
722
718
|
return True
|
@@ -733,10 +729,10 @@ def add_to_displayed(displayed: list[int], to_add: Iterable[int]) -> list[int]:
|
|
733
729
|
|
734
730
|
|
735
731
|
def move_elements_by_mapping(
|
736
|
-
seq: list[
|
732
|
+
seq: list[Any],
|
737
733
|
new_idxs: dict[int, int],
|
738
734
|
old_idxs: dict[int, int] | None = None,
|
739
|
-
) -> list[
|
735
|
+
) -> list[Any]:
|
740
736
|
# move elements of a list around
|
741
737
|
# displacing other elements based on mapping
|
742
738
|
# new_idxs = {old index: new index, ...}
|
@@ -748,10 +744,10 @@ def move_elements_by_mapping(
|
|
748
744
|
|
749
745
|
|
750
746
|
def move_elements_by_mapping_gen(
|
751
|
-
seq: list[
|
747
|
+
seq: list[Any],
|
752
748
|
new_idxs: dict[int, int],
|
753
749
|
old_idxs: dict[int, int] | None = None,
|
754
|
-
) -> Generator[
|
750
|
+
) -> Generator[Any]:
|
755
751
|
if old_idxs is None:
|
756
752
|
old_idxs = dict(zip(new_idxs.values(), new_idxs))
|
757
753
|
remaining_values = (e for i, e in enumerate(seq) if i not in new_idxs)
|
@@ -759,10 +755,10 @@ def move_elements_by_mapping_gen(
|
|
759
755
|
|
760
756
|
|
761
757
|
def move_elements_to(
|
762
|
-
seq: list[
|
758
|
+
seq: list[Any],
|
763
759
|
move_to: int,
|
764
760
|
to_move: list[int],
|
765
|
-
) -> list[
|
761
|
+
) -> list[Any]:
|
766
762
|
return move_elements_by_mapping(
|
767
763
|
seq,
|
768
764
|
*get_new_indexes(
|
@@ -791,14 +787,14 @@ def get_new_indexes(
|
|
791
787
|
|
792
788
|
|
793
789
|
def insert_items(
|
794
|
-
seq: list[
|
795
|
-
to_insert: dict[int,
|
790
|
+
seq: list[Any],
|
791
|
+
to_insert: dict[int, Any],
|
796
792
|
seq_len_func: Callable | None = None,
|
797
|
-
) -> list[
|
793
|
+
) -> list[Any]:
|
798
794
|
"""
|
799
|
-
seq: list[
|
795
|
+
seq: list[Any]
|
800
796
|
to_insert: keys are ints sorted in reverse, representing list indexes to insert items.
|
801
|
-
Values are any
|
797
|
+
Values are any, e.g. {1: 200, 0: 200}
|
802
798
|
"""
|
803
799
|
if to_insert:
|
804
800
|
if seq_len_func and next(iter(to_insert)) >= len(seq) + len(to_insert):
|
@@ -809,11 +805,11 @@ def insert_items(
|
|
809
805
|
|
810
806
|
|
811
807
|
def del_placeholder_dict_key(
|
812
|
-
d: dict[Hashable,
|
808
|
+
d: dict[Hashable, Any],
|
813
809
|
k: Hashable,
|
814
|
-
v:
|
810
|
+
v: Any,
|
815
811
|
p: tuple = (),
|
816
|
-
) -> dict[Hashable,
|
812
|
+
) -> dict[Hashable, Any]:
|
817
813
|
if p in d:
|
818
814
|
del d[p]
|
819
815
|
d[k] = v
|
@@ -994,7 +990,7 @@ def is_last_cell(
|
|
994
990
|
return row == end_row - 1 and col == end_col - 1
|
995
991
|
|
996
992
|
|
997
|
-
def zip_fill_2nd_value(x: AnyIter[
|
993
|
+
def zip_fill_2nd_value(x: AnyIter[Any], o: Any) -> Generator[Any, Any]:
|
998
994
|
return zip(x, repeat(o))
|
999
995
|
|
1000
996
|
|
@@ -1011,7 +1007,7 @@ def str_to_int(s: str) -> int | None:
|
|
1011
1007
|
|
1012
1008
|
def gen_formatted(
|
1013
1009
|
options: dict,
|
1014
|
-
formatter:
|
1010
|
+
formatter: Any = None,
|
1015
1011
|
) -> Generator[tuple[int, int]] | Generator[int]:
|
1016
1012
|
if formatter is None:
|
1017
1013
|
return (k for k, dct in options.items() if "format" in dct)
|
@@ -1060,7 +1056,7 @@ def span_dict(
|
|
1060
1056
|
convert: Callable | None = None,
|
1061
1057
|
undo: bool = False,
|
1062
1058
|
emit_event: bool = False,
|
1063
|
-
widget:
|
1059
|
+
widget: Any = None,
|
1064
1060
|
) -> Span:
|
1065
1061
|
d: Span = Span(
|
1066
1062
|
from_r=from_r,
|
@@ -1087,7 +1083,7 @@ def span_dict(
|
|
1087
1083
|
|
1088
1084
|
|
1089
1085
|
def coords_to_span(
|
1090
|
-
widget:
|
1086
|
+
widget: Any,
|
1091
1087
|
from_r: int | None = None,
|
1092
1088
|
from_c: int | None = None,
|
1093
1089
|
upto_r: int | None = None,
|
@@ -1112,6 +1108,37 @@ def coords_to_span(
|
|
1112
1108
|
)
|
1113
1109
|
|
1114
1110
|
|
1111
|
+
PATTERN_ROW = re.compile(r"^(\d+)$") # "1"
|
1112
|
+
PATTERN_COL = re.compile(r"^([A-Z]+)$") # "A"
|
1113
|
+
PATTERN_CELL = re.compile(r"^([A-Z]+)(\d+)$") # "A1"
|
1114
|
+
PATTERN_RANGE = re.compile(r"^([A-Z]+)(\d+):([A-Z]+)(\d+)$") # "A1:B2"
|
1115
|
+
PATTERN_ROW_RANGE = re.compile(r"^(\d+):(\d+)$") # "1:2"
|
1116
|
+
PATTERN_ROW_START = re.compile(r"^(\d+):$") # "2:"
|
1117
|
+
PATTERN_ROW_END = re.compile(r"^:(\d+)$") # ":2"
|
1118
|
+
PATTERN_COL_RANGE = re.compile(r"^([A-Z]+):([A-Z]+)$") # "A:B"
|
1119
|
+
PATTERN_COL_START = re.compile(r"^([A-Z]+):$") # "A:"
|
1120
|
+
PATTERN_COL_END = re.compile(r"^:([A-Z]+)$") # ":B"
|
1121
|
+
PATTERN_CELL_START = re.compile(r"^([A-Z]+)(\d+):$") # "A1:"
|
1122
|
+
PATTERN_CELL_END = re.compile(r"^:([A-Z]+)(\d+)$") # ":B1"
|
1123
|
+
PATTERN_CELL_COL = re.compile(r"^([A-Z]+)(\d+):([A-Z]+)$") # "A1:B"
|
1124
|
+
PATTERN_CELL_ROW = re.compile(r"^([A-Z]+)(\d+):(\d+)$") # "A1:2"
|
1125
|
+
PATTERN_ALL = re.compile(r"^:$") # ":"
|
1126
|
+
|
1127
|
+
|
1128
|
+
def span_a2i(a: str) -> int | None:
|
1129
|
+
n = 0
|
1130
|
+
for c in a:
|
1131
|
+
n = n * 26 + ord(c) - ORD_A + 1
|
1132
|
+
return n - 1
|
1133
|
+
|
1134
|
+
|
1135
|
+
def span_a2n(a: str) -> int | None:
|
1136
|
+
n = 0
|
1137
|
+
for c in a:
|
1138
|
+
n = n * 26 + ord(c) - ORD_A + 1
|
1139
|
+
return n
|
1140
|
+
|
1141
|
+
|
1115
1142
|
def key_to_span(
|
1116
1143
|
key: (
|
1117
1144
|
str
|
@@ -1122,78 +1149,34 @@ def key_to_span(
|
|
1122
1149
|
| Sequence[Sequence[int | None, int | None], Sequence[int | None, int | None]]
|
1123
1150
|
),
|
1124
1151
|
spans: dict[str, Span],
|
1125
|
-
widget:
|
1152
|
+
widget: Any = None,
|
1126
1153
|
) -> Span:
|
1154
|
+
"""
|
1155
|
+
Convert various input types to a Span object representing a 2D range.
|
1156
|
+
|
1157
|
+
Args:
|
1158
|
+
key: The input to convert (str, int, slice, sequence, or None).
|
1159
|
+
spans: A dictionary of named spans (e.g., {"<name>": Span(...)}).
|
1160
|
+
widget: Optional widget context for span creation.
|
1161
|
+
|
1162
|
+
Returns:
|
1163
|
+
A Span object or an error message string if the key is invalid.
|
1164
|
+
"""
|
1165
|
+
# Handle Span object directly
|
1127
1166
|
if isinstance(key, Span):
|
1128
1167
|
return key
|
1168
|
+
|
1169
|
+
# Handle None as full span
|
1129
1170
|
elif key is None:
|
1130
|
-
|
1171
|
+
return coords_to_span(widget=widget, from_r=None, from_c=None, upto_r=None, upto_c=None)
|
1172
|
+
|
1173
|
+
# Validate input type
|
1131
1174
|
elif not isinstance(key, (str, int, slice, list, tuple)):
|
1132
|
-
return f"Key type must be either str, int, list, tuple or slice, not '{type(key)}'."
|
1175
|
+
return f"Key type must be either str, int, list, tuple or slice, not '{type(key).__name__}'."
|
1176
|
+
|
1133
1177
|
try:
|
1134
|
-
|
1135
|
-
|
1136
|
-
if len(key) == 2:
|
1137
|
-
"""
|
1138
|
-
(int | None, int | None) -
|
1139
|
-
(0, 0) - row 0, column 0 - the first cell
|
1140
|
-
(0, None) - row 0, all columns
|
1141
|
-
(None, 0) - column 0, all rows
|
1142
|
-
"""
|
1143
|
-
return span_dict(
|
1144
|
-
from_r=key[0] if isinstance(key[0], int) else 0,
|
1145
|
-
from_c=key[1] if isinstance(key[1], int) else 0,
|
1146
|
-
upto_r=(key[0] + 1) if isinstance(key[0], int) else None,
|
1147
|
-
upto_c=(key[1] + 1) if isinstance(key[1], int) else None,
|
1148
|
-
widget=widget,
|
1149
|
-
)
|
1150
|
-
|
1151
|
-
elif len(key) == 4:
|
1152
|
-
"""
|
1153
|
-
(int | None, int | None, int | None, int | None) -
|
1154
|
-
(from row, from column, up to row, up to column)
|
1155
|
-
"""
|
1156
|
-
return coords_to_span(
|
1157
|
-
widget=widget,
|
1158
|
-
from_r=key[0],
|
1159
|
-
from_c=key[1],
|
1160
|
-
upto_r=key[2],
|
1161
|
-
upto_c=key[3],
|
1162
|
-
)
|
1163
|
-
# return span_dict(
|
1164
|
-
# from_r=key[0] if isinstance(key[0], int) else 0,
|
1165
|
-
# from_c=key[1] if isinstance(key[1], int) else 0,
|
1166
|
-
# upto_r=key[2] if isinstance(key[2], int) else None,
|
1167
|
-
# upto_c=key[3] if isinstance(key[3], int) else None,
|
1168
|
-
# widget=widget,
|
1169
|
-
# )
|
1170
|
-
|
1171
|
-
elif isinstance(key[0], (list, tuple)):
|
1172
|
-
"""
|
1173
|
-
((int | None, int | None), (int | None, int | None))
|
1174
|
-
|
1175
|
-
First Sequence is start row and column
|
1176
|
-
Second Sequence is up to but not including row and column
|
1177
|
-
"""
|
1178
|
-
return coords_to_span(
|
1179
|
-
widget=widget,
|
1180
|
-
from_r=key[0][0],
|
1181
|
-
from_c=key[0][1],
|
1182
|
-
upto_r=key[1][0],
|
1183
|
-
upto_c=key[1][1],
|
1184
|
-
)
|
1185
|
-
# return span_dict(
|
1186
|
-
# from_r=key[0][0] if isinstance(key[0][0], int) else 0,
|
1187
|
-
# from_c=key[0][1] if isinstance(key[0][1], int) else 0,
|
1188
|
-
# upto_r=key[1][0],
|
1189
|
-
# upto_c=key[1][1],
|
1190
|
-
# widget=widget,
|
1191
|
-
# )
|
1192
|
-
|
1193
|
-
elif isinstance(key, int):
|
1194
|
-
"""
|
1195
|
-
[int] - Whole row at that index
|
1196
|
-
"""
|
1178
|
+
# Integer key: whole row
|
1179
|
+
if isinstance(key, int):
|
1197
1180
|
return span_dict(
|
1198
1181
|
from_r=key,
|
1199
1182
|
from_c=None,
|
@@ -1202,29 +1185,8 @@ def key_to_span(
|
|
1202
1185
|
widget=widget,
|
1203
1186
|
)
|
1204
1187
|
|
1188
|
+
# Slice key: row range
|
1205
1189
|
elif isinstance(key, slice):
|
1206
|
-
"""
|
1207
|
-
[slice]
|
1208
|
-
"""
|
1209
|
-
"""
|
1210
|
-
[:] - All rows
|
1211
|
-
"""
|
1212
|
-
if key.start is None and key.stop is None:
|
1213
|
-
"""
|
1214
|
-
[:]
|
1215
|
-
"""
|
1216
|
-
return span_dict(
|
1217
|
-
from_r=0,
|
1218
|
-
from_c=None,
|
1219
|
-
upto_r=None,
|
1220
|
-
upto_c=None,
|
1221
|
-
widget=widget,
|
1222
|
-
)
|
1223
|
-
"""
|
1224
|
-
[1:3] - Rows 1, 2
|
1225
|
-
[:2] - Rows up to but not including 2
|
1226
|
-
[2:] - Rows starting from and including 2
|
1227
|
-
"""
|
1228
1190
|
start = 0 if key.start is None else key.start
|
1229
1191
|
return span_dict(
|
1230
1192
|
from_r=start,
|
@@ -1234,251 +1196,187 @@ def key_to_span(
|
|
1234
1196
|
widget=widget,
|
1235
1197
|
)
|
1236
1198
|
|
1199
|
+
# Sequence key: various span formats
|
1200
|
+
elif isinstance(key, (list, tuple)):
|
1201
|
+
if (
|
1202
|
+
len(key) == 2
|
1203
|
+
and (isinstance(key[0], int) or key[0] is None)
|
1204
|
+
and (isinstance(key[1], int) or key[1] is None)
|
1205
|
+
):
|
1206
|
+
# Single cell or partial span: (row, col)
|
1207
|
+
r_int = isinstance(key[0], int)
|
1208
|
+
c_int = isinstance(key[1], int)
|
1209
|
+
return span_dict(
|
1210
|
+
from_r=key[0] if r_int else 0,
|
1211
|
+
from_c=key[1] if c_int else 0,
|
1212
|
+
upto_r=key[0] + 1 if r_int else None,
|
1213
|
+
upto_c=key[1] + 1 if c_int else None,
|
1214
|
+
widget=widget,
|
1215
|
+
)
|
1216
|
+
elif len(key) == 4:
|
1217
|
+
# Full span coordinates: (from_r, from_c, upto_r, upto_c)
|
1218
|
+
return coords_to_span(
|
1219
|
+
widget=widget,
|
1220
|
+
from_r=key[0],
|
1221
|
+
from_c=key[1],
|
1222
|
+
upto_r=key[2],
|
1223
|
+
upto_c=key[3],
|
1224
|
+
)
|
1225
|
+
elif len(key) == 2 and all(isinstance(k, (list, tuple)) for k in key):
|
1226
|
+
# Start and end points: ((from_r, from_c), (upto_r, upto_c))
|
1227
|
+
return coords_to_span(
|
1228
|
+
widget=widget,
|
1229
|
+
from_r=key[0][0],
|
1230
|
+
from_c=key[0][1],
|
1231
|
+
upto_r=key[1][0],
|
1232
|
+
upto_c=key[1][1],
|
1233
|
+
)
|
1234
|
+
|
1235
|
+
# String key: parse various span formats
|
1237
1236
|
elif isinstance(key, str):
|
1238
1237
|
if not key:
|
1239
|
-
|
1240
|
-
|
1241
|
-
if key.startswith("<") and key.endswith(">"):
|
1242
|
-
if (key := key[1:-1]) in spans:
|
1243
|
-
"""
|
1244
|
-
["<name>"] - Surrounded by "<" ">" cells from a named range
|
1245
|
-
"""
|
1246
|
-
return spans[key]
|
1247
|
-
return f"'{key}' not in named spans."
|
1248
|
-
|
1249
|
-
key = key.upper()
|
1250
|
-
|
1251
|
-
if key.isdigit():
|
1252
|
-
"""
|
1253
|
-
["1"] - Row 0
|
1254
|
-
"""
|
1238
|
+
# Empty string treated as full span
|
1255
1239
|
return span_dict(
|
1256
|
-
from_r=
|
1240
|
+
from_r=0,
|
1257
1241
|
from_c=None,
|
1258
|
-
upto_r=
|
1242
|
+
upto_r=None,
|
1259
1243
|
upto_c=None,
|
1260
1244
|
widget=widget,
|
1261
1245
|
)
|
1246
|
+
elif key.startswith("<") and key.endswith(">"):
|
1247
|
+
name = key[1:-1]
|
1248
|
+
return spans.get(name, f"'{name}' not in named spans.")
|
1249
|
+
|
1250
|
+
key = key.upper() # Case-insensitive parsing
|
1262
1251
|
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1252
|
+
# Match string against precompiled patterns
|
1253
|
+
if m := PATTERN_ROW.match(key):
|
1254
|
+
return span_dict(
|
1255
|
+
from_r=int(m[1]) - 1,
|
1256
|
+
from_c=None,
|
1257
|
+
upto_r=int(m[1]),
|
1258
|
+
upto_c=None,
|
1259
|
+
widget=widget,
|
1260
|
+
)
|
1261
|
+
elif m := PATTERN_COL.match(key):
|
1267
1262
|
return span_dict(
|
1268
1263
|
from_r=None,
|
1269
|
-
from_c=
|
1264
|
+
from_c=span_a2i(m[1]),
|
1270
1265
|
upto_r=None,
|
1271
|
-
upto_c=
|
1266
|
+
upto_c=span_a2n(m[1]),
|
1272
1267
|
widget=widget,
|
1273
1268
|
)
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
return f"'{key}' could not be converted to span."
|
1278
|
-
|
1279
|
-
if len(splitk) == 1 and not splitk[0].isdigit() and not splitk[0].isalpha() and not splitk[0][0].isdigit():
|
1280
|
-
"""
|
1281
|
-
["A1"] - Cell (0, 0)
|
1282
|
-
"""
|
1283
|
-
keys_digits = re.search(r"\d", splitk[0])
|
1284
|
-
if keys_digits:
|
1285
|
-
digits_start = keys_digits.start()
|
1286
|
-
if not digits_start:
|
1287
|
-
return f"'{key}' could not be converted to span."
|
1288
|
-
if digits_start:
|
1289
|
-
key_row = splitk[0][digits_start:]
|
1290
|
-
key_column = splitk[0][:digits_start]
|
1291
|
-
return span_dict(
|
1292
|
-
from_r=int(key_row) - 1,
|
1293
|
-
from_c=alpha2idx(key_column),
|
1294
|
-
upto_r=int(key_row),
|
1295
|
-
upto_c=alpha2idx(key_column) + 1,
|
1296
|
-
widget=widget,
|
1297
|
-
)
|
1298
|
-
|
1299
|
-
if not splitk[0] and not splitk[1]:
|
1300
|
-
"""
|
1301
|
-
[":"] - All rows
|
1302
|
-
"""
|
1269
|
+
elif m := PATTERN_CELL.match(key):
|
1270
|
+
c = span_a2i(m[1])
|
1271
|
+
r = int(m[2]) - 1
|
1303
1272
|
return span_dict(
|
1304
|
-
from_r=
|
1305
|
-
from_c=
|
1306
|
-
upto_r=
|
1307
|
-
upto_c=
|
1273
|
+
from_r=r,
|
1274
|
+
from_c=c,
|
1275
|
+
upto_r=r + 1,
|
1276
|
+
upto_c=c + 1,
|
1308
1277
|
widget=widget,
|
1309
1278
|
)
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1279
|
+
elif m := PATTERN_RANGE.match(key):
|
1280
|
+
return span_dict(
|
1281
|
+
from_r=int(m[2]) - 1,
|
1282
|
+
from_c=span_a2i(m[1]),
|
1283
|
+
upto_r=int(m[4]),
|
1284
|
+
upto_c=span_a2n(m[3]),
|
1285
|
+
widget=widget,
|
1286
|
+
)
|
1287
|
+
elif m := PATTERN_ROW_RANGE.match(key):
|
1315
1288
|
return span_dict(
|
1316
|
-
from_r=int(
|
1289
|
+
from_r=int(m[1]) - 1,
|
1317
1290
|
from_c=None,
|
1318
|
-
upto_r=
|
1291
|
+
upto_r=int(m[2]),
|
1319
1292
|
upto_c=None,
|
1320
1293
|
widget=widget,
|
1321
1294
|
)
|
1322
|
-
|
1323
|
-
if splitk[1].isdigit() and not splitk[0]:
|
1324
|
-
"""
|
1325
|
-
[":2"] - Rows up to and including 1
|
1326
|
-
"""
|
1295
|
+
elif m := PATTERN_ROW_START.match(key):
|
1327
1296
|
return span_dict(
|
1328
|
-
from_r=
|
1297
|
+
from_r=int(m[1]) - 1,
|
1329
1298
|
from_c=None,
|
1330
|
-
upto_r=
|
1299
|
+
upto_r=None,
|
1331
1300
|
upto_c=None,
|
1332
1301
|
widget=widget,
|
1333
1302
|
)
|
1334
|
-
|
1335
|
-
if splitk[0].isdigit() and splitk[1].isdigit():
|
1336
|
-
"""
|
1337
|
-
["1:2"] - Rows 0, 1
|
1338
|
-
"""
|
1303
|
+
elif m := PATTERN_ROW_END.match(key):
|
1339
1304
|
return span_dict(
|
1340
|
-
from_r=
|
1305
|
+
from_r=0,
|
1341
1306
|
from_c=None,
|
1342
|
-
upto_r=int(
|
1307
|
+
upto_r=int(m[1]),
|
1343
1308
|
upto_c=None,
|
1344
1309
|
widget=widget,
|
1345
1310
|
)
|
1346
|
-
|
1347
|
-
if splitk[0].isalpha() and not splitk[1]:
|
1348
|
-
"""
|
1349
|
-
["B:"] - Columns starting from and including 2
|
1350
|
-
"""
|
1311
|
+
elif m := PATTERN_COL_RANGE.match(key):
|
1351
1312
|
return span_dict(
|
1352
1313
|
from_r=None,
|
1353
|
-
from_c=
|
1314
|
+
from_c=span_a2i(m[1]),
|
1354
1315
|
upto_r=None,
|
1355
|
-
upto_c=
|
1316
|
+
upto_c=span_a2n(m[2]),
|
1356
1317
|
widget=widget,
|
1357
1318
|
)
|
1358
|
-
|
1359
|
-
if splitk[1].isalpha() and not splitk[0]:
|
1360
|
-
"""
|
1361
|
-
[":B"] - Columns up to and including 2
|
1362
|
-
"""
|
1319
|
+
elif m := PATTERN_COL_START.match(key):
|
1363
1320
|
return span_dict(
|
1364
1321
|
from_r=None,
|
1365
|
-
from_c=
|
1322
|
+
from_c=span_a2i(m[1]),
|
1366
1323
|
upto_r=None,
|
1367
|
-
upto_c=
|
1324
|
+
upto_c=None,
|
1368
1325
|
widget=widget,
|
1369
1326
|
)
|
1370
|
-
|
1371
|
-
if splitk[0].isalpha() and splitk[1].isalpha():
|
1372
|
-
"""
|
1373
|
-
["A:B"] - Columns 0, 1
|
1374
|
-
"""
|
1327
|
+
elif m := PATTERN_COL_END.match(key):
|
1375
1328
|
return span_dict(
|
1376
1329
|
from_r=None,
|
1377
|
-
from_c=
|
1330
|
+
from_c=0,
|
1378
1331
|
upto_r=None,
|
1379
|
-
upto_c=
|
1332
|
+
upto_c=span_a2n(m[1]),
|
1380
1333
|
widget=widget,
|
1381
1334
|
)
|
1382
|
-
|
1383
|
-
m1 = re.search(r"\d", splitk[0])
|
1384
|
-
m2 = re.search(r"\d", splitk[1])
|
1385
|
-
m1start = m1.start() if m1 else None
|
1386
|
-
m2start = m2.start() if m2 else None
|
1387
|
-
if m1start and m2start:
|
1388
|
-
"""
|
1389
|
-
["A1:B1"] - Cells (0, 0), (0, 1)
|
1390
|
-
"""
|
1391
|
-
c1 = splitk[0][:m1start]
|
1392
|
-
r1 = splitk[0][m1start:]
|
1393
|
-
c2 = splitk[1][:m2start]
|
1394
|
-
r2 = splitk[1][m2start:]
|
1335
|
+
elif m := PATTERN_CELL_START.match(key):
|
1395
1336
|
return span_dict(
|
1396
|
-
from_r=int(
|
1397
|
-
from_c=
|
1398
|
-
upto_r=
|
1399
|
-
upto_c=
|
1337
|
+
from_r=int(m[2]) - 1,
|
1338
|
+
from_c=span_a2i(m[1]),
|
1339
|
+
upto_r=None,
|
1340
|
+
upto_c=None,
|
1400
1341
|
widget=widget,
|
1401
1342
|
)
|
1402
|
-
|
1403
|
-
if not splitk[0] and m2start:
|
1404
|
-
"""
|
1405
|
-
[":B1"] - Cells (0, 0), (0, 1)
|
1406
|
-
"""
|
1407
|
-
c2 = splitk[1][:m2start]
|
1408
|
-
r2 = splitk[1][m2start:]
|
1343
|
+
elif m := PATTERN_CELL_END.match(key):
|
1409
1344
|
return span_dict(
|
1410
1345
|
from_r=0,
|
1411
1346
|
from_c=0,
|
1412
|
-
upto_r=int(
|
1413
|
-
upto_c=
|
1347
|
+
upto_r=int(m[2]),
|
1348
|
+
upto_c=span_a2n(m[1]),
|
1414
1349
|
widget=widget,
|
1415
1350
|
)
|
1416
|
-
|
1417
|
-
if not splitk[1] and m1start:
|
1418
|
-
"""
|
1419
|
-
["A1:"] - Cells starting from and including (0, 0)
|
1420
|
-
"""
|
1421
|
-
c1 = splitk[0][:m1start]
|
1422
|
-
r1 = splitk[0][m1start:]
|
1351
|
+
elif m := PATTERN_CELL_COL.match(key):
|
1423
1352
|
return span_dict(
|
1424
|
-
from_r=int(
|
1425
|
-
from_c=
|
1353
|
+
from_r=int(m[2]) - 1,
|
1354
|
+
from_c=span_a2i(m[1]),
|
1426
1355
|
upto_r=None,
|
1427
|
-
upto_c=
|
1356
|
+
upto_c=span_a2n(m[3]),
|
1428
1357
|
widget=widget,
|
1429
1358
|
)
|
1430
|
-
|
1431
|
-
if m1start and splitk[1].isalpha():
|
1432
|
-
"""
|
1433
|
-
["A1:B"] - All the cells starting from (0, 0)
|
1434
|
-
expanding out to include column 1
|
1435
|
-
but not including cells beyond column
|
1436
|
-
1 and expanding down to include all rows
|
1437
|
-
A B C D
|
1438
|
-
1 x x
|
1439
|
-
2 x x
|
1440
|
-
3 x x
|
1441
|
-
4 x x
|
1442
|
-
...
|
1443
|
-
"""
|
1444
|
-
c1 = splitk[0][:m1start]
|
1445
|
-
r1 = splitk[0][m1start:]
|
1359
|
+
elif m := PATTERN_CELL_ROW.match(key):
|
1446
1360
|
return span_dict(
|
1447
|
-
from_r=int(
|
1448
|
-
from_c=
|
1449
|
-
upto_r=
|
1450
|
-
upto_c=
|
1361
|
+
from_r=int(m[2]) - 1,
|
1362
|
+
from_c=span_a2i(m[1]),
|
1363
|
+
upto_r=int(m[3]),
|
1364
|
+
upto_c=None,
|
1451
1365
|
widget=widget,
|
1452
1366
|
)
|
1453
|
-
|
1454
|
-
if m1start and splitk[1].isdigit():
|
1455
|
-
"""
|
1456
|
-
["A1:2"] - All the cells starting from (0, 0)
|
1457
|
-
expanding down to include row 1
|
1458
|
-
but not including cells beyond row
|
1459
|
-
1 and expanding out to include all
|
1460
|
-
columns
|
1461
|
-
A B C D
|
1462
|
-
1 x x x x
|
1463
|
-
2 x x x x
|
1464
|
-
3
|
1465
|
-
4
|
1466
|
-
...
|
1467
|
-
"""
|
1468
|
-
c1 = splitk[0][:m1start]
|
1469
|
-
r1 = splitk[0][m1start:]
|
1367
|
+
elif PATTERN_ALL.match(key):
|
1470
1368
|
return span_dict(
|
1471
|
-
from_r=
|
1472
|
-
from_c=
|
1473
|
-
upto_r=
|
1369
|
+
from_r=0,
|
1370
|
+
from_c=None,
|
1371
|
+
upto_r=None,
|
1474
1372
|
upto_c=None,
|
1475
1373
|
widget=widget,
|
1476
1374
|
)
|
1375
|
+
else:
|
1376
|
+
return f"'{key}' could not be converted to span."
|
1477
1377
|
|
1478
1378
|
except ValueError as error:
|
1479
1379
|
return f"Error, '{key}' could not be converted to span: {error}"
|
1480
|
-
else:
|
1481
|
-
return f"'{key}' could not be converted to span."
|
1482
1380
|
|
1483
1381
|
|
1484
1382
|
def span_is_cell(span: Span) -> bool:
|
@@ -1623,7 +1521,7 @@ def add_to_options(
|
|
1623
1521
|
options: dict,
|
1624
1522
|
coords: int | tuple[int, int],
|
1625
1523
|
key: str,
|
1626
|
-
value:
|
1524
|
+
value: Any,
|
1627
1525
|
) -> dict:
|
1628
1526
|
if coords not in options:
|
1629
1527
|
options[coords] = {}
|
@@ -1694,14 +1592,14 @@ def mod_span(
|
|
1694
1592
|
return to_set_to
|
1695
1593
|
|
1696
1594
|
|
1697
|
-
def mod_span_widget(span: Span, widget:
|
1595
|
+
def mod_span_widget(span: Span, widget: Any) -> Span:
|
1698
1596
|
span.widget = widget
|
1699
1597
|
return span
|
1700
1598
|
|
1701
1599
|
|
1702
1600
|
def mod_event_val(
|
1703
1601
|
event_data: EventDataDict,
|
1704
|
-
val:
|
1602
|
+
val: Any,
|
1705
1603
|
loc: Loc | None = None,
|
1706
1604
|
row: int | None = None,
|
1707
1605
|
column: int | None = None,
|