tksheet 7.4.2__tar.gz → 7.4.4__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.2/tksheet.egg-info → tksheet-7.4.4}/PKG-INFO +3 -1
  2. {tksheet-7.4.2 → tksheet-7.4.4}/README.md +2 -0
  3. {tksheet-7.4.2 → tksheet-7.4.4}/pyproject.toml +1 -1
  4. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/__init__.py +2 -1
  5. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/column_headers.py +21 -14
  6. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/functions.py +87 -77
  7. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/main_table.py +98 -86
  8. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/row_index.py +42 -32
  9. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/sheet.py +51 -70
  10. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/sheet_options.py +3 -1
  11. tksheet-7.4.4/tksheet/sorting.py +531 -0
  12. {tksheet-7.4.2 → tksheet-7.4.4/tksheet.egg-info}/PKG-INFO +3 -1
  13. tksheet-7.4.2/tksheet/sorting.py +0 -335
  14. {tksheet-7.4.2 → tksheet-7.4.4}/LICENSE.txt +0 -0
  15. {tksheet-7.4.2 → tksheet-7.4.4}/setup.cfg +0 -0
  16. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/colors.py +0 -0
  17. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/constants.py +0 -0
  18. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/find_window.py +0 -0
  19. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/formatters.py +0 -0
  20. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/other_classes.py +0 -0
  21. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/text_editor.py +0 -0
  22. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/themes.py +0 -0
  23. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/tksheet_types.py +0 -0
  24. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet/top_left_rectangle.py +0 -0
  25. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet.egg-info/SOURCES.txt +0 -0
  26. {tksheet-7.4.2 → tksheet-7.4.4}/tksheet.egg-info/dependency_links.txt +0 -0
  27. {tksheet-7.4.2 → tksheet-7.4.4}/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.2
3
+ Version: 7.4.4
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
@@ -101,6 +101,8 @@ License-File: LICENSE.txt
101
101
  - [In-built natural sorting](https://github.com/ragardner/tksheet/wiki/Version-7#sorting-the-table)
102
102
  - [Optional built-in find window](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
103
103
 
104
+ Note that due to the limitations of the Tkinter Canvas right-to-left (RTL) languages are not supported.
105
+
104
106
  ```python
105
107
  """
106
108
  Versions 7+ have succinct and easy to read syntax:
@@ -58,6 +58,8 @@
58
58
  - [In-built natural sorting](https://github.com/ragardner/tksheet/wiki/Version-7#sorting-the-table)
59
59
  - [Optional built-in find window](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
60
60
 
61
+ Note that due to the limitations of the Tkinter Canvas right-to-left (RTL) languages are not supported.
62
+
61
63
  ```python
62
64
  """
63
65
  Versions 7+ have succinct and easy to read syntax:
@@ -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.2"
9
+ version = "7.4.4"
10
10
  authors = [{ name = "ragardner", email = "github@ragardner.simplelogin.com" }]
11
11
  requires-python = ">=3.8"
12
12
  license = {file = "LICENSE.txt"}
@@ -4,7 +4,7 @@
4
4
  tksheet - A Python tkinter table widget
5
5
  """
6
6
 
7
- __version__ = "7.4.2"
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,
@@ -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
- if check_readonly and self.get_cell_kwargs(datacn, key="readonly"):
2246
+ kwargs = self.get_cell_kwargs(datacn, key=None)
2247
+ if check_readonly and "readonly" in kwargs:
2243
2248
  return False
2244
- if self.get_cell_kwargs(datacn, key="checkbox"):
2249
+ elif "checkbox" in kwargs:
2245
2250
  return is_bool_like(value)
2246
- if self.cell_equal_to(datacn, value):
2251
+ elif self.cell_equal_to(datacn, value):
2247
2252
  return False
2248
- kwargs = self.get_cell_kwargs(datacn, key="dropdown")
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
- return True
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
- if self.get_cell_kwargs(datacn, key="checkbox", cell=c_ops):
2311
+ kwargs = self.get_cell_kwargs(datacn, key=None, cell=c_ops)
2312
+ if "checkbox" in kwargs:
2307
2313
  return False
2308
- kwargs = self.get_cell_kwargs(datacn, key="dropdown", cell=c_ops)
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
- return ""
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 and key in self.cell_options[datacn]:
2381
- return self.cell_options[datacn][key]
2382
- return {}
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 {}
@@ -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 Box_nt, DotDict, EventDataDict, Highlight, Loc, Span
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: object,
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
- # search_for in data
240
- match_rn = float("inf")
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
- # data in search_for in case no match
244
- match_data_rn = float("inf")
245
- match_data_st = float("inf")
246
- match_data_numchars = 0
247
- for rn, row in enumerate(data):
248
- dd_val = rf"{row[0]}".lower()
249
- # checking if search text is in dropdown row
250
- st = dd_val.find(search_for)
251
- if st > -1:
252
- # priority is start index
253
- # if there's already a matching start
254
- # then compare the len difference
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
- # fall back in case of no existing match
261
- elif match_rn == float("inf"):
262
- for numchars in range(2, search_len - 1):
263
- for from_idx in range(search_len - 1):
264
- if from_idx + numchars > search_len:
265
- break
266
- st = dd_val.find(search_for[from_idx : from_idx + numchars])
267
- if st > -1 and (
268
- numchars > match_data_numchars or (numchars == match_data_numchars and st < match_data_st)
269
- ):
270
- match_data_rn = rn
271
- match_data_st = st
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
- for e in sorted_seq:
413
- if num >= e:
414
- num += 1
415
- else:
416
- return num
417
- return num
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
- start = 0
619
- for index, value in enumerate(seq, 1):
620
- try:
621
- if seq[index] > value + 1:
622
- yield seq[start], seq[index - 1] + 1
623
- start = index
624
- except Exception:
625
- yield seq[start], seq[-1] + 1
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: list[int],
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
- return (
881
- int(b - a)
882
- for a, b in zip(
883
- seq,
884
- islice(seq, 1, None),
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(