tksheet 7.4.3__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 CHANGED
@@ -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.4"
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,
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
- 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 {}
tksheet/functions.py CHANGED
@@ -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,9 +330,7 @@ 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
- ),
333
+ selection_boxes={} if boxes is None else boxes,
340
334
  selected=tuple() if selected is None else selected,
341
335
  being_selected=tuple() if being_selected is None else being_selected,
342
336
  data=[] if data is None else data,
@@ -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,10 +775,12 @@ 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: AnyIter[int],
774
779
  get_inverse: bool = False,
775
780
  ) -> tuple[dict]:
776
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.
777
784
  returns {old idx: new idx, ...}
778
785
  """
779
786
  offset = sum(1 for i in to_move if i < move_to)
@@ -789,6 +796,11 @@ def insert_items(
789
796
  to_insert: dict[int, object],
790
797
  seq_len_func: Callable | None = None,
791
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
+ """
792
804
  if to_insert:
793
805
  if seq_len_func and next(iter(to_insert)) >= len(seq) + len(to_insert):
794
806
  seq_len_func(next(iter(to_insert)) - len(to_insert))
@@ -876,24 +888,12 @@ def rounded_box_coords(
876
888
  )
877
889
 
878
890
 
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
891
  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
- )
892
+ it = iter(seq)
893
+ a = next(it)
894
+ for b in it:
895
+ yield int(b - a)
896
+ a = b
897
897
 
898
898
 
899
899
  def gen_coords(
tksheet/main_table.py CHANGED
@@ -50,7 +50,6 @@ from .functions import (
50
50
  consecutive_ranges,
51
51
  data_to_displayed_idxs,
52
52
  diff_gen,
53
- diff_list,
54
53
  down_cell_within_box,
55
54
  event_dict,
56
55
  event_has_char_key,
@@ -72,6 +71,7 @@ from .functions import (
72
71
  mod_span,
73
72
  mod_span_widget,
74
73
  move_elements_by_mapping,
74
+ move_elements_by_mapping_gen,
75
75
  new_tk_event,
76
76
  next_cell,
77
77
  push_n,
@@ -932,19 +932,21 @@ class MainTable(tk.Canvas):
932
932
  def sort_boxes(
933
933
  self,
934
934
  event: tk.Event | None = None,
935
- boxes: AnyIter[int, int, int, int] | None = None,
935
+ boxes: dict[tuple[int, int, int, int], Literal["cells", "rows", "columns"]] | None = None,
936
936
  reverse: bool = False,
937
937
  row_wise: bool = False,
938
938
  validation: bool = True,
939
939
  key: Callable | None = None,
940
940
  undo: bool = True,
941
941
  ) -> EventDataDict:
942
- if boxes is None:
942
+ if not boxes:
943
943
  boxes = self.get_boxes()
944
944
  if not boxes:
945
- boxes = [(0, 0, len(self.row_positions) - 1, len(self.col_positions) - 1)]
945
+ boxes = {Box_nt(0, 0, len(self.row_positions) - 1, len(self.col_positions) - 1): "cells"}
946
946
  event_data = self.new_event_dict("edit_table", boxes=boxes)
947
947
  try_binding(self.extra_begin_sort_cells_func, event_data)
948
+ if key is None:
949
+ key = self.PAR.ops.sort_key
948
950
  for r1, c1, r2, c2 in boxes:
949
951
  data = sort_selection(
950
952
  [[self.get_cell_data(self.datarn(r), self.datacn(c)) for c in range(c1, c2)] for r in range(r1, r2)],
@@ -1287,26 +1289,36 @@ class MainTable(tk.Canvas):
1287
1289
  data_indexes: bool = False,
1288
1290
  ) -> tuple[dict[int, int], dict[int, int], int, dict[int, int]]:
1289
1291
  if not data_indexes or self.all_columns_displayed:
1290
- disp_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1292
+ disp_new_idxs = get_new_indexes(
1293
+ move_to=move_to,
1294
+ to_move=to_move,
1295
+ )
1296
+ data_new_idxs = dict(disp_new_idxs)
1291
1297
  else:
1292
1298
  disp_new_idxs = {}
1299
+ data_new_idxs = get_new_indexes(
1300
+ move_to=move_to,
1301
+ to_move=to_move,
1302
+ )
1293
1303
  # at_least_cols should not be len in this case as move_to can be len
1294
1304
  fix_len = (move_to - 1) if move_to else move_to
1295
1305
  if not self.all_columns_displayed and not data_indexes:
1296
1306
  fix_len = self.datacn(fix_len)
1297
1307
  totalcols = self.equalize_data_row_lengths(at_least_cols=fix_len)
1298
- data_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1299
1308
  if not self.all_columns_displayed and not data_indexes:
1300
- data_new_idxs = dict(
1301
- zip(
1302
- move_elements_by_mapping(
1309
+ keep = set(map(self.datacn, to_move))
1310
+ data_new_idxs = {
1311
+ k: v
1312
+ for k, v in zip(
1313
+ move_elements_by_mapping_gen(
1303
1314
  self.displayed_columns,
1304
1315
  data_new_idxs,
1305
1316
  dict(zip(data_new_idxs.values(), data_new_idxs)),
1306
1317
  ),
1307
1318
  self.displayed_columns,
1308
- ),
1309
- )
1319
+ )
1320
+ if k in keep
1321
+ }
1310
1322
  return data_new_idxs, dict(zip(data_new_idxs.values(), data_new_idxs)), totalcols, disp_new_idxs
1311
1323
 
1312
1324
  def move_columns_adjust_options_dict(
@@ -1338,7 +1350,7 @@ class MainTable(tk.Canvas):
1338
1350
 
1339
1351
  if move_widths and disp_new_idxs:
1340
1352
  self.set_col_positions(
1341
- itr=move_elements_by_mapping(
1353
+ itr=move_elements_by_mapping_gen(
1342
1354
  self.get_column_widths(),
1343
1355
  disp_new_idxs,
1344
1356
  dict(
@@ -1537,7 +1549,7 @@ class MainTable(tk.Canvas):
1537
1549
  data_new_idxs = {
1538
1550
  k: v
1539
1551
  for k, v in zip(
1540
- move_elements_by_mapping(
1552
+ move_elements_by_mapping_gen(
1541
1553
  self.displayed_rows,
1542
1554
  data_new_idxs,
1543
1555
  dict(zip(data_new_idxs.values(), data_new_idxs)),
@@ -1732,7 +1744,7 @@ class MainTable(tk.Canvas):
1732
1744
 
1733
1745
  if move_heights and disp_new_idxs:
1734
1746
  self.set_row_positions(
1735
- itr=move_elements_by_mapping(
1747
+ itr=move_elements_by_mapping_gen(
1736
1748
  self.get_row_heights(),
1737
1749
  disp_new_idxs,
1738
1750
  dict(
@@ -1788,7 +1800,7 @@ class MainTable(tk.Canvas):
1788
1800
  old_idxs = dict(zip(new_idxs.values(), new_idxs))
1789
1801
  return dict(
1790
1802
  zip(
1791
- move_elements_by_mapping(tuple(range(max_idx + 1)), new_idxs, old_idxs),
1803
+ move_elements_by_mapping_gen(tuple(range(max_idx + 1)), new_idxs, old_idxs),
1792
1804
  range(max_idx + 1),
1793
1805
  )
1794
1806
  )
@@ -4489,10 +4501,10 @@ class MainTable(tk.Canvas):
4489
4501
  self.set_row_positions(itr=(h for i, h in enumerate(self.gen_row_heights()) if i not in idxs))
4490
4502
 
4491
4503
  def get_column_widths(self) -> list[int]:
4492
- return diff_list(self.col_positions)
4504
+ return list(diff_gen(self.col_positions))
4493
4505
 
4494
4506
  def get_row_heights(self) -> list[int]:
4495
- return diff_list(self.row_positions)
4507
+ return list(diff_gen(self.row_positions))
4496
4508
 
4497
4509
  def gen_column_widths(self) -> Generator[int]:
4498
4510
  return diff_gen(self.col_positions)
@@ -7893,26 +7905,20 @@ class MainTable(tk.Canvas):
7893
7905
  self.data[datarn][datacn] = value
7894
7906
 
7895
7907
  def get_value_for_empty_cell(self, datarn: int, datacn: int, r_ops: bool = True, c_ops: bool = True) -> object:
7896
- if self.get_cell_kwargs(
7897
- datarn,
7898
- datacn,
7899
- key="checkbox",
7900
- cell=r_ops and c_ops,
7901
- row=r_ops,
7902
- column=c_ops,
7903
- ):
7904
- return False
7905
7908
  kwargs = self.get_cell_kwargs(
7906
7909
  datarn,
7907
7910
  datacn,
7908
- key="dropdown",
7911
+ key=None,
7909
7912
  cell=r_ops and c_ops,
7910
7913
  row=r_ops,
7911
7914
  column=c_ops,
7912
7915
  )
7913
- if kwargs and kwargs["validate_input"] and kwargs["values"]:
7916
+ if "checkbox" in kwargs:
7917
+ return False
7918
+ elif (kwargs := kwargs.get("dropdown", {})) and kwargs["validate_input"] and kwargs["values"]:
7914
7919
  return kwargs["values"][0]
7915
- return ""
7920
+ else:
7921
+ return ""
7916
7922
 
7917
7923
  def get_empty_row_seq(
7918
7924
  self,
@@ -8072,18 +8078,21 @@ class MainTable(tk.Canvas):
8072
8078
  check_readonly: bool = True,
8073
8079
  ignore_empty: bool = False,
8074
8080
  ) -> bool:
8075
- if check_readonly and self.get_cell_kwargs(datarn, datacn, key="readonly"):
8081
+ kwargs = self.get_cell_kwargs(datarn, datacn, key=None)
8082
+ if check_readonly and "readonly" in kwargs:
8076
8083
  return False
8077
- if self.get_cell_kwargs(datarn, datacn, key="format"):
8084
+ elif "format" in kwargs:
8078
8085
  return True
8079
- if self.cell_equal_to(datarn, datacn, value, ignore_empty=ignore_empty):
8086
+ elif self.cell_equal_to(datarn, datacn, value, ignore_empty=ignore_empty):
8080
8087
  return False
8081
- kwargs = self.get_cell_kwargs(datarn, datacn, key="dropdown")
8082
- if kwargs and kwargs["validate_input"] and value not in kwargs["values"]:
8088
+ elif (
8089
+ (dropdown := kwargs.get("dropdown", {})) and dropdown["validate_input"] and value not in dropdown["values"]
8090
+ ):
8083
8091
  return False
8084
- if self.get_cell_kwargs(datarn, datacn, key="checkbox"):
8092
+ elif "checkbox" in kwargs:
8085
8093
  return is_bool_like(value)
8086
- return True
8094
+ else:
8095
+ return True
8087
8096
 
8088
8097
  def cell_equal_to(self, datarn: int, datacn: int, value: object, ignore_empty: bool = False, **kwargs) -> bool:
8089
8098
  v = self.get_cell_data(datarn, datacn)
@@ -8118,18 +8127,21 @@ class MainTable(tk.Canvas):
8118
8127
  self,
8119
8128
  datarn: int,
8120
8129
  datacn: int,
8121
- key: Hashable = "format",
8130
+ key: Hashable | None = "format",
8122
8131
  cell: bool = True,
8123
8132
  row: bool = True,
8124
8133
  column: bool = True,
8125
8134
  ) -> dict:
8126
- if cell and (datarn, datacn) in self.cell_options and key in self.cell_options[(datarn, datacn)]:
8127
- return self.cell_options[(datarn, datacn)][key]
8128
- if row and datarn in self.row_options and key in self.row_options[datarn]:
8129
- return self.row_options[datarn][key]
8130
- if column and datacn in self.col_options and key in self.col_options[datacn]:
8131
- return self.col_options[datacn][key]
8132
- return {}
8135
+ if cell and (datarn, datacn) in self.cell_options:
8136
+ return (
8137
+ self.cell_options[(datarn, datacn)] if key is None else self.cell_options[(datarn, datacn)].get(key, {})
8138
+ )
8139
+ elif row and datarn in self.row_options:
8140
+ return self.row_options[datarn] if key is None else self.row_options[datarn].get(key, {})
8141
+ elif column and datacn in self.col_options:
8142
+ return self.col_options[datacn] if key is None else self.col_options[datacn].get(key, {})
8143
+ else:
8144
+ return {}
8133
8145
 
8134
8146
  def datacn(self, c: int) -> int:
8135
8147
  return c if self.all_columns_displayed else self.displayed_columns[c]
tksheet/row_index.py CHANGED
@@ -900,6 +900,8 @@ class RowIndex(tk.Canvas):
900
900
  rows = list(range(0, len(self.MT.row_positions) - 1))
901
901
  event_data = self.MT.new_event_dict("edit_table")
902
902
  try_binding(self.MT.extra_begin_sort_cells_func, event_data)
903
+ if key is None:
904
+ key = self.PAR.ops.sort_key
903
905
  for r in rows:
904
906
  datarn = self.MT.datarn(r)
905
907
  for c, val in enumerate(sort_row(self.MT.data[datarn], reverse=reverse, key=key)):
@@ -943,6 +945,8 @@ class RowIndex(tk.Canvas):
943
945
  return event_data
944
946
  row = self.MT.selected.row
945
947
  if try_binding(self.ri_extra_begin_sort_cols_func, event_data, "begin_move_columns"):
948
+ if key is None:
949
+ key = self.PAR.ops.sort_key
946
950
  sorted_indices, data_new_idxs = sort_columns_by_row(self.MT.data, row=row, reverse=reverse, key=key)
947
951
  disp_new_idxs = {}
948
952
  if self.MT.all_columns_displayed:
@@ -2424,16 +2428,17 @@ class RowIndex(tk.Canvas):
2424
2428
  self.MT._row_index[datarn] = value
2425
2429
 
2426
2430
  def input_valid_for_cell(self, datarn: int, value: object, check_readonly: bool = True) -> bool:
2427
- if check_readonly and self.get_cell_kwargs(datarn, key="readonly"):
2431
+ kwargs = self.get_cell_kwargs(datarn, key=None)
2432
+ if check_readonly and "readonly" in kwargs:
2428
2433
  return False
2429
- if self.get_cell_kwargs(datarn, key="checkbox"):
2434
+ elif "checkbox" in kwargs:
2430
2435
  return is_bool_like(value)
2431
- if self.cell_equal_to(datarn, value):
2436
+ elif self.cell_equal_to(datarn, value):
2432
2437
  return False
2433
- kwargs = self.get_cell_kwargs(datarn, key="dropdown")
2434
- if kwargs and kwargs["validate_input"] and value not in kwargs["values"]:
2438
+ elif (kwargs := kwargs.get("dropdown", {})) and kwargs["validate_input"] and value not in kwargs["values"]:
2435
2439
  return False
2436
- return True
2440
+ else:
2441
+ return True
2437
2442
 
2438
2443
  def cell_equal_to(self, datarn: int, value: object) -> bool:
2439
2444
  self.fix_index(datarn)
@@ -2495,12 +2500,13 @@ class RowIndex(tk.Canvas):
2495
2500
  if self.ops.treeview:
2496
2501
  iid = self.new_iid()
2497
2502
  return Node(text=iid, iid=iid, parent=self.get_row_parent(datarn))
2498
- if self.get_cell_kwargs(datarn, key="checkbox", cell=r_ops):
2503
+ kwargs = self.get_cell_kwargs(datarn, key=None, cell=r_ops)
2504
+ if "checkbox" in kwargs:
2499
2505
  return False
2500
- kwargs = self.get_cell_kwargs(datarn, key="dropdown", cell=r_ops)
2501
- if kwargs and kwargs["validate_input"] and kwargs["values"]:
2506
+ elif (kwargs := kwargs.get("dropdown", {})) and kwargs["validate_input"] and kwargs["values"]:
2502
2507
  return kwargs["values"][0]
2503
- return ""
2508
+ else:
2509
+ return ""
2504
2510
 
2505
2511
  def get_empty_index_seq(self, end: int, start: int = 0, r_ops: bool = True) -> list[object]:
2506
2512
  return [self.get_value_for_empty_cell(datarn, r_ops=r_ops) for datarn in range(start, end)]
@@ -2567,10 +2573,11 @@ class RowIndex(tk.Canvas):
2567
2573
  if redraw:
2568
2574
  self.MT.refresh()
2569
2575
 
2570
- def get_cell_kwargs(self, datarn: int, key: Hashable = "dropdown", cell: bool = True) -> dict:
2571
- if cell and datarn in self.cell_options and key in self.cell_options[datarn]:
2572
- return self.cell_options[datarn][key]
2573
- return {}
2576
+ def get_cell_kwargs(self, datarn: int, key: Hashable | None = "dropdown", cell: bool = True) -> dict:
2577
+ if cell and datarn in self.cell_options:
2578
+ return self.cell_options[datarn] if key is None else self.cell_options[datarn].get(key, {})
2579
+ else:
2580
+ return {}
2574
2581
 
2575
2582
  # Treeview Mode
2576
2583
 
@@ -2671,25 +2678,33 @@ class RowIndex(tk.Canvas):
2671
2678
  new_parent = node_change[1]
2672
2679
  move_to_index = node_change[2]
2673
2680
  if new_parent:
2674
- move_to_index = (
2675
- move_to_index if isinstance(move_to_index, int) else len(self.tree[new_parent].children)
2676
- )
2677
- move_to_row = self.tree_rns[new_parent] + max(
2678
- 0, min(move_to_index, len(self.tree[new_parent].children))
2679
- )
2681
+ if isinstance(move_to_index, int):
2682
+ move_to_index = min(move_to_index, len(self.tree[new_parent].children))
2683
+ else:
2684
+ move_to_index = len(self.tree[new_parent].children)
2685
+ move_to_row = self.tree_rns[new_parent]
2686
+ if new_parent == self.tree[item].parent:
2687
+ move_to_index += 1
2688
+ for i, ciid in enumerate(self.tree[new_parent].children):
2689
+ if i == move_to_index:
2690
+ break
2691
+ move_to_row += sum(1 for _ in self.get_iid_descendants(ciid)) + 1
2692
+ insert_row = move_to_row + 1
2680
2693
  else:
2681
2694
  num_top_nodes = sum(1 for _ in self.gen_top_nodes())
2682
2695
  if move_to_index is None:
2683
2696
  move_to_row = self.PAR.top_index_row(num_top_nodes - 1)
2684
2697
  move_to_index = num_top_nodes
2698
+ insert_row = move_to_row
2685
2699
  else:
2686
2700
  move_to_row = self.PAR.top_index_row(move_to_index)
2701
+ insert_row = move_to_row
2687
2702
  if move_to_row is None:
2688
2703
  move_to_row = self.PAR.top_index_row(num_top_nodes - 1)
2689
2704
  move_to_index = num_top_nodes
2705
+ insert_row = move_to_row + 1
2690
2706
 
2691
2707
  move_to_iid = self.MT._row_index[move_to_row].iid
2692
- insert_row = move_to_row + 1
2693
2708
  disp_insert_row = None
2694
2709
 
2695
2710
  else:
@@ -2733,10 +2748,10 @@ class RowIndex(tk.Canvas):
2733
2748
  event_data["moved"]["rows"]["displayed"] = {}
2734
2749
  if new_loc_is_displayed:
2735
2750
  if disp_insert_row is None:
2736
- if new_parent == self.tree[item].parent:
2751
+ if new_parent or insert_row > move_to_row:
2737
2752
  disp_insert_row = self.MT.disprn(self.tree_rns[move_to_iid]) + 1
2738
2753
  else:
2739
- disp_insert_row = self.MT.disprn(self.tree_rns[move_to_iid]) + 1
2754
+ disp_insert_row = self.MT.disprn(self.tree_rns[move_to_iid])
2740
2755
  if (disp_from_row := self.MT.try_disprn(self.tree_rns[item])) is not None:
2741
2756
  event_data["moved"]["rows"]["displayed"] = {disp_from_row: disp_insert_row}
2742
2757
  else:
@@ -2756,7 +2771,6 @@ class RowIndex(tk.Canvas):
2756
2771
  index=move_to_index,
2757
2772
  )
2758
2773
  move_to_index += 1
2759
-
2760
2774
  event_data["moved"]["rows"]["data"] = get_new_indexes(
2761
2775
  insert_row,
2762
2776
  event_data["moved"]["rows"]["data"],
@@ -2782,7 +2796,6 @@ class RowIndex(tk.Canvas):
2782
2796
  if not undo_modification and data_new_idxs:
2783
2797
  if new_parent and (not self.PAR.item_displayed(new_parent) or new_parent not in self.tree_open_ids):
2784
2798
  self.PAR.hide_rows(set(data_new_idxs.values()), data_indexes=True)
2785
-
2786
2799
  if new_loc_is_displayed:
2787
2800
  self.PAR.show_rows(
2788
2801
  (r for r in data_new_idxs.values() if self.ancestors_all_open(self.MT._row_index[r].iid))
@@ -2959,9 +2972,7 @@ class RowIndex(tk.Canvas):
2959
2972
  def move_pid_causes_recursive_loop(self, to_move_iid: str, move_to_parent: str) -> bool:
2960
2973
  # if the parent the item is being moved under is one of the item's descendants
2961
2974
  # then it is a recursive loop
2962
- return to_move_iid == move_to_parent or any(
2963
- move_to_parent == diid for diid in self.get_iid_descendants(to_move_iid)
2964
- )
2975
+ return any(move_to_parent == diid for diid in self.get_iid_descendants(to_move_iid))
2965
2976
 
2966
2977
  def tree_build(
2967
2978
  self,
tksheet/sheet.py CHANGED
@@ -54,6 +54,7 @@ from .functions import (
54
54
  )
55
55
  from .main_table import MainTable
56
56
  from .other_classes import (
57
+ Box_nt,
57
58
  DotDict,
58
59
  EventDataDict,
59
60
  FontTuple,
@@ -66,6 +67,7 @@ from .other_classes import (
66
67
  )
67
68
  from .row_index import RowIndex
68
69
  from .sheet_options import new_sheet_options
70
+ from .sorting import fast_sort_key, natural_sort_key, version_sort_key # noqa: F401
69
71
  from .themes import (
70
72
  theme_black,
71
73
  theme_dark,
@@ -187,6 +189,7 @@ class Sheet(tk.Frame):
187
189
  table_wrap: Literal["", "w", "c"] = "c",
188
190
  index_wrap: Literal["", "w", "c"] = "c",
189
191
  header_wrap: Literal["", "w", "c"] = "c",
192
+ sort_key: Callable = natural_sort_key,
190
193
  # colors
191
194
  outline_thickness: int = 0,
192
195
  theme: str = "light blue",
@@ -2586,7 +2589,7 @@ class Sheet(tk.Frame):
2586
2589
 
2587
2590
  def sort(
2588
2591
  self,
2589
- boxes: AnyIter[Sequence[int, int, int, int]] | Span | None = None,
2592
+ *box: CreateSpanTypes,
2590
2593
  reverse: bool = False,
2591
2594
  row_wise: bool = False,
2592
2595
  validation: bool = True,
@@ -2599,17 +2602,11 @@ class Sheet(tk.Frame):
2599
2602
  This method sorts the data within one or multiple box regions defined by their coordinates. Each box's columns are sorted independently.
2600
2603
 
2601
2604
  Args:
2602
- boxes (AnyIter[Sequence[int, int, int, int]] | Span | None): An iterable of box coordinates. Each box is defined by:
2603
- - From Row (inclusive)
2604
- - From Column (inclusive)
2605
- - Up To Row (exclusive)
2606
- - Up To Column (exclusive)
2607
- If None, it will sort the currently selected boxes or the entire table.
2608
- If Span, it will use the span coordinates.
2605
+ boxes (CreateSpanTypes): A type that can create a Span.
2609
2606
 
2610
2607
  reverse (bool): If True, sorts in descending order. Default is False (ascending).
2611
2608
 
2612
- row_wise (bool): if True sorts cells row-wise. Default is column-wise
2609
+ row_wise (bool): if True sorts cells row-wise. Default is column-wise.
2613
2610
 
2614
2611
  validation (bool): If True, checks if the new cell values are valid according to any restrictions
2615
2612
  (e.g., dropdown validations) before applying the sort. Default is True.
@@ -2626,19 +2623,21 @@ class Sheet(tk.Frame):
2626
2623
  ValueError: If the input boxes are not in the expected format or if the data cannot be sorted.
2627
2624
 
2628
2625
  Note:
2629
- - Sorting is performed column-wise within each box.
2630
2626
  - If validation is enabled, any cell content that fails validation will not be updated.
2631
2627
  - Any cell options attached to cells will not be moved. Event data can be used to re-locate them.
2632
2628
  """
2633
- if isinstance(boxes, Span):
2634
- boxes = [boxes.coords]
2635
2629
  return self.MT.sort_boxes(
2636
- boxes=boxes, reverse=reverse, row_wise=row_wise, validation=validation, key=key, undo=undo
2630
+ boxes={Box_nt(*self.span_from_key(*box).coords): "cells"},
2631
+ reverse=reverse,
2632
+ row_wise=row_wise,
2633
+ validation=validation,
2634
+ key=key,
2635
+ undo=undo,
2637
2636
  )
2638
2637
 
2639
2638
  def sort_rows(
2640
2639
  self,
2641
- rows: AnyIter[int] | Span | None = None,
2640
+ rows: AnyIter[int] | Span | int | None = None,
2642
2641
  reverse: bool = False,
2643
2642
  validation: bool = True,
2644
2643
  key: Callable | None = None,
@@ -2646,11 +2645,13 @@ class Sheet(tk.Frame):
2646
2645
  ) -> EventDataDict:
2647
2646
  if isinstance(rows, Span):
2648
2647
  rows = rows.rows
2648
+ elif isinstance(rows, int):
2649
+ rows = (rows,)
2649
2650
  return self.RI._sort_rows(rows=rows, reverse=reverse, validation=validation, key=key, undo=undo)
2650
2651
 
2651
2652
  def sort_columns(
2652
2653
  self,
2653
- columns: AnyIter[int] | Span | None = None,
2654
+ columns: AnyIter[int] | Span | int | None = None,
2654
2655
  reverse: bool = False,
2655
2656
  validation: bool = True,
2656
2657
  key: Callable | None = None,
@@ -2658,6 +2659,8 @@ class Sheet(tk.Frame):
2658
2659
  ) -> EventDataDict:
2659
2660
  if isinstance(columns, Span):
2660
2661
  columns = columns.columns
2662
+ elif isinstance(columns, int):
2663
+ columns = (columns,)
2661
2664
  return self.CH._sort_columns(columns=columns, reverse=reverse, validation=validation, key=key, undo=undo)
2662
2665
 
2663
2666
  def sort_rows_by_column(
@@ -4662,24 +4665,14 @@ class Sheet(tk.Frame):
4662
4665
  Also retrieves any row or column options
4663
4666
  impacting that cell
4664
4667
  """
4665
- if isinstance(column, str):
4666
- column = alpha2idx(column)
4667
- if key is not None:
4668
- return self.MT.get_cell_kwargs(
4669
- datarn=row,
4670
- datacn=column,
4671
- key=key,
4672
- cell=cellops,
4673
- row=rowops,
4674
- column=columnops,
4675
- )
4676
- if cellops and (row, column) in self.MT.cell_options:
4677
- return self.MT.cell_options[(row, column)]
4678
- if rowops and row in self.MT.row_options:
4679
- return self.MT.row_options[row]
4680
- if columnops and column in self.MT.col_options:
4681
- return self.MT.col_options[column]
4682
- return {}
4668
+ return self.MT.get_cell_kwargs(
4669
+ datarn=row,
4670
+ datacn=alpha2idx(column) if isinstance(column, str) else column,
4671
+ key=key,
4672
+ cell=cellops,
4673
+ row=rowops,
4674
+ column=columnops,
4675
+ )
4683
4676
 
4684
4677
  def index_props(
4685
4678
  self,
@@ -4690,12 +4683,10 @@ class Sheet(tk.Frame):
4690
4683
  Retrieve options (properties - props)
4691
4684
  from a cell in the index
4692
4685
  """
4693
- if key is not None:
4694
- return self.RI.get_cell_kwargs(
4695
- datarn=row,
4696
- key=key,
4697
- )
4698
- return self.RI.cell_options[row] if row in self.RI.cell_options else {}
4686
+ return self.RI.get_cell_kwargs(
4687
+ datarn=row,
4688
+ key=key,
4689
+ )
4699
4690
 
4700
4691
  def header_props(
4701
4692
  self,
@@ -4706,14 +4697,10 @@ class Sheet(tk.Frame):
4706
4697
  Retrieve options (properties - props)
4707
4698
  from a cell in the header
4708
4699
  """
4709
- if isinstance(column, str):
4710
- column = alpha2idx(column)
4711
- if key is not None:
4712
- return self.CH.get_cell_kwargs(
4713
- datacn=column,
4714
- key=key,
4715
- )
4716
- return self.CH.cell_options[column] if column in self.CH.cell_options else {}
4700
+ return self.CH.get_cell_kwargs(
4701
+ datacn=alpha2idx(column) if isinstance(column, str) else column,
4702
+ key=key,
4703
+ )
4717
4704
 
4718
4705
  def get_cell_options(
4719
4706
  self,
tksheet/sheet_options.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from .constants import USER_OS, ctrl_key
4
4
  from .other_classes import DotDict, FontTuple
5
+ from .sorting import fast_sort_key, natural_sort_key, version_sort_key # noqa: F401
5
6
  from .themes import theme_light_blue
6
7
 
7
8
 
@@ -255,5 +256,6 @@ def new_sheet_options() -> DotDict:
255
256
  "max_row_height": float("inf"),
256
257
  "max_index_width": float("inf"),
257
258
  "show_top_left": None,
259
+ "sort_key": natural_sort_key,
258
260
  }
259
261
  )
tksheet/sorting.py CHANGED
@@ -3,12 +3,12 @@ from __future__ import annotations
3
3
  from collections.abc import Callable, Generator, Iterable, Iterator
4
4
  from datetime import datetime
5
5
  from pathlib import Path
6
- from re import finditer
6
+ from re import split
7
7
 
8
8
  AnyIter = Iterable | Iterator
9
9
 
10
10
  # Possible date formats to try for the entire string
11
- date_formats = [
11
+ date_formats = (
12
12
  # Common formats
13
13
  "%d/%m/%Y", # Day/Month/Year
14
14
  "%m/%d/%Y", # Month/Day/Year (US format)
@@ -24,7 +24,8 @@ date_formats = [
24
24
  "%m,%d,%Y", # Month,Day,Year
25
25
  "%Y,%m,%d", # Year,Month,Day
26
26
  "%d %m %Y", # Day Month Year (with space)
27
- "%m %d %Y", # Month Day Year
27
+ "%m %d %Y", # Month Day Year (with space)
28
+ "%Y %d %m", # Year Month Day (with space)
28
29
  # With month names
29
30
  "%d %b %Y", # Day Abbreviated Month Year
30
31
  "%b %d, %Y", # Abbreviated Month Day, Year
@@ -42,59 +43,228 @@ date_formats = [
42
43
  "%d/%m/%y %H:%M", # Day/Month/Year Hour:Minute
43
44
  "%m/%d/%y %H:%M", # Month/Day/Year Hour:Minute
44
45
  "%Y-%m-%d %H:%M:%S", # Year-Month-Day Hour:Minute:Second
45
- ]
46
+ )
46
47
 
47
48
 
48
- def natural_sort_key(item: object) -> tuple[int, ...]:
49
+ def _string_fallback(item: str) -> tuple[int, ...]:
50
+ """
51
+ In order to have reasonable file path sorting:
52
+ - Split by path separators
53
+ - Determine depth, more separators more depth
54
+ - Deal with every dir by splitting by digits
55
+ - Deal with file name by splitting by digits
56
+ """
57
+ components = split(r"[/\\]", item)
58
+ if components[-1]:
59
+ return (
60
+ 5,
61
+ len(components),
62
+ tuple(int(e) if e.isdigit() else e.lower() for comp in components[:-1] for e in split(r"(\d+)", comp) if e),
63
+ tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", components[-1])),
64
+ )
65
+ else:
66
+ return (
67
+ 5,
68
+ len(components),
69
+ tuple(int(e) if e.isdigit() else e.lower() for comp in components[:-1] for e in split(r"(\d+)", comp) if e),
70
+ tuple(),
71
+ )
72
+
73
+
74
+ def version_sort_key(item: object) -> tuple[int, ...]:
49
75
  """
50
76
  A key for natural sorting of various Python types.
51
77
 
78
+ - Won't convert strings to floats
79
+ - Will sort string version numbers
80
+
52
81
  0. None
53
- 1. bool
54
- 2. int, float
55
- 3. datetime
56
- 4. empty strings
57
- 5. strings (including paths as POSIX strings)
58
- 6. unknown objects with __str__
59
- 7. unknown objects
82
+ 1. Empty strings
83
+ 2. bool
84
+ 3. int, float
85
+ 4. datetime (inc. strings that are dates)
86
+ 5. strings (including string file paths and paths as POSIX strings) & unknown objects with __str__
87
+ 6. unknown objects
60
88
  """
61
- if item is None:
89
+ if isinstance(item, str):
90
+ if item:
91
+ for date_format in date_formats:
92
+ try:
93
+ return (4, datetime.strptime(item, date_format).timestamp())
94
+ except ValueError:
95
+ continue
96
+ # the same as _string_fallback
97
+ components = split(r"[/\\]", item)
98
+ if components[-1]:
99
+ return (
100
+ 5,
101
+ len(components),
102
+ tuple(
103
+ int(e) if e.isdigit() else e.lower()
104
+ for comp in components[:-1]
105
+ for e in split(r"(\d+)", comp)
106
+ if e
107
+ ),
108
+ tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", components[-1])),
109
+ )
110
+ else:
111
+ return (
112
+ 5,
113
+ len(components),
114
+ tuple(
115
+ int(e) if e.isdigit() else e.lower()
116
+ for comp in components[:-1]
117
+ for e in split(r"(\d+)", comp)
118
+ if e
119
+ ),
120
+ tuple(),
121
+ )
122
+ else:
123
+ return (1, item)
124
+
125
+ elif item is None:
62
126
  return (0,)
63
127
 
64
128
  elif isinstance(item, bool):
65
- return (1, item)
129
+ return (2, item)
66
130
 
67
131
  elif isinstance(item, (int, float)):
68
- return (2, item)
132
+ return (3, item)
69
133
 
70
134
  elif isinstance(item, datetime):
71
- return (3, item.timestamp())
135
+ return (4, item.timestamp())
72
136
 
73
- elif isinstance(item, str):
74
- if not item:
75
- return (4, item)
137
+ elif isinstance(item, Path):
138
+ return _string_fallback(item.as_posix())
139
+
140
+ else:
141
+ try:
142
+ return _string_fallback(f"{item}")
143
+ except Exception:
144
+ return (6, item)
145
+
146
+
147
+ def natural_sort_key(item: object) -> tuple[int, ...]:
148
+ """
149
+ A key for natural sorting of various Python types.
150
+
151
+ - Won't sort string version numbers
152
+ - Will convert strings to floats
153
+ - Will sort strings that are file paths
154
+
155
+ 0. None
156
+ 1. Empty strings
157
+ 2. bool
158
+ 3. int, float (inc. strings that are numbers)
159
+ 4. datetime (inc. strings that are dates)
160
+ 5. strings (including string file paths and paths as POSIX strings) & unknown objects with __str__
161
+ 6. unknown objects
162
+ """
163
+ if isinstance(item, str):
164
+ if item:
165
+ for date_format in date_formats:
166
+ try:
167
+ return (4, datetime.strptime(item, date_format).timestamp())
168
+ except ValueError:
169
+ continue
76
170
 
77
- for date_format in date_formats:
78
171
  try:
79
- return (3, datetime.strptime(item, date_format).timestamp())
172
+ return (3, float(item))
80
173
  except ValueError:
81
- continue
174
+ # the same as _string_fallback
175
+ components = split(r"[/\\]", item)
176
+ if components[-1]:
177
+ return (
178
+ 5,
179
+ len(components),
180
+ tuple(
181
+ int(e) if e.isdigit() else e.lower()
182
+ for comp in components[:-1]
183
+ for e in split(r"(\d+)", comp)
184
+ if e
185
+ ),
186
+ tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", components[-1])),
187
+ )
188
+ else:
189
+ return (
190
+ 5,
191
+ len(components),
192
+ tuple(
193
+ int(e) if e.isdigit() else e.lower()
194
+ for comp in components[:-1]
195
+ for e in split(r"(\d+)", comp)
196
+ if e
197
+ ),
198
+ tuple(),
199
+ )
200
+ else:
201
+ return (1, item)
202
+
203
+ elif item is None:
204
+ return (0,)
205
+
206
+ elif isinstance(item, bool):
207
+ return (2, item)
208
+
209
+ elif isinstance(item, (int, float)):
210
+ return (3, item)
211
+
212
+ elif isinstance(item, datetime):
213
+ return (4, item.timestamp())
214
+
215
+ elif isinstance(item, Path):
216
+ return _string_fallback(item.as_posix())
217
+
218
+ else:
82
219
  try:
83
- return (2, float(item))
220
+ return _string_fallback(f"{item}")
84
221
  except Exception:
85
- pass
222
+ return (6, item)
223
+
224
+
225
+ def fast_sort_key(item: object) -> tuple[int, ...]:
226
+ """
227
+ A faster key for natural sorting of various Python types.
228
+
229
+ - Won't sort strings that are dates very well
230
+ - Won't convert strings to floats
231
+ - Won't sort string file paths very well
232
+ - Will do ok with string version numbers
86
233
 
87
- return (5, item.lower(), tuple(int(match.group()) for match in finditer(r"\d+", item)))
234
+ 0. None
235
+ 1. Empty strings
236
+ 2. bool
237
+ 3. int, float
238
+ 4. datetime
239
+ 5. strings (including paths as POSIX strings) & unknown objects with __str__
240
+ 6. unknown objects
241
+ """
242
+ if isinstance(item, str):
243
+ if item:
244
+ return (5, tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", item)))
245
+ else:
246
+ return (1, item)
247
+
248
+ elif item is None:
249
+ return (0,)
250
+
251
+ elif isinstance(item, bool):
252
+ return (2, item)
253
+
254
+ elif isinstance(item, (int, float)):
255
+ return (3, item)
256
+
257
+ elif isinstance(item, datetime):
258
+ return (4, item.timestamp())
88
259
 
89
260
  elif isinstance(item, Path):
90
- posix_str = item.as_posix()
91
- return (5, posix_str.lower(), tuple(int(match.group()) for match in finditer(r"\d+", posix_str)))
261
+ return _string_fallback(item.as_posix())
92
262
 
93
263
  else:
94
264
  try:
95
- return (6, f"{item}".lower())
265
+ return _string_fallback(f"{item}")
96
266
  except Exception:
97
- return (7, item)
267
+ return (6, item)
98
268
 
99
269
 
100
270
  def sort_selection(
@@ -247,14 +417,15 @@ def sort_tree_view(
247
417
  _row_index: list[object],
248
418
  tree_rns: dict[str, int],
249
419
  tree: dict[str, object],
250
- key: Callable = natural_sort_key,
420
+ key: Callable | None = None,
251
421
  reverse: bool = False,
252
422
  ) -> tuple[list[object], dict[int, int]]:
253
423
  if not _row_index or not tree_rns or not tree:
254
424
  return [], {}
255
425
 
256
426
  if key is None:
257
- key = natural_sort_key
427
+ # prefer version_sort_key for iid names
428
+ key = version_sort_key
258
429
 
259
430
  # Create the index map and sorted nodes list
260
431
  mapping = {}
@@ -281,38 +452,80 @@ def sort_tree_view(
281
452
  return sorted_nodes, mapping
282
453
 
283
454
 
284
- # def test_natural_sort_key():
285
- # test_items = [
286
- # None,
287
- # False,
288
- # True,
289
- # 5,
290
- # 3.14,
291
- # datetime(2023, 1, 1),
292
- # "abc123",
293
- # "123abc",
294
- # "abc123def",
295
- # "998zzz",
296
- # "10-01-2023",
297
- # "01-10-2023",
298
- # "fi1le_0.txt",
299
- # "file_2.txt",
300
- # "file_10.txt",
301
- # "file_1.txt",
302
- # "path/to/file_2.txt",
303
- # "path/to/file_10.txt",
304
- # "path/to/file_1.txt",
305
- # "/another/path/file_2.log",
306
- # "/another/path/file_10.log",
307
- # "/another/path/file_1.log",
308
- # "C:\\Windows\\System32\\file_2.dll",
309
- # "C:\\Windows\\System32\\file_10.dll",
310
- # "C:\\Windows\\System32\\file_1.dll",
311
- # ]
312
- # print("Sort objects:", [natural_sort_key(e) for e in test_items])
313
- # sorted_items = sorted(test_items, key=natural_sort_key)
314
- # print("\nNatural Sort Order:", sorted_items)
315
-
316
-
317
455
  # if __name__ == "__main__":
318
- # test_natural_sort_key()
456
+ # test_cases = {
457
+ # "Filenames": ["file10.txt", "file2.txt", "file1.txt"],
458
+ # "Versions": ["1.10", "1.2", "1.9", "1.21"],
459
+ # "Mixed": ["z1.doc", "z10.doc", "z2.doc", "z100.doc"],
460
+ # "Paths": [
461
+ # "/folder/file.txt",
462
+ # "/folder/file (1).txt",
463
+ # "/folder (1)/file.txt",
464
+ # "/folder (10)/file.txt",
465
+ # "/dir/subdir/file1.txt",
466
+ # "/dir/subdir/file10.txt",
467
+ # "/dir/subdir/file2.txt",
468
+ # "/dir/file.txt",
469
+ # "/dir/sub/file123.txt",
470
+ # "/dir/sub/file12.txt",
471
+ # # New challenging cases
472
+ # "/a/file.txt",
473
+ # "/a/file1.txt",
474
+ # "/a/b/file.txt",
475
+ # "/a/b/file1.txt",
476
+ # "/x/file-v1.2.txt",
477
+ # "/x/file-v1.10.txt",
478
+ # # My own new challenging cases
479
+ # "/a/zzzzz.txt",
480
+ # "/a/b/a.txt",
481
+ # ],
482
+ # "Case": ["Apple", "banana", "Corn", "apple", "Banana", "corn"],
483
+ # "Leading Zeros": ["001", "010", "009", "100"],
484
+ # "Complex": ["x-1-y-10", "x-1-y-2", "x-2-y-1", "x-10-y-1"],
485
+ # "Lengths": ["2 ft 9 in", "2 ft 10 in", "10 ft 1 in", "10 ft 2 in"],
486
+ # "Floats": [
487
+ # "1.5",
488
+ # "1.25",
489
+ # "1.025",
490
+ # "10.5",
491
+ # "-10.2",
492
+ # "-2.5",
493
+ # "5.7",
494
+ # "-1.25",
495
+ # "0.0",
496
+ # "1.5e3",
497
+ # "2.5e2",
498
+ # "1.23e4",
499
+ # "1e-2",
500
+ # "file1.2.txt",
501
+ # "file1.5.txt",
502
+ # "file1.10.txt",
503
+ # ],
504
+ # "Strings": [
505
+ # "123abc",
506
+ # "abc123",
507
+ # "123abc456",
508
+ # "abc123def",
509
+ # ],
510
+ # }
511
+
512
+ # print("FAST SORT KEY:")
513
+
514
+ # for name, data in test_cases.items():
515
+ # sorted1 = sorted(data, key=fast_sort_key)
516
+ # print(f"\n{name}:")
517
+ # print(sorted1)
518
+
519
+ # print("\nNATURAL SORT KEY:")
520
+
521
+ # for name, data in test_cases.items():
522
+ # sorted1 = sorted(data, key=natural_sort_key)
523
+ # print(f"\n{name}:")
524
+ # print(sorted1)
525
+
526
+ # print("\nVERSION SORT KEY:")
527
+
528
+ # for name, data in test_cases.items():
529
+ # sorted1 = sorted(data, key=version_sort_key)
530
+ # print(f"\n{name}:")
531
+ # print(sorted1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.3
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
@@ -0,0 +1,22 @@
1
+ tksheet/__init__.py,sha256=DJduXwiEFlPhe0uDqKUZCRGgf9pB-aubUvC_sXCpVp8,2313
2
+ tksheet/colors.py,sha256=dHhmdFuQDlwohDHsAfT9VdrKoSl_R33L72a3HCin5zo,51591
3
+ tksheet/column_headers.py,sha256=Dq3GqcUvBpereMdOTU3OqkfSkCb8nSUM18uHF2Vsrqo,103442
4
+ tksheet/constants.py,sha256=PkvAtdYXSOiOO5zuxqrObvGIVV2sEE0enN8YLhI8zCc,3922
5
+ tksheet/find_window.py,sha256=JfkgpGluSng3bKMBneDNQg-AJmBcmCW7JIhtYbSUZaE,8036
6
+ tksheet/formatters.py,sha256=21ZkMaDIJNUtjvtlAbPl8Y19I9nDxue-JJegw6hblz8,10551
7
+ tksheet/functions.py,sha256=KQe1AncAiVUxvcq9GAJ8TBXKaSAxk5-VDHDY6hHApLk,53183
8
+ tksheet/main_table.py,sha256=jWG9Of249O-bPujDG1umoXZUndM6wIsx8lqNWPqVTz4,358190
9
+ tksheet/other_classes.py,sha256=ADybikLipEG4NABXx8bGVAovJJhWcomQOWTorzS1CPU,16581
10
+ tksheet/row_index.py,sha256=NUpv_cpGDKO_tF4tICcK2BaW1Z0-w8sdE9NYVyHNHO8,133849
11
+ tksheet/sheet.py,sha256=s_9jyRok7AGanMZ9NzQYjkFxlwavjEMSBnOEN3uoJoQ,283724
12
+ tksheet/sheet_options.py,sha256=A_cJEc6XirLO8YrWQHfb10lK2VhGbz28_3XgrKx3TfM,9753
13
+ tksheet/sorting.py,sha256=xBSh_b7QyRRDzwqmKMPEevCZY1KpmMbyA6kl0yvcK6U,16887
14
+ tksheet/text_editor.py,sha256=ZLVF-0WxDin5qUAJ5r7dmsdwvhyEoxw0PlPvi_AGNPE,7328
15
+ tksheet/themes.py,sha256=AoNAxibnQi04MN0Zpbn9-kyDnkiiV8TDNWP9FYjpuf0,18473
16
+ tksheet/tksheet_types.py,sha256=8JQVlA6N9jEZTEAytbcyuhOGuNE4fUPxYhTqoidxEE0,588
17
+ tksheet/top_left_rectangle.py,sha256=KhTT-rBUwQTgaHjSwL83cL5_71k2L1B7gxkSxZlTSK8,8598
18
+ tksheet-7.4.4.dist-info/LICENSE.txt,sha256=ndbcCPe9SlHfweE_W2RAueWUe2k7yudyxYLq6WjFdn4,1101
19
+ tksheet-7.4.4.dist-info/METADATA,sha256=F4df5FisLoL6dkzwUbayE2Djt0fgD1bibXlVOFLaoDM,7839
20
+ tksheet-7.4.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
+ tksheet-7.4.4.dist-info/top_level.txt,sha256=my61PXCcck_HHAc9cq3NAlyAr3A3FXxCy9gptEOaCN8,8
22
+ tksheet-7.4.4.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- tksheet/__init__.py,sha256=6xbeTomYvgJaGjRKmHAS1YW24Tmc5ZcR7_YmeyJvDnY,2280
2
- tksheet/colors.py,sha256=dHhmdFuQDlwohDHsAfT9VdrKoSl_R33L72a3HCin5zo,51591
3
- tksheet/column_headers.py,sha256=ZfHLhXEKe8M9ggf3nzg252DBVH_NRtQichtTF7pisIc,103245
4
- tksheet/constants.py,sha256=PkvAtdYXSOiOO5zuxqrObvGIVV2sEE0enN8YLhI8zCc,3922
5
- tksheet/find_window.py,sha256=JfkgpGluSng3bKMBneDNQg-AJmBcmCW7JIhtYbSUZaE,8036
6
- tksheet/formatters.py,sha256=21ZkMaDIJNUtjvtlAbPl8Y19I9nDxue-JJegw6hblz8,10551
7
- tksheet/functions.py,sha256=BDCCKdOUF9FRDq9GF7HmRXaZ22PYRDySlo6WLvyZtVA,52806
8
- tksheet/main_table.py,sha256=ChMaI_t5rDwfc5pStG5pxKhqWwKnauJGxa_hoTXq478,357778
9
- tksheet/other_classes.py,sha256=ADybikLipEG4NABXx8bGVAovJJhWcomQOWTorzS1CPU,16581
10
- tksheet/row_index.py,sha256=R6tCsfXnPbblnSU1tvse_fCjx2DG0U-vx-XaiAPdZs0,133195
11
- tksheet/sheet.py,sha256=6hHI_W0rmuV6l2C2jd3fa9pgnNx3kW9k6rY0qqEq-Wg,284585
12
- tksheet/sheet_options.py,sha256=7q5T55sZe_elA3MCP0VnbX_f0Euo425aw4uSoPXteoA,9624
13
- tksheet/sorting.py,sha256=qLusdRmqDUBkcEgK3WcYbFjIJmXNBoDhR-D2yrWC-BI,9895
14
- tksheet/text_editor.py,sha256=ZLVF-0WxDin5qUAJ5r7dmsdwvhyEoxw0PlPvi_AGNPE,7328
15
- tksheet/themes.py,sha256=AoNAxibnQi04MN0Zpbn9-kyDnkiiV8TDNWP9FYjpuf0,18473
16
- tksheet/tksheet_types.py,sha256=8JQVlA6N9jEZTEAytbcyuhOGuNE4fUPxYhTqoidxEE0,588
17
- tksheet/top_left_rectangle.py,sha256=KhTT-rBUwQTgaHjSwL83cL5_71k2L1B7gxkSxZlTSK8,8598
18
- tksheet-7.4.3.dist-info/LICENSE.txt,sha256=ndbcCPe9SlHfweE_W2RAueWUe2k7yudyxYLq6WjFdn4,1101
19
- tksheet-7.4.3.dist-info/METADATA,sha256=36JytEf6SmrgwlyWLnvv1SLYumrDvxH4wDERw-cxTXE,7839
20
- tksheet-7.4.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
- tksheet-7.4.3.dist-info/top_level.txt,sha256=my61PXCcck_HHAc9cq3NAlyAr3A3FXxCy9gptEOaCN8,8
22
- tksheet-7.4.3.dist-info/RECORD,,