tksheet 7.4.2__py3-none-any.whl → 7.4.4__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 +21 -14
- tksheet/functions.py +87 -77
- tksheet/main_table.py +98 -86
- tksheet/row_index.py +42 -32
- tksheet/sheet.py +51 -70
- tksheet/sheet_options.py +3 -1
- tksheet/sorting.py +273 -77
- {tksheet-7.4.2.dist-info → tksheet-7.4.4.dist-info}/METADATA +3 -1
- tksheet-7.4.4.dist-info/RECORD +22 -0
- tksheet-7.4.2.dist-info/RECORD +0 -22
- {tksheet-7.4.2.dist-info → tksheet-7.4.4.dist-info}/LICENSE.txt +0 -0
- {tksheet-7.4.2.dist-info → tksheet-7.4.4.dist-info}/WHEEL +0 -0
- {tksheet-7.4.2.dist-info → tksheet-7.4.4.dist-info}/top_level.txt +0 -0
tksheet/__init__.py
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
tksheet - A Python tkinter table widget
|
5
5
|
"""
|
6
6
|
|
7
|
-
__version__ = "7.4.
|
7
|
+
__version__ = "7.4.4"
|
8
8
|
|
9
9
|
from .colors import (
|
10
10
|
color_map,
|
@@ -93,6 +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 fast_sort_key, natural_sort_key, version_sort_key
|
96
97
|
from .text_editor import (
|
97
98
|
TextEditor,
|
98
99
|
TextEditorTkText,
|
tksheet/column_headers.py
CHANGED
@@ -874,6 +874,8 @@ class ColumnHeaders(tk.Canvas):
|
|
874
874
|
columns = list(range(0, len(self.MT.col_positions) - 1))
|
875
875
|
event_data = self.MT.new_event_dict("edit_table")
|
876
876
|
try_binding(self.MT.extra_begin_sort_cells_func, event_data)
|
877
|
+
if key is None:
|
878
|
+
key = self.PAR.ops.sort_key
|
877
879
|
for c in columns:
|
878
880
|
datacn = self.MT.datacn(c)
|
879
881
|
for r, val in enumerate(
|
@@ -923,6 +925,8 @@ class ColumnHeaders(tk.Canvas):
|
|
923
925
|
return event_data
|
924
926
|
column = self.MT.selected.column
|
925
927
|
if try_binding(self.ch_extra_begin_sort_rows_func, event_data, "begin_move_rows"):
|
928
|
+
if key is None:
|
929
|
+
key = self.PAR.ops.sort_key
|
926
930
|
disp_new_idxs, disp_row_ctr = {}, 0
|
927
931
|
if self.ops.treeview:
|
928
932
|
new_nodes_order, data_new_idxs = sort_tree_view(
|
@@ -2239,16 +2243,17 @@ class ColumnHeaders(tk.Canvas):
|
|
2239
2243
|
self.MT._headers[datacn] = value
|
2240
2244
|
|
2241
2245
|
def input_valid_for_cell(self, datacn: int, value: object, check_readonly: bool = True) -> bool:
|
2242
|
-
|
2246
|
+
kwargs = self.get_cell_kwargs(datacn, key=None)
|
2247
|
+
if check_readonly and "readonly" in kwargs:
|
2243
2248
|
return False
|
2244
|
-
|
2249
|
+
elif "checkbox" in kwargs:
|
2245
2250
|
return is_bool_like(value)
|
2246
|
-
|
2251
|
+
elif self.cell_equal_to(datacn, value):
|
2247
2252
|
return False
|
2248
|
-
kwargs
|
2249
|
-
if kwargs and kwargs["validate_input"] and value not in kwargs["values"]:
|
2253
|
+
elif (kwargs := kwargs.get("dropdown", {})) and kwargs["validate_input"] and value not in kwargs["values"]:
|
2250
2254
|
return False
|
2251
|
-
|
2255
|
+
else:
|
2256
|
+
return True
|
2252
2257
|
|
2253
2258
|
def cell_equal_to(self, datacn: int, value: object) -> bool:
|
2254
2259
|
self.fix_header(datacn)
|
@@ -2303,12 +2308,13 @@ class ColumnHeaders(tk.Canvas):
|
|
2303
2308
|
return value
|
2304
2309
|
|
2305
2310
|
def get_value_for_empty_cell(self, datacn: int, c_ops: bool = True) -> object:
|
2306
|
-
|
2311
|
+
kwargs = self.get_cell_kwargs(datacn, key=None, cell=c_ops)
|
2312
|
+
if "checkbox" in kwargs:
|
2307
2313
|
return False
|
2308
|
-
kwargs
|
2309
|
-
if kwargs and kwargs["validate_input"] and kwargs["values"]:
|
2314
|
+
elif (kwargs := kwargs.get("dropdown", {})) and kwargs["validate_input"] and kwargs["values"]:
|
2310
2315
|
return kwargs["values"][0]
|
2311
|
-
|
2316
|
+
else:
|
2317
|
+
return ""
|
2312
2318
|
|
2313
2319
|
def get_empty_header_seq(self, end: int, start: int = 0, c_ops: bool = True) -> list[object]:
|
2314
2320
|
return [self.get_value_for_empty_cell(datacn, c_ops=c_ops) for datacn in range(start, end)]
|
@@ -2376,7 +2382,8 @@ class ColumnHeaders(tk.Canvas):
|
|
2376
2382
|
if redraw:
|
2377
2383
|
self.MT.refresh()
|
2378
2384
|
|
2379
|
-
def get_cell_kwargs(self, datacn: int, key: Hashable = "dropdown", cell: bool = True) -> dict:
|
2380
|
-
if cell and datacn in self.cell_options
|
2381
|
-
return self.cell_options[datacn][key
|
2382
|
-
|
2385
|
+
def get_cell_kwargs(self, datacn: int, key: Hashable | None = "dropdown", cell: bool = True) -> dict:
|
2386
|
+
if cell and datacn in self.cell_options:
|
2387
|
+
return self.cell_options[datacn] if key is None else self.cell_options[datacn].get(key, {})
|
2388
|
+
else:
|
2389
|
+
return {}
|
tksheet/functions.py
CHANGED
@@ -8,13 +8,14 @@ import tkinter as tk
|
|
8
8
|
from bisect import bisect_left
|
9
9
|
from collections import deque
|
10
10
|
from collections.abc import Callable, Generator, Hashable, Iterable, Iterator, Sequence
|
11
|
+
from difflib import SequenceMatcher
|
11
12
|
from itertools import islice, repeat
|
12
13
|
from typing import Literal
|
13
14
|
|
14
15
|
from .colors import color_map
|
15
16
|
from .constants import align_value_error, symbols_set
|
16
17
|
from .formatters import to_bool
|
17
|
-
from .other_classes import
|
18
|
+
from .other_classes import DotDict, EventDataDict, Highlight, Loc, Span
|
18
19
|
from .tksheet_types import AnyIter
|
19
20
|
|
20
21
|
unpickle_obj = pickle.loads
|
@@ -231,50 +232,45 @@ def event_opens_dropdown_or_checkbox(event=None) -> bool:
|
|
231
232
|
)
|
232
233
|
|
233
234
|
|
234
|
-
def dropdown_search_function(
|
235
|
-
search_for
|
236
|
-
data: Sequence[object],
|
237
|
-
) -> None | int:
|
235
|
+
def dropdown_search_function(search_for: str, data: Iterable[object]) -> None | int:
|
236
|
+
search_for = search_for.lower()
|
238
237
|
search_len = len(search_for)
|
239
|
-
|
240
|
-
|
238
|
+
if not search_len:
|
239
|
+
return next((i for i, v in enumerate(data) if not str(v)), None)
|
240
|
+
|
241
|
+
matcher = SequenceMatcher(None, search_for, "", autojunk=False)
|
242
|
+
|
243
|
+
match_rn = None
|
241
244
|
match_st = float("inf")
|
242
245
|
match_len_diff = float("inf")
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
len_diff = len(dd_val) - search_len
|
246
|
+
|
247
|
+
fallback_rn = None
|
248
|
+
fallback_match_length = 0
|
249
|
+
fallback_st = float("inf")
|
250
|
+
|
251
|
+
for rn, value in enumerate(data):
|
252
|
+
value = str(value).lower()
|
253
|
+
if not value:
|
254
|
+
continue
|
255
|
+
st = value.find(search_for)
|
256
|
+
if st != -1:
|
257
|
+
len_diff = len(value) - search_len
|
256
258
|
if st < match_st or (st == match_st and len_diff < match_len_diff):
|
257
259
|
match_rn = rn
|
258
260
|
match_st = st
|
259
261
|
match_len_diff = len_diff
|
260
|
-
|
261
|
-
elif match_rn
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
match_data_numchars = numchars
|
273
|
-
if match_rn != float("inf"):
|
274
|
-
return match_rn
|
275
|
-
elif match_data_rn != float("inf"):
|
276
|
-
return match_data_rn
|
277
|
-
return None
|
262
|
+
|
263
|
+
elif match_rn is None:
|
264
|
+
matcher.set_seq2(value)
|
265
|
+
match = matcher.find_longest_match(0, search_len, 0, len(value))
|
266
|
+
match_length = match.size
|
267
|
+
start = match.b if match_length > 0 else -1
|
268
|
+
if match_length > fallback_match_length or (match_length == fallback_match_length and start < fallback_st):
|
269
|
+
fallback_rn = rn
|
270
|
+
fallback_match_length = match_length
|
271
|
+
fallback_st = start
|
272
|
+
|
273
|
+
return match_rn if match_rn is not None else fallback_rn
|
278
274
|
|
279
275
|
|
280
276
|
def float_to_int(f: int | float) -> int | float:
|
@@ -283,10 +279,6 @@ def float_to_int(f: int | float) -> int | float:
|
|
283
279
|
return int(f)
|
284
280
|
|
285
281
|
|
286
|
-
def selection_box_tup_to_dict(box: tuple) -> dict:
|
287
|
-
return {Box_nt(*box[:-1]): box[-1]}
|
288
|
-
|
289
|
-
|
290
282
|
def event_dict(
|
291
283
|
name: str = None,
|
292
284
|
sheet: object = None,
|
@@ -338,9 +330,7 @@ def event_dict(
|
|
338
330
|
),
|
339
331
|
named_spans=DotDict() if named_spans is None else named_spans,
|
340
332
|
options=DotDict(),
|
341
|
-
selection_boxes=
|
342
|
-
{} if boxes is None else selection_box_tup_to_dict(boxes) if isinstance(boxes, tuple) else boxes
|
343
|
-
),
|
333
|
+
selection_boxes={} if boxes is None else boxes,
|
344
334
|
selected=tuple() if selected is None else selected,
|
345
335
|
being_selected=tuple() if being_selected is None else being_selected,
|
346
336
|
data=[] if data is None else data,
|
@@ -409,12 +399,22 @@ def push_n(num: int, sorted_seq: Sequence[int]) -> int:
|
|
409
399
|
if num < sorted_seq[0]:
|
410
400
|
return num
|
411
401
|
else:
|
412
|
-
|
413
|
-
|
414
|
-
num
|
415
|
-
|
416
|
-
|
417
|
-
|
402
|
+
if (hi := len(sorted_seq)) < 5:
|
403
|
+
for e in sorted_seq:
|
404
|
+
if num >= e:
|
405
|
+
num += 1
|
406
|
+
else:
|
407
|
+
return num
|
408
|
+
return num
|
409
|
+
else:
|
410
|
+
lo = 0
|
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
|
418
418
|
|
419
419
|
|
420
420
|
def get_dropdown_kwargs(
|
@@ -615,14 +615,18 @@ def consecutive_chunks(seq: list[int]) -> Generator[list[int]]:
|
|
615
615
|
|
616
616
|
|
617
617
|
def consecutive_ranges(seq: Sequence[int]) -> Generator[tuple[int, int]]:
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
618
|
+
seq_iter = iter(seq)
|
619
|
+
try:
|
620
|
+
start = next(seq_iter)
|
621
|
+
except StopIteration:
|
622
|
+
return
|
623
|
+
prev = start
|
624
|
+
for curr in seq_iter:
|
625
|
+
if curr > prev + 1:
|
626
|
+
yield start, prev + 1
|
627
|
+
start = curr
|
628
|
+
prev = curr
|
629
|
+
yield start, prev + 1
|
626
630
|
|
627
631
|
|
628
632
|
def is_contiguous(iterable: Iterable[int]) -> bool:
|
@@ -743,6 +747,17 @@ def move_elements_by_mapping(
|
|
743
747
|
return [seq[old_idxs[i]] if i in old_idxs else next(remaining_values) for i in range(len(seq))]
|
744
748
|
|
745
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
|
+
|
746
761
|
def move_elements_to(
|
747
762
|
seq: list[object],
|
748
763
|
move_to: int,
|
@@ -760,10 +775,12 @@ def move_elements_to(
|
|
760
775
|
|
761
776
|
def get_new_indexes(
|
762
777
|
move_to: int,
|
763
|
-
to_move:
|
778
|
+
to_move: AnyIter[int],
|
764
779
|
get_inverse: bool = False,
|
765
780
|
) -> tuple[dict]:
|
766
781
|
"""
|
782
|
+
move_to: A positive int, could possibly be the same as an element of to_move
|
783
|
+
to_move: A sorted list[int], ints always positive and unique, list never empty.
|
767
784
|
returns {old idx: new idx, ...}
|
768
785
|
"""
|
769
786
|
offset = sum(1 for i in to_move if i < move_to)
|
@@ -779,6 +796,11 @@ def insert_items(
|
|
779
796
|
to_insert: dict[int, object],
|
780
797
|
seq_len_func: Callable | None = None,
|
781
798
|
) -> list[object]:
|
799
|
+
"""
|
800
|
+
seq: list[object]
|
801
|
+
to_insert: keys are ints sorted in reverse, representing list indexes to insert items.
|
802
|
+
Values are any object, e.g. {1: 200, 0: 200}
|
803
|
+
"""
|
782
804
|
if to_insert:
|
783
805
|
if seq_len_func and next(iter(to_insert)) >= len(seq) + len(to_insert):
|
784
806
|
seq_len_func(next(iter(to_insert)) - len(to_insert))
|
@@ -866,24 +888,12 @@ def rounded_box_coords(
|
|
866
888
|
)
|
867
889
|
|
868
890
|
|
869
|
-
def diff_list(seq: list[float]) -> list[int]:
|
870
|
-
return [
|
871
|
-
int(b - a)
|
872
|
-
for a, b in zip(
|
873
|
-
seq,
|
874
|
-
islice(seq, 1, None),
|
875
|
-
)
|
876
|
-
]
|
877
|
-
|
878
|
-
|
879
891
|
def diff_gen(seq: list[float]) -> Generator[int]:
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
)
|
886
|
-
)
|
892
|
+
it = iter(seq)
|
893
|
+
a = next(it)
|
894
|
+
for b in it:
|
895
|
+
yield int(b - a)
|
896
|
+
a = b
|
887
897
|
|
888
898
|
|
889
899
|
def gen_coords(
|