tksheet 7.4.1__py3-none-any.whl → 7.4.3__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.1"
7
+ __version__ = "7.4.3"
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 natural_sort_key
96
97
  from .text_editor import (
97
98
  TextEditor,
98
99
  TextEditorTkText,
tksheet/column_headers.py CHANGED
@@ -915,14 +915,14 @@ class ColumnHeaders(tk.Canvas):
915
915
  key: Callable | None = None,
916
916
  undo: bool = True,
917
917
  ) -> EventDataDict:
918
- event_data = self.MT.new_event_dict("sort_rows", state=True)
918
+ event_data = self.MT.new_event_dict("move_rows", state=True)
919
919
  if not self.MT.data:
920
920
  return event_data
921
921
  if column is None:
922
922
  if not self.MT.selected:
923
923
  return event_data
924
924
  column = self.MT.selected.column
925
- if try_binding(self.ch_extra_begin_sort_rows_func, event_data, "begin_sort_rows"):
925
+ if try_binding(self.ch_extra_begin_sort_rows_func, event_data, "begin_move_rows"):
926
926
  disp_new_idxs, disp_row_ctr = {}, 0
927
927
  if self.ops.treeview:
928
928
  new_nodes_order, data_new_idxs = sort_tree_view(
@@ -973,7 +973,7 @@ class ColumnHeaders(tk.Canvas):
973
973
  }
974
974
  if undo and self.MT.undo_enabled:
975
975
  self.MT.undo_stack.append(stored_event_dict(event_data))
976
- try_binding(self.ch_extra_end_sort_rows_func, event_data, "end_sort_rows")
976
+ try_binding(self.ch_extra_end_sort_rows_func, event_data, "end_move_rows")
977
977
  self.MT.sheet_modified(event_data)
978
978
  self.PAR.emit_event("<<SheetModified>>", event_data)
979
979
  self.MT.refresh()
tksheet/functions.py CHANGED
@@ -8,6 +8,7 @@ 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
 
@@ -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:
@@ -409,12 +405,22 @@ def push_n(num: int, sorted_seq: Sequence[int]) -> int:
409
405
  if num < sorted_seq[0]:
410
406
  return num
411
407
  else:
412
- for e in sorted_seq:
413
- if num >= e:
414
- num += 1
415
- else:
416
- return num
417
- return num
408
+ if (hi := len(sorted_seq)) < 5:
409
+ for e in sorted_seq:
410
+ if num >= e:
411
+ num += 1
412
+ else:
413
+ return num
414
+ return num
415
+ else:
416
+ lo = 0
417
+ while lo < hi:
418
+ mid = (lo + hi) // 2
419
+ if sorted_seq[mid] < num + mid + 1:
420
+ lo = mid + 1
421
+ else:
422
+ hi = mid
423
+ return num + lo
418
424
 
419
425
 
420
426
  def get_dropdown_kwargs(
@@ -615,14 +621,18 @@ def consecutive_chunks(seq: list[int]) -> Generator[list[int]]:
615
621
 
616
622
 
617
623
  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
624
+ seq_iter = iter(seq)
625
+ try:
626
+ start = next(seq_iter)
627
+ except StopIteration:
628
+ return
629
+ prev = start
630
+ for curr in seq_iter:
631
+ if curr > prev + 1:
632
+ yield start, prev + 1
633
+ start = curr
634
+ prev = curr
635
+ yield start, prev + 1
626
636
 
627
637
 
628
638
  def is_contiguous(iterable: Iterable[int]) -> bool:
tksheet/main_table.py CHANGED
@@ -1973,7 +1973,7 @@ class MainTable(tk.Canvas):
1973
1973
  event_data = self.delete_rows_displayed(
1974
1974
  rows=tuple(reversed(modification["added"]["rows"]["row_heights"])),
1975
1975
  event_data=event_data,
1976
- restored_state=True,
1976
+ from_undo=True,
1977
1977
  )
1978
1978
 
1979
1979
  if modification["added"]["columns"]:
@@ -1985,7 +1985,7 @@ class MainTable(tk.Canvas):
1985
1985
  event_data = self.delete_columns_displayed(
1986
1986
  cols=tuple(reversed(modification["added"]["columns"]["column_widths"])),
1987
1987
  event_data=event_data,
1988
- restored_state=True,
1988
+ from_undo=True,
1989
1989
  )
1990
1990
 
1991
1991
  if modification["deleted"]["rows"] or modification["deleted"]["row_heights"]:
@@ -1998,7 +1998,7 @@ class MainTable(tk.Canvas):
1998
1998
  create_ops=False,
1999
1999
  create_selections=False,
2000
2000
  add_col_positions=False,
2001
- restored_state=True,
2001
+ from_undo=True,
2002
2002
  )
2003
2003
 
2004
2004
  if modification["deleted"]["columns"] or modification["deleted"]["column_widths"]:
@@ -2010,7 +2010,7 @@ class MainTable(tk.Canvas):
2010
2010
  create_ops=False,
2011
2011
  create_selections=False,
2012
2012
  add_row_positions=False,
2013
- restored_state=True,
2013
+ from_undo=True,
2014
2014
  )
2015
2015
 
2016
2016
  if modification["eventname"].startswith(("edit", "move")):
@@ -4893,16 +4893,18 @@ class MainTable(tk.Canvas):
4893
4893
  add_row_positions: bool = True,
4894
4894
  push_ops: bool = True,
4895
4895
  mod_event_boxes: bool = True,
4896
- restored_state: bool = False,
4897
- ) -> EventDataDict:
4896
+ from_undo: bool = False,
4897
+ ) -> EventDataDict | None:
4898
+ if not from_undo and not try_binding(self.extra_begin_insert_cols_rc_func, event_data, "begin_add_columns"):
4899
+ return
4898
4900
  self.saved_column_widths = {}
4899
- if not restored_state and not self.all_columns_displayed:
4901
+ if not from_undo and not self.all_columns_displayed:
4900
4902
  self.displayed_columns = add_to_displayed(self.displayed_columns, columns)
4901
4903
  cws = self.get_column_widths()
4902
4904
  if column_widths and next(reversed(column_widths)) > len(cws):
4903
4905
  for i in reversed(range(len(cws), len(cws) + next(reversed(column_widths)) - len(cws))):
4904
4906
  column_widths[i] = self.PAR.ops.default_column_width
4905
- if not restored_state:
4907
+ if not from_undo:
4906
4908
  self.set_col_positions(
4907
4909
  itr=insert_items(
4908
4910
  cws,
@@ -4928,7 +4930,7 @@ class MainTable(tk.Canvas):
4928
4930
  "index": {},
4929
4931
  "row_heights": {rn: default_height for rn in range(len(self.row_positions) - 1, maxrn + 1)},
4930
4932
  }
4931
- if not restored_state:
4933
+ if not from_undo:
4932
4934
  self.set_row_positions(
4933
4935
  itr=chain(
4934
4936
  self.gen_row_heights(),
@@ -4961,9 +4963,11 @@ class MainTable(tk.Canvas):
4961
4963
  "header": header,
4962
4964
  "column_widths": column_widths,
4963
4965
  }
4966
+ if not from_undo:
4967
+ try_binding(self.extra_end_insert_cols_rc_func, event_data, "end_add_columns")
4964
4968
  return event_data
4965
4969
 
4966
- def rc_add_columns(self, event: object = None):
4970
+ def rc_add_columns(self, event: object = None) -> None:
4967
4971
  rowlen = self.equalize_data_row_lengths()
4968
4972
  selcols = sorted(self.get_selected_cols())
4969
4973
  if (
@@ -4997,8 +5001,6 @@ class MainTable(tk.Canvas):
4997
5001
  if numcols < 1:
4998
5002
  return
4999
5003
  event_data = self.new_event_dict("add_columns", state=True)
5000
- if not try_binding(self.extra_begin_insert_cols_rc_func, event_data, "begin_add_columns"):
5001
- return
5002
5004
  columns, headers, widths = self.get_args_for_add_columns(data_ins_col, displayed_ins_col, numcols)
5003
5005
  event_data = self.add_columns(
5004
5006
  columns=columns,
@@ -5006,11 +5008,11 @@ class MainTable(tk.Canvas):
5006
5008
  column_widths=widths,
5007
5009
  event_data=event_data,
5008
5010
  )
5009
- if self.undo_enabled:
5010
- self.undo_stack.append(stored_event_dict(event_data))
5011
- self.refresh()
5012
- try_binding(self.extra_end_insert_cols_rc_func, event_data, "end_add_columns")
5013
- self.sheet_modified(event_data)
5011
+ if event_data:
5012
+ if self.undo_enabled:
5013
+ self.undo_stack.append(stored_event_dict(event_data))
5014
+ self.refresh()
5015
+ self.sheet_modified(event_data)
5014
5016
 
5015
5017
  def add_rows(
5016
5018
  self,
@@ -5024,17 +5026,19 @@ class MainTable(tk.Canvas):
5024
5026
  push_ops: bool = True,
5025
5027
  tree: bool = True,
5026
5028
  mod_event_boxes: bool = True,
5027
- restored_state: bool = False,
5028
- ) -> EventDataDict:
5029
+ from_undo: bool = False,
5030
+ ) -> EventDataDict | None:
5031
+ if not from_undo and not try_binding(self.extra_begin_insert_rows_rc_func, event_data, "begin_add_rows"):
5032
+ return
5029
5033
  self.saved_row_heights = {}
5030
- if not restored_state and not self.all_rows_displayed:
5034
+ if not from_undo and not self.all_rows_displayed:
5031
5035
  self.displayed_rows = add_to_displayed(self.displayed_rows, rows)
5032
5036
  rhs = self.get_row_heights()
5033
5037
  if row_heights and next(reversed(row_heights)) > len(rhs):
5034
5038
  default_row_height = self.get_default_row_height()
5035
5039
  for i in reversed(range(len(rhs), len(rhs) + next(reversed(row_heights)) - len(rhs))):
5036
5040
  row_heights[i] = default_row_height
5037
- if not restored_state:
5041
+ if not from_undo:
5038
5042
  self.set_row_positions(
5039
5043
  itr=insert_items(
5040
5044
  rhs,
@@ -5060,7 +5064,7 @@ class MainTable(tk.Canvas):
5060
5064
  "header": {},
5061
5065
  "column_widths": {cn: default_width for cn in range(len(self.col_positions) - 1, maxcn + 1)},
5062
5066
  }
5063
- if not restored_state:
5067
+ if not from_undo:
5064
5068
  self.set_col_positions(
5065
5069
  itr=chain(
5066
5070
  self.gen_column_widths(),
@@ -5093,9 +5097,11 @@ class MainTable(tk.Canvas):
5093
5097
  }
5094
5098
  if tree and self.PAR.ops.treeview:
5095
5099
  event_data = self.RI.tree_add_rows(event_data=event_data)
5100
+ if not from_undo:
5101
+ try_binding(self.extra_end_insert_rows_rc_func, event_data, "end_add_rows")
5096
5102
  return event_data
5097
5103
 
5098
- def rc_add_rows(self, event: object = None):
5104
+ def rc_add_rows(self, event: object = None) -> None:
5099
5105
  total_data_rows = self.total_data_rows()
5100
5106
  selrows = sorted(self.get_selected_rows())
5101
5107
  if (
@@ -5129,8 +5135,6 @@ class MainTable(tk.Canvas):
5129
5135
  if numrows < 1:
5130
5136
  return
5131
5137
  event_data = self.new_event_dict("add_rows", state=True)
5132
- if not try_binding(self.extra_begin_insert_rows_rc_func, event_data, "begin_add_rows"):
5133
- return
5134
5138
  rows, index, heights = self.get_args_for_add_rows(data_ins_row, displayed_ins_row, numrows)
5135
5139
  event_data = self.add_rows(
5136
5140
  rows=rows,
@@ -5138,11 +5142,11 @@ class MainTable(tk.Canvas):
5138
5142
  row_heights=heights,
5139
5143
  event_data=event_data,
5140
5144
  )
5141
- if self.undo_enabled:
5142
- self.undo_stack.append(stored_event_dict(event_data))
5143
- self.refresh()
5144
- try_binding(self.extra_end_insert_rows_rc_func, event_data, "end_add_rows")
5145
- self.sheet_modified(event_data)
5145
+ if event_data:
5146
+ if self.undo_enabled:
5147
+ self.undo_stack.append(stored_event_dict(event_data))
5148
+ self.refresh()
5149
+ self.sheet_modified(event_data)
5146
5150
 
5147
5151
  def get_args_for_add_columns(
5148
5152
  self,
@@ -5311,7 +5315,7 @@ class MainTable(tk.Canvas):
5311
5315
  self,
5312
5316
  cols: list[int],
5313
5317
  event_data: EventDataDict | None = None,
5314
- restored_state: bool = False,
5318
+ from_undo: bool = False,
5315
5319
  ) -> EventDataDict:
5316
5320
  if not event_data:
5317
5321
  event_data = self.new_event_dict("delete_columns", state=True)
@@ -5320,7 +5324,7 @@ class MainTable(tk.Canvas):
5320
5324
  for c in reversed(cols):
5321
5325
  if len(self.col_positions) > c + 1:
5322
5326
  event_data["deleted"]["column_widths"][c] = self.col_positions[c + 1] - self.col_positions[c]
5323
- if not restored_state:
5327
+ if not from_undo:
5324
5328
  cols_set = set(cols)
5325
5329
  self.set_col_positions(
5326
5330
  itr=(width for c, width in enumerate(self.gen_column_widths()) if c not in cols_set)
@@ -5334,13 +5338,12 @@ class MainTable(tk.Canvas):
5334
5338
  data_indexes: bool = False,
5335
5339
  undo: bool = True,
5336
5340
  emit_event: bool = True,
5337
- ext: bool = False,
5338
5341
  ) -> EventDataDict:
5339
5342
  event_data = self.new_event_dict("delete_columns", state=True)
5340
5343
  if not columns:
5341
5344
  if not (columns := sorted(self.get_selected_cols())):
5342
5345
  return event_data
5343
- if not ext and not try_binding(self.extra_begin_del_cols_rc_func, event_data, "begin_delete_columns"):
5346
+ if not try_binding(self.extra_begin_del_cols_rc_func, event_data, "begin_delete_columns"):
5344
5347
  return
5345
5348
  if self.all_columns_displayed:
5346
5349
  data_columns = columns
@@ -5362,8 +5365,7 @@ class MainTable(tk.Canvas):
5362
5365
  )
5363
5366
  if undo and self.undo_enabled:
5364
5367
  self.undo_stack.append(stored_event_dict(event_data))
5365
- if not ext:
5366
- try_binding(self.extra_end_del_cols_rc_func, event_data, "end_delete_columns")
5368
+ try_binding(self.extra_end_del_cols_rc_func, event_data, "end_delete_columns")
5367
5369
  if emit_event:
5368
5370
  self.sheet_modified(event_data)
5369
5371
  self.deselect("all")
@@ -5400,7 +5402,7 @@ class MainTable(tk.Canvas):
5400
5402
  self,
5401
5403
  rows: list[int],
5402
5404
  event_data: EventDataDict | None = None,
5403
- restored_state: bool = False,
5405
+ from_undo: bool = False,
5404
5406
  ) -> EventDataDict:
5405
5407
  if not event_data:
5406
5408
  event_data = self.new_event_dict("delete_rows", state=True)
@@ -5409,7 +5411,7 @@ class MainTable(tk.Canvas):
5409
5411
  for r in reversed(rows):
5410
5412
  if len(self.row_positions) > r + 1:
5411
5413
  event_data["deleted"]["row_heights"][r] = self.row_positions[r + 1] - self.row_positions[r]
5412
- if not restored_state:
5414
+ if not from_undo:
5413
5415
  rows_set = set(rows)
5414
5416
  self.set_row_positions(
5415
5417
  itr=(height for r, height in enumerate(self.gen_row_heights()) if r not in rows_set)
@@ -5423,13 +5425,12 @@ class MainTable(tk.Canvas):
5423
5425
  data_indexes: bool = False,
5424
5426
  undo: bool = True,
5425
5427
  emit_event: bool = True,
5426
- ext: bool = False,
5427
5428
  ) -> EventDataDict:
5428
5429
  event_data = self.new_event_dict("delete_rows", state=True)
5429
5430
  if not rows:
5430
5431
  if not (rows := sorted(self.get_selected_rows())):
5431
5432
  return
5432
- if not ext and not try_binding(self.extra_begin_del_rows_rc_func, event_data, "begin_delete_rows"):
5433
+ if not try_binding(self.extra_begin_del_rows_rc_func, event_data, "begin_delete_rows"):
5433
5434
  return
5434
5435
  if self.all_rows_displayed:
5435
5436
  data_rows = rows
@@ -5462,8 +5463,7 @@ class MainTable(tk.Canvas):
5462
5463
  )
5463
5464
  if undo and self.undo_enabled:
5464
5465
  self.undo_stack.append(stored_event_dict(event_data))
5465
- if not ext:
5466
- try_binding(self.extra_end_del_rows_rc_func, event_data, "end_delete_rows")
5466
+ try_binding(self.extra_end_del_rows_rc_func, event_data, "end_delete_rows")
5467
5467
  if emit_event:
5468
5468
  self.sheet_modified(event_data)
5469
5469
  self.deselect("all")
tksheet/row_index.py CHANGED
@@ -19,7 +19,6 @@ from .constants import (
19
19
  )
20
20
  from .formatters import is_bool_like, try_to_bool
21
21
  from .functions import (
22
- consecutive_chunks,
23
22
  consecutive_ranges,
24
23
  del_placeholder_dict_key,
25
24
  event_dict,
@@ -686,10 +685,10 @@ class RowIndex(tk.Canvas):
686
685
  tag="move_rows",
687
686
  )
688
687
  self.MT.create_resize_line(x1, ypos, x2, ypos, width=3, fill=self.ops.drag_and_drop_bg, tag="move_rows")
689
- for chunk in consecutive_chunks(rows):
688
+ for boxst, boxend in consecutive_ranges(rows):
690
689
  self.MT.show_ctrl_outline(
691
- start_cell=(0, chunk[0]),
692
- end_cell=(len(self.MT.col_positions) - 1, chunk[-1] + 1),
690
+ start_cell=(0, boxst),
691
+ end_cell=(len(self.MT.col_positions) - 1, boxend),
693
692
  dash=(),
694
693
  outline=self.ops.drag_and_drop_bg,
695
694
  delete_on_timer=False,
@@ -936,14 +935,14 @@ class RowIndex(tk.Canvas):
936
935
  key: Callable | None = None,
937
936
  undo: bool = True,
938
937
  ) -> EventDataDict:
939
- event_data = self.MT.new_event_dict("sort_columns", state=True)
938
+ event_data = self.MT.new_event_dict("move_columns", state=True)
940
939
  if not self.MT.data:
941
940
  return event_data
942
941
  if row is None:
943
942
  if not self.MT.selected:
944
943
  return event_data
945
944
  row = self.MT.selected.row
946
- if try_binding(self.ri_extra_begin_sort_cols_func, event_data, "begin_sort_columns"):
945
+ if try_binding(self.ri_extra_begin_sort_cols_func, event_data, "begin_move_columns"):
947
946
  sorted_indices, data_new_idxs = sort_columns_by_row(self.MT.data, row=row, reverse=reverse, key=key)
948
947
  disp_new_idxs = {}
949
948
  if self.MT.all_columns_displayed:
@@ -970,7 +969,7 @@ class RowIndex(tk.Canvas):
970
969
  }
971
970
  if undo and self.MT.undo_enabled:
972
971
  self.MT.undo_stack.append(stored_event_dict(event_data))
973
- try_binding(self.ri_extra_end_sort_cols_func, event_data, "end_sort_columns")
972
+ try_binding(self.ri_extra_end_sort_cols_func, event_data, "end_move_columns")
974
973
  self.MT.sheet_modified(event_data)
975
974
  self.PAR.emit_event("<<SheetModified>>", event_data)
976
975
  self.MT.refresh()
tksheet/sheet.py CHANGED
@@ -84,7 +84,7 @@ class Sheet(tk.Frame):
84
84
  parent: tk.Misc,
85
85
  name: str = "!sheet",
86
86
  show_table: bool = True,
87
- show_top_left: bool = False,
87
+ show_top_left: bool | None = None,
88
88
  show_row_index: bool = True,
89
89
  show_header: bool = True,
90
90
  show_x_scrollbar: bool = True,
@@ -429,14 +429,18 @@ class Sheet(tk.Frame):
429
429
  orient="vertical",
430
430
  style=f"Sheet{self.unique_id}.Vertical.TScrollbar",
431
431
  )
432
+ self.MT["yscrollcommand"] = self.yscroll.set
433
+ self.RI["yscrollcommand"] = self.yscroll.set
432
434
  self.xscroll = ttk.Scrollbar(
433
435
  self,
434
436
  command=self.MT._xscrollbar,
435
437
  orient="horizontal",
436
438
  style=f"Sheet{self.unique_id}.Horizontal.TScrollbar",
437
439
  )
440
+ self.MT["xscrollcommand"] = self.xscroll.set
441
+ self.CH["xscrollcommand"] = self.xscroll.set
438
442
  self.show()
439
- if not show_top_left and not (show_row_index and show_header):
443
+ if show_top_left is False or (show_top_left is None and (not show_row_index or not show_header)):
440
444
  self.hide("top_left")
441
445
  if not show_row_index:
442
446
  self.hide("row_index")
@@ -728,9 +732,9 @@ class Sheet(tk.Frame):
728
732
  - "begin_rc_delete_column", "begin_delete_columns"
729
733
  - "rc_delete_column", "end_rc_delete_column","end_delete_columns", "delete_columns"
730
734
  - "begin_rc_insert_column", "begin_insert_column", "begin_insert_columns", "begin_add_column","begin_rc_add_column", "begin_add_columns"
731
- - "rc_insert_column", "end_rc_insert_column", "end_insert_column", "end_insert_columns", "rc_add_column", "end_rc_add_column", "end_add_column", "end_add_columns"
735
+ - "rc_insert_column", "end_rc_insert_column", "end_insert_column", "end_insert_columns", "rc_add_column", "end_rc_add_column", "end_add_column", "end_add_columns", "add_columns"
732
736
  - "begin_rc_insert_row", "begin_insert_row", "begin_insert_rows", "begin_rc_add_row", "begin_add_row", "begin_add_rows"
733
- - "rc_insert_row", "end_rc_insert_row", "end_insert_row", "end_insert_rows", "rc_add_row", "end_rc_add_row", "end_add_row", "end_add_rows"
737
+ - "rc_insert_row", "end_rc_insert_row", "end_insert_row", "end_insert_rows", "rc_add_row", "end_rc_add_row", "end_add_row", "end_add_rows", "add_rows"
734
738
  - "row_height_resize"
735
739
  - "column_width_resize"
736
740
  - "cell_select"
@@ -1031,6 +1035,7 @@ class Sheet(tk.Frame):
1031
1035
  "end_rc_add_column",
1032
1036
  "end_add_column",
1033
1037
  "end_add_columns",
1038
+ "add_columns",
1034
1039
  ):
1035
1040
  self.MT.extra_end_insert_cols_rc_func = f
1036
1041
 
@@ -1052,6 +1057,7 @@ class Sheet(tk.Frame):
1052
1057
  "end_rc_add_row",
1053
1058
  "end_add_row",
1054
1059
  "end_add_rows",
1060
+ "add_rows",
1055
1061
  ):
1056
1062
  self.MT.extra_end_insert_rows_rc_func = f
1057
1063
 
@@ -2328,7 +2334,6 @@ class Sheet(tk.Frame):
2328
2334
  data_indexes=data_indexes,
2329
2335
  undo=undo,
2330
2336
  emit_event=emit_event,
2331
- ext=True,
2332
2337
  )
2333
2338
  self.set_refresh_timer(redraw)
2334
2339
  return event_data
@@ -2350,7 +2355,6 @@ class Sheet(tk.Frame):
2350
2355
  data_indexes=data_indexes,
2351
2356
  undo=undo,
2352
2357
  emit_event=emit_event,
2353
- ext=True,
2354
2358
  )
2355
2359
  self.set_refresh_timer(redraw)
2356
2360
  return event_data
@@ -4365,19 +4369,13 @@ class Sheet(tk.Frame):
4365
4369
  self.MT.grid(row=1, column=1, sticky="nswe")
4366
4370
  if canvas in ("all", "row_index", "index"):
4367
4371
  self.RI.grid(row=1, column=0, sticky="nswe")
4368
- self.MT["yscrollcommand"] = self.yscroll.set
4369
- self.RI["yscrollcommand"] = self.yscroll.set
4370
4372
  self.MT.show_index = True
4371
- if self.MT.show_header:
4372
- self.show("top_left")
4373
4373
  if canvas in ("all", "header"):
4374
4374
  self.CH.grid(row=0, column=1, sticky="nswe")
4375
- self.MT["xscrollcommand"] = self.xscroll.set
4376
- self.CH["xscrollcommand"] = self.xscroll.set
4377
4375
  self.MT.show_header = True
4378
- if self.MT.show_index:
4379
- self.show("top_left")
4380
- if canvas in ("all", "top_left"):
4376
+ if canvas in ("all", "top_left") or (
4377
+ self.ops.show_top_left is not False and self.MT.show_header and self.MT.show_index
4378
+ ):
4381
4379
  self.TL.grid(row=0, column=0)
4382
4380
  if canvas in ("all", "x_scrollbar"):
4383
4381
  self.xscroll.grid(row=2, column=0, columnspan=2, sticky="nswe")
@@ -4405,17 +4403,13 @@ class Sheet(tk.Frame):
4405
4403
  ) -> Sheet:
4406
4404
  if canvas in ("all", "row_index"):
4407
4405
  self.RI.grid_remove()
4408
- self.RI["yscrollcommand"] = 0
4409
4406
  self.MT.show_index = False
4410
- if not self.ops.show_top_left:
4411
- self.hide("top_left")
4412
4407
  if canvas in ("all", "header"):
4413
4408
  self.CH.grid_remove()
4414
- self.CH["xscrollcommand"] = 0
4415
4409
  self.MT.show_header = False
4416
- if not self.ops.show_top_left:
4417
- self.hide("top_left")
4418
- if canvas in ("all", "top_left"):
4410
+ if canvas in ("all", "top_left") or (
4411
+ not self.ops.show_top_left and (not self.MT.show_index or not self.MT.show_header)
4412
+ ):
4419
4413
  self.TL.grid_remove()
4420
4414
  if canvas in ("all", "x_scrollbar"):
4421
4415
  self.xscroll.grid_remove()
@@ -7241,7 +7235,7 @@ class Dropdown(Sheet):
7241
7235
 
7242
7236
  def search_and_see(self, event: object = None) -> str:
7243
7237
  if self.search_function is not None:
7244
- rn = self.search_function(search_for=rf"{event['value']}".lower(), data=self.MT.data)
7238
+ rn = self.search_function(search_for=rf"{event['value']}", data=(r[0] for r in self.MT.data))
7245
7239
  if isinstance(rn, int):
7246
7240
  self.row = rn
7247
7241
  self.see(self.row, 0, redraw=False)
tksheet/sheet_options.py CHANGED
@@ -254,6 +254,6 @@ def new_sheet_options() -> DotDict:
254
254
  "max_header_height": float("inf"),
255
255
  "max_row_height": float("inf"),
256
256
  "max_index_width": float("inf"),
257
- "show_top_left": False,
257
+ "show_top_left": None,
258
258
  }
259
259
  )
tksheet/sorting.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Callable, Generator
3
+ from collections.abc import Callable, Generator, Iterable, Iterator
4
4
  from datetime import datetime
5
+ from pathlib import Path
5
6
  from re import finditer
6
7
 
7
- from .other_classes import Node
8
- from .tksheet_types import AnyIter
8
+ AnyIter = Iterable | Iterator
9
9
 
10
10
  # Possible date formats to try for the entire string
11
11
  date_formats = [
@@ -45,24 +45,18 @@ date_formats = [
45
45
  ]
46
46
 
47
47
 
48
- def natural_sort_key(item: object) -> tuple[int, object]:
48
+ def natural_sort_key(item: object) -> tuple[int, ...]:
49
49
  """
50
- A key function for natural sorting that handles various Python types, including
51
- date-like strings in multiple formats.
52
-
53
- This function aims to sort elements in a human-readable order:
54
- - None values first
55
- - Booleans (False before True)
56
- - Numbers (integers, floats combined)
57
- - Datetime objects
58
- - Strings with natural sorting for embedded numbers and dates
59
- - Unknown types treated as strings or left at the end
60
-
61
- Args:
62
- item: Any Python object to be sorted.
63
-
64
- Returns:
65
- A tuple or value that can be used for sorting.
50
+ A key for natural sorting of various Python types.
51
+
52
+ 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
66
60
  """
67
61
  if item is None:
68
62
  return (0,)
@@ -77,26 +71,26 @@ def natural_sort_key(item: object) -> tuple[int, object]:
77
71
  return (3, item.timestamp())
78
72
 
79
73
  elif isinstance(item, str):
74
+ if not item:
75
+ return (4, item)
76
+
80
77
  for date_format in date_formats:
81
78
  try:
82
79
  return (3, datetime.strptime(item, date_format).timestamp())
83
80
  except ValueError:
84
81
  continue
85
-
86
82
  try:
87
83
  return (2, float(item))
88
84
  except Exception:
89
- n = []
90
- s = []
91
- for match in finditer(r"\d+|[^\d\s]+", item):
92
- if (m := match.group()).isdigit():
93
- n.append(int(m))
94
- else:
95
- s.append(m.lower())
96
- return (5, s, n)
85
+ pass
86
+
87
+ return (5, item.lower(), tuple(int(match.group()) for match in finditer(r"\d+", item)))
88
+
89
+ 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)))
97
92
 
98
93
  else:
99
- # For unknown types, attempt to convert to string, or place at end
100
94
  try:
101
95
  return (6, f"{item}".lower())
102
96
  except Exception:
@@ -233,11 +227,11 @@ def sort_columns_by_row(
233
227
 
234
228
 
235
229
  def _sort_node_children(
236
- node: Node,
237
- tree: dict[str, Node],
230
+ node: object,
231
+ tree: dict[str, object],
238
232
  reverse: bool,
239
233
  key: Callable,
240
- ) -> Generator[Node, None, None]:
234
+ ) -> Generator[object, None, None]:
241
235
  sorted_children = sorted(
242
236
  (tree[child_iid] for child_iid in node.children if child_iid in tree),
243
237
  key=lambda child: key(child.text),
@@ -250,12 +244,12 @@ def _sort_node_children(
250
244
 
251
245
 
252
246
  def sort_tree_view(
253
- _row_index: list[Node],
247
+ _row_index: list[object],
254
248
  tree_rns: dict[str, int],
255
- tree: dict[str, Node],
249
+ tree: dict[str, object],
256
250
  key: Callable = natural_sort_key,
257
251
  reverse: bool = False,
258
- ) -> tuple[list[Node], dict[int, int]]:
252
+ ) -> tuple[list[object], dict[int, int]]:
259
253
  if not _row_index or not tree_rns or not tree:
260
254
  return [], {}
261
255
 
@@ -285,3 +279,40 @@ def sort_tree_view(
285
279
  new_index += 1
286
280
 
287
281
  return sorted_nodes, mapping
282
+
283
+
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
+ # if __name__ == "__main__":
318
+ # test_natural_sort_key()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.1
3
+ Version: 7.4.3
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
@@ -77,7 +77,7 @@ License-File: LICENSE.txt
77
77
  <td align="right" colspan="2"><a href="https://github.com/ragardner/tksheet/wiki/Version-7#issues">Issues</a></td>
78
78
  </tr>
79
79
  <tr>
80
- <td align="right" colspan="2"><a href="https://github.com/ragardner/tksheet/wiki/Version-7#enhancements-or-suggestions">Suggestions</a></td>
80
+ <td align="right" colspan="2"><a href="https://github.com/ragardner/tksheet/wiki/Version-7#enhancements-or-suggestions">Suggestions and Contributors</a></td>
81
81
  </tr>
82
82
  </tbody>
83
83
  </table>
@@ -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:
@@ -0,0 +1,22 @@
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,,
@@ -1,22 +0,0 @@
1
- tksheet/__init__.py,sha256=fR2C-xu9T3nD65tI8sKGFUjapge6Nev2Uy1n_K86b_4,2241
2
- tksheet/colors.py,sha256=dHhmdFuQDlwohDHsAfT9VdrKoSl_R33L72a3HCin5zo,51591
3
- tksheet/column_headers.py,sha256=kWCK1fTf0poj5vilqxh99r6gJ4-bBaIJfB-oNbH7oE8,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=tVwMLWsW4n5M-RCL8DrLMOXygDhBP26wz4nEQq1gkUo,52734
8
- tksheet/main_table.py,sha256=mK74o6VlN8EdM6iJt3XyLlJc4DvNGENyjmDJcNEIcX0,357780
9
- tksheet/other_classes.py,sha256=ADybikLipEG4NABXx8bGVAovJJhWcomQOWTorzS1CPU,16581
10
- tksheet/row_index.py,sha256=F64DrvSBmq89JVhYomSRzyQtS4nWAeWDcqnPL8cgsGI,133222
11
- tksheet/sheet.py,sha256=2_fMNnJqwvEkYsrDuyxFC68l6XZROzZVjG2FrSZc7IU,284689
12
- tksheet/sheet_options.py,sha256=RAQX9NF8Bvauj4fHW-8TGtZG6cRVSzwGxSUKplg3c28,9625
13
- tksheet/sorting.py,sha256=ttjPPJwbYKaiQ_uNDU8hXjPiGkCVeGp2Hfd6SWYIN8w,9181
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.1.dist-info/LICENSE.txt,sha256=ndbcCPe9SlHfweE_W2RAueWUe2k7yudyxYLq6WjFdn4,1101
19
- tksheet-7.4.1.dist-info/METADATA,sha256=bI-Stt1T78UK-_qsUjpFpWxsOHNwULQvAsMadKxP8ak,7717
20
- tksheet-7.4.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
- tksheet-7.4.1.dist-info/top_level.txt,sha256=my61PXCcck_HHAc9cq3NAlyAr3A3FXxCy9gptEOaCN8,8
22
- tksheet-7.4.1.dist-info/RECORD,,