tksheet 7.4.2__tar.gz → 7.4.3__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 (26) hide show
  1. {tksheet-7.4.2/tksheet.egg-info → tksheet-7.4.3}/PKG-INFO +3 -1
  2. {tksheet-7.4.2 → tksheet-7.4.3}/README.md +2 -0
  3. {tksheet-7.4.2 → tksheet-7.4.3}/pyproject.toml +1 -1
  4. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/__init__.py +2 -1
  5. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/functions.py +61 -51
  6. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/main_table.py +42 -42
  7. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/row_index.py +3 -4
  8. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/sheet.py +17 -23
  9. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/sheet_options.py +1 -1
  10. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/sorting.py +24 -41
  11. {tksheet-7.4.2 → tksheet-7.4.3/tksheet.egg-info}/PKG-INFO +3 -1
  12. {tksheet-7.4.2 → tksheet-7.4.3}/LICENSE.txt +0 -0
  13. {tksheet-7.4.2 → tksheet-7.4.3}/setup.cfg +0 -0
  14. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/colors.py +0 -0
  15. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/column_headers.py +0 -0
  16. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/constants.py +0 -0
  17. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/find_window.py +0 -0
  18. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/formatters.py +0 -0
  19. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/other_classes.py +0 -0
  20. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/text_editor.py +0 -0
  21. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/themes.py +0 -0
  22. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/tksheet_types.py +0 -0
  23. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet/top_left_rectangle.py +0 -0
  24. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet.egg-info/SOURCES.txt +0 -0
  25. {tksheet-7.4.2 → tksheet-7.4.3}/tksheet.egg-info/dependency_links.txt +0 -0
  26. {tksheet-7.4.2 → tksheet-7.4.3}/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.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
@@ -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.3"
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.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,
@@ -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:
@@ -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")
@@ -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,
@@ -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)
@@ -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
  )
@@ -45,27 +45,18 @@ date_formats = [
45
45
  ]
46
46
 
47
47
 
48
- def natural_sort_key(
49
- item: object,
50
- ) -> tuple[int, ...]:
48
+ def natural_sort_key(item: object) -> tuple[int, ...]:
51
49
  """
52
50
  A key for natural sorting of various Python types.
53
51
 
54
- This function returns a tuple where the first element is an integer
55
- ranking the type, followed by type-specific comparison values.
56
-
57
- 1. None
58
- 2. bool
59
- 3. int, float
60
- 4. datetime
61
- 5. filepaths
62
- 6. empty strings
63
- 7. strings
64
- 8. unknown objects with __str__ method
65
- 9. unknown objects
66
-
67
- :param item: Any Python object to be sorted.
68
- :return: A tuple for sorting, with type indicator and comparison values.
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
69
60
  """
70
61
  if item is None:
71
62
  return (0,)
@@ -79,39 +70,31 @@ def natural_sort_key(
79
70
  elif isinstance(item, datetime):
80
71
  return (3, item.timestamp())
81
72
 
82
- elif isinstance(item, Path):
83
- return (4, item.as_posix())
84
-
85
73
  elif isinstance(item, str):
86
74
  if not item:
87
- return (5, item)
88
-
89
- else:
90
- for date_format in date_formats:
91
- try:
92
- return (3, datetime.strptime(item, date_format).timestamp())
93
- except ValueError:
94
- continue
75
+ return (4, item)
95
76
 
77
+ for date_format in date_formats:
96
78
  try:
97
- return (2, float(item))
98
- except Exception:
99
- pass
79
+ return (3, datetime.strptime(item, date_format).timestamp())
80
+ except ValueError:
81
+ continue
82
+ try:
83
+ return (2, float(item))
84
+ except Exception:
85
+ pass
100
86
 
101
- if "/" in item or "\\" in item:
102
- try:
103
- return (4, Path(item).as_posix())
104
- except Exception:
105
- pass
87
+ return (5, item.lower(), tuple(int(match.group()) for match in finditer(r"\d+", item)))
106
88
 
107
- return (6, item.lower(), tuple(int(match.group()) for match in finditer(r"\d+", item)))
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)))
108
92
 
109
- # For unknown types, attempt to convert to string, or place at end
110
93
  else:
111
94
  try:
112
- return (7, f"{item}".lower())
95
+ return (6, f"{item}".lower())
113
96
  except Exception:
114
- return (8, item)
97
+ return (7, item)
115
98
 
116
99
 
117
100
  def sort_selection(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.2
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
@@ -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:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes