tksheet 7.2.3__tar.gz → 7.2.5__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 (24) hide show
  1. {tksheet-7.2.3/tksheet.egg-info → tksheet-7.2.5}/PKG-INFO +3 -3
  2. {tksheet-7.2.3 → tksheet-7.2.5}/README.md +2 -2
  3. {tksheet-7.2.3 → tksheet-7.2.5}/pyproject.toml +1 -1
  4. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/__init__.py +2 -1
  5. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/column_headers.py +1 -1
  6. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/functions.py +7 -12
  7. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/main_table.py +79 -35
  8. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/other_classes.py +28 -1
  9. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/row_index.py +9 -1
  10. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/sheet.py +167 -59
  11. {tksheet-7.2.3 → tksheet-7.2.5/tksheet.egg-info}/PKG-INFO +3 -3
  12. {tksheet-7.2.3 → tksheet-7.2.5}/LICENSE.txt +0 -0
  13. {tksheet-7.2.3 → tksheet-7.2.5}/setup.cfg +0 -0
  14. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/colors.py +0 -0
  15. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/formatters.py +0 -0
  16. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/sheet_options.py +0 -0
  17. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/text_editor.py +0 -0
  18. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/themes.py +0 -0
  19. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/top_left_rectangle.py +0 -0
  20. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/types.py +0 -0
  21. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet/vars.py +0 -0
  22. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet.egg-info/SOURCES.txt +0 -0
  23. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet.egg-info/dependency_links.txt +0 -0
  24. {tksheet-7.2.3 → tksheet-7.2.5}/tksheet.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tksheet
3
- Version: 7.2.3
3
+ Version: 7.2.5
4
4
  Summary: Tkinter table / sheet widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
41
41
  License-File: LICENSE.txt
42
42
 
43
43
  <p align="center" width="100%">
44
- <img width="33%" src="https://github.com/ragardner/tksheet/assets/26602401/4124c3ce-cf62-4925-9158-c5bdf712765b">
44
+ <img width="33%" src="https://github.com/ragardner/tksheet/assets/26602401/4124c3ce-cf62-4925-9158-c5bdf712765b">
45
45
  </p>
46
46
 
47
47
  # <div align="center">tksheet - python tkinter table widget</div>
@@ -82,7 +82,7 @@ This library is maintained with the help of **[others](https://github.com/ragard
82
82
  - Expand row heights and column widths
83
83
  - Change fonts and font size (not for individual cells)
84
84
  - Change any colors in the sheet
85
- - Create an unlimited number of high performance dropdown and check boxes
85
+ - Dropdowns, check boxes, progress bars
86
86
  - [Hide rows and/or columns](https://github.com/ragardner/tksheet/wiki/Version-7#example-header-dropdown-boxes-and-row-filtering)
87
87
  - Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column
88
88
 
@@ -1,5 +1,5 @@
1
1
  <p align="center" width="100%">
2
- <img width="33%" src="https://github.com/ragardner/tksheet/assets/26602401/4124c3ce-cf62-4925-9158-c5bdf712765b">
2
+ <img width="33%" src="https://github.com/ragardner/tksheet/assets/26602401/4124c3ce-cf62-4925-9158-c5bdf712765b">
3
3
  </p>
4
4
 
5
5
  # <div align="center">tksheet - python tkinter table widget</div>
@@ -40,7 +40,7 @@ This library is maintained with the help of **[others](https://github.com/ragard
40
40
  - Expand row heights and column widths
41
41
  - Change fonts and font size (not for individual cells)
42
42
  - Change any colors in the sheet
43
- - Create an unlimited number of high performance dropdown and check boxes
43
+ - Dropdowns, check boxes, progress bars
44
44
  - [Hide rows and/or columns](https://github.com/ragardner/tksheet/wiki/Version-7#example-header-dropdown-boxes-and-row-filtering)
45
45
  - Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column
46
46
 
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "tksheet"
7
7
  description = "Tkinter table / sheet widget"
8
8
  readme = "README.md"
9
- version = "7.2.3"
9
+ version = "7.2.5"
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.2.3"
7
+ __version__ = "7.2.5"
8
8
 
9
9
  from .colors import (
10
10
  color_map,
@@ -53,6 +53,7 @@ from .functions import (
53
53
  get_new_indexes,
54
54
  get_seq_without_gaps_at_index,
55
55
  insert_items,
56
+ is_contiguous,
56
57
  is_iterable,
57
58
  move_elements_by_mapping,
58
59
  move_elements_to,
@@ -1440,7 +1440,7 @@ class ColumnHeaders(tk.Canvas):
1440
1440
  ]
1441
1441
  for c in range(grid_start_col, grid_end_col):
1442
1442
  draw_x = self.MT.col_positions[c]
1443
- if self.width_resizing_enabled:
1443
+ if c and self.width_resizing_enabled:
1444
1444
  self.visible_col_dividers[c] = (draw_x - 2, 1, draw_x + 2, yend)
1445
1445
  points.extend(
1446
1446
  (
@@ -412,8 +412,8 @@ def consecutive_ranges(seq: Sequence[int]) -> Generator[tuple[int, int]]:
412
412
  yield seq[start], seq[-1] + 1
413
413
 
414
414
 
415
- def is_contiguous(seq: list[int]) -> bool:
416
- itr = iter(seq)
415
+ def is_contiguous(iterable: Iterator[int]) -> bool:
416
+ itr = iter(iterable)
417
417
  prev = next(itr)
418
418
  return all(i == (prev := prev + 1) for i in itr)
419
419
 
@@ -485,7 +485,6 @@ def move_elements_to(
485
485
  return move_elements_by_mapping(
486
486
  seq,
487
487
  *get_new_indexes(
488
- len(seq),
489
488
  move_to,
490
489
  to_move,
491
490
  get_inverse=True,
@@ -494,19 +493,15 @@ def move_elements_to(
494
493
 
495
494
 
496
495
  def get_new_indexes(
497
- seqlen: int,
498
496
  move_to: int,
499
497
  to_move: list[int],
500
- keep_len: bool = True,
501
498
  get_inverse: bool = False,
502
499
  ) -> tuple[dict]:
503
- # returns
504
- # {old idx: new idx, ...}
505
- offset = len(to_move) - 1
506
- if move_to <= to_move[0]:
507
- new_idxs = range(move_to, move_to + len(to_move))
508
- else:
509
- new_idxs = range(move_to - offset, move_to - offset + len(to_move))
500
+ """
501
+ returns {old idx: new idx, ...}
502
+ """
503
+ offset = sum(1 for i in to_move if i < move_to)
504
+ new_idxs = range(move_to - offset, move_to - offset + len(to_move))
510
505
  new_idxs = {old: new for old, new in zip(to_move, new_idxs)}
511
506
  if get_inverse:
512
507
  return new_idxs, dict(zip(new_idxs.values(), new_idxs))
@@ -84,6 +84,7 @@ from .other_classes import (
84
84
  EventDataDict,
85
85
  FontTuple,
86
86
  Loc,
87
+ ProgressBar,
87
88
  Selected,
88
89
  SelectionBox,
89
90
  TextEditorStorage,
@@ -165,6 +166,7 @@ class MainTable(tk.Canvas):
165
166
  self.col_options = {}
166
167
  self.row_options = {}
167
168
  self.purge_undo_and_redo_stack()
169
+ self.progress_bars = {}
168
170
 
169
171
  self.extra_table_rc_menu_funcs = {}
170
172
  self.extra_index_rc_menu_funcs = {}
@@ -994,18 +996,15 @@ class MainTable(tk.Canvas):
994
996
  data_indexes: bool = False,
995
997
  ) -> tuple[dict[int, int], dict[int, int], int, dict[int, int]]:
996
998
  if not data_indexes or self.all_columns_displayed:
997
- disp_new_idxs = get_new_indexes(
998
- seqlen=len(self.col_positions) - 1,
999
- move_to=move_to,
1000
- to_move=to_move,
1001
- )
999
+ disp_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1002
1000
  else:
1003
1001
  disp_new_idxs = {}
1004
- totalcols = self.equalize_data_row_lengths(at_least_cols=move_to + 1)
1005
- if self.all_columns_displayed or data_indexes:
1006
- data_new_idxs = get_new_indexes(seqlen=totalcols, move_to=move_to, to_move=to_move)
1007
- elif not self.all_columns_displayed and not data_indexes:
1008
- data_new_idxs = get_new_indexes(seqlen=len(self.displayed_columns), move_to=move_to, to_move=to_move)
1002
+ if not self.all_columns_displayed and not data_indexes:
1003
+ totalcols = self.equalize_data_row_lengths(at_least_cols=self.datacn(move_to) + 1)
1004
+ else:
1005
+ totalcols = self.equalize_data_row_lengths(at_least_cols=move_to + 1)
1006
+ data_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1007
+ if not self.all_columns_displayed and not data_indexes:
1009
1008
  moved = {self.displayed_columns[i] for i in to_move}
1010
1009
  data_new_idxs = dict(
1011
1010
  filter(
@@ -1102,6 +1101,7 @@ class MainTable(tk.Canvas):
1102
1101
  tags: {(k[0], full_new_idxs[k[1]]) for k in tagged} for tags, tagged in self.tagged_cells.items()
1103
1102
  }
1104
1103
  self.cell_options = {(k[0], full_new_idxs[k[1]]): v for k, v in self.cell_options.items()}
1104
+ self.progress_bars = {(k[0], full_new_idxs[k[1]]): v for k, v in self.progress_bars.items()}
1105
1105
  self.col_options = {full_new_idxs[k]: v for k, v in self.col_options.items()}
1106
1106
  self.tagged_columns = {
1107
1107
  tags: {full_new_idxs[k] for k in tagged} for tags, tagged in self.tagged_columns.items()
@@ -1230,22 +1230,13 @@ class MainTable(tk.Canvas):
1230
1230
  data_indexes: bool = False,
1231
1231
  ) -> tuple[dict[int, int], dict[int, int], int, dict[int, int]]:
1232
1232
  if not data_indexes or self.all_rows_displayed:
1233
- disp_new_idxs = get_new_indexes(
1234
- seqlen=len(self.row_positions) - 1,
1235
- move_to=move_to,
1236
- to_move=to_move,
1237
- )
1233
+ disp_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1238
1234
  else:
1239
1235
  disp_new_idxs = {}
1240
- self.fix_data_len(move_to)
1241
- totalrows = max(
1242
- self.total_data_rows(),
1243
- len(self.row_positions) - 1,
1244
- )
1245
- if self.all_rows_displayed or data_indexes:
1246
- data_new_idxs = get_new_indexes(seqlen=totalrows, move_to=move_to, to_move=to_move)
1247
- elif not self.all_rows_displayed and not data_indexes:
1248
- data_new_idxs = get_new_indexes(seqlen=len(self.displayed_rows), move_to=move_to, to_move=to_move)
1236
+ self.fix_data_len(self.datarn(move_to) if not self.all_rows_displayed and not data_indexes else move_to)
1237
+ totalrows = max(self.total_data_rows(), len(self.row_positions) - 1)
1238
+ data_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1239
+ if not self.all_rows_displayed and not data_indexes:
1249
1240
  moved = {self.displayed_rows[i] for i in to_move}
1250
1241
  data_new_idxs = dict(
1251
1242
  filter(
@@ -1281,7 +1272,7 @@ class MainTable(tk.Canvas):
1281
1272
  len(self.row_positions) - 1,
1282
1273
  max(data_new_idxs.values(), default=0),
1283
1274
  )
1284
- self.fix_data_len(totalrows)
1275
+ self.fix_data_len(totalrows - 1)
1285
1276
  if event_data is None:
1286
1277
  event_data = event_dict(
1287
1278
  name="move_rows",
@@ -1341,6 +1332,7 @@ class MainTable(tk.Canvas):
1341
1332
  tags: {(full_new_idxs[k[0]], k[1]) for k in tagged} for tags, tagged in self.tagged_cells.items()
1342
1333
  }
1343
1334
  self.cell_options = {(full_new_idxs[k[0]], k[1]): v for k, v in self.cell_options.items()}
1335
+ self.progress_bars = {(full_new_idxs[k[0]], k[1]): v for k, v in self.progress_bars.items()}
1344
1336
  self.tagged_rows = {tags: {full_new_idxs[k] for k in tagged} for tags, tagged in self.tagged_rows.items()}
1345
1337
  self.row_options = {full_new_idxs[k]: v for k, v in self.row_options.items()}
1346
1338
  self.RI.cell_options = {full_new_idxs[k]: v for k, v in self.RI.cell_options.items()}
@@ -3981,6 +3973,9 @@ class MainTable(tk.Canvas):
3981
3973
  self.cell_options = {
3982
3974
  (r, c if not (num := bisect_right(cols, c)) else c + num): v for (r, c), v in self.cell_options.items()
3983
3975
  }
3976
+ self.progress_bars = {
3977
+ (r, c if not (num := bisect_right(cols, c)) else c + num): v for (r, c), v in self.progress_bars.items()
3978
+ }
3984
3979
  self.tagged_columns = {
3985
3980
  tags: {c if not (num := bisect_right(cols, c)) else c + num for c in tagged}
3986
3981
  for tags, tagged in self.tagged_columns.items()
@@ -4050,6 +4045,9 @@ class MainTable(tk.Canvas):
4050
4045
  self.cell_options = {
4051
4046
  (r if not (num := bisect_right(rows, r)) else r + num, c): v for (r, c), v in self.cell_options.items()
4052
4047
  }
4048
+ self.progress_bars = {
4049
+ (r if not (num := bisect_right(rows, r)) else r + num, c): v for (r, c), v in self.progress_bars.items()
4050
+ }
4053
4051
  self.tagged_rows = {
4054
4052
  tags: {r if not (num := bisect_right(rows, r)) else r + num for r in tagged}
4055
4053
  for tags, tagged in self.tagged_rows.items()
@@ -4139,6 +4137,14 @@ class MainTable(tk.Canvas):
4139
4137
  for (r, c), v in self.cell_options.items()
4140
4138
  if c not in to_del
4141
4139
  }
4140
+ self.progress_bars = {
4141
+ (
4142
+ r,
4143
+ c if not (num := bisect_left(to_bis, c)) else c - num,
4144
+ ): v
4145
+ for (r, c), v in self.progress_bars.items()
4146
+ if c not in to_del
4147
+ }
4142
4148
  self.tagged_columns = {
4143
4149
  tags: {c if not (num := bisect_left(to_bis, c)) else c - num for c in tagged if c not in to_del}
4144
4150
  for tags, tagged in self.tagged_columns.items()
@@ -4219,6 +4225,14 @@ class MainTable(tk.Canvas):
4219
4225
  for (r, c), v in self.cell_options.items()
4220
4226
  if r not in to_del
4221
4227
  }
4228
+ self.progress_bars = {
4229
+ (
4230
+ r if not (num := bisect_left(to_bis, r)) else r - num,
4231
+ c,
4232
+ ): v
4233
+ for (r, c), v in self.progress_bars.items()
4234
+ if r not in to_del
4235
+ }
4222
4236
  self.tagged_rows = {
4223
4237
  tags: {r if not (num := bisect_left(to_bis, r)) else r - num for r in tagged if r not in to_del}
4224
4238
  for tags, tagged in self.tagged_rows.items()
@@ -4347,7 +4361,7 @@ class MainTable(tk.Canvas):
4347
4361
  create_ops=create_ops,
4348
4362
  )
4349
4363
  if create_selections:
4350
- self.deselect("all")
4364
+ self.deselect("all", redraw=False)
4351
4365
  for boxst, boxend in consecutive_ranges(tuple(reversed(column_widths))):
4352
4366
  self.create_selection_box(
4353
4367
  0,
@@ -4491,7 +4505,7 @@ class MainTable(tk.Canvas):
4491
4505
  create_ops=create_ops,
4492
4506
  )
4493
4507
  if create_selections:
4494
- self.deselect("all")
4508
+ self.deselect("all", redraw=False)
4495
4509
  for boxst, boxend in consecutive_ranges(tuple(reversed(row_heights))):
4496
4510
  self.create_selection_box(
4497
4511
  boxst,
@@ -5059,7 +5073,10 @@ class MainTable(tk.Canvas):
5059
5073
  can_width: int | None,
5060
5074
  ) -> str:
5061
5075
  redrawn = False
5062
- kwargs = self.get_cell_kwargs(datarn, datacn, key="highlight")
5076
+ if (datarn, datacn) in self.progress_bars:
5077
+ kwargs = self.progress_bars[(datarn, datacn)]
5078
+ else:
5079
+ kwargs = self.get_cell_kwargs(datarn, datacn, key="highlight")
5063
5080
  if kwargs:
5064
5081
  if kwargs[0] is not None:
5065
5082
  c_1 = kwargs[0] if kwargs[0].startswith("#") else color_map[kwargs[0]]
@@ -5104,11 +5121,12 @@ class MainTable(tk.Canvas):
5104
5121
  if kwargs[0] is not None:
5105
5122
  fill = kwargs[0]
5106
5123
  if kwargs[0] is not None:
5107
- redrawn = self.redraw_highlight(
5108
- fc + 1,
5109
- fr + 1,
5110
- sc,
5111
- sr,
5124
+ highlight_fn = partial(
5125
+ self.redraw_highlight,
5126
+ x1=fc + 1,
5127
+ y1=fr + 1,
5128
+ x2=sc,
5129
+ y2=sr,
5112
5130
  fill=fill,
5113
5131
  outline=(
5114
5132
  self.PAR.ops.table_fg
@@ -5116,8 +5134,20 @@ class MainTable(tk.Canvas):
5116
5134
  else ""
5117
5135
  ),
5118
5136
  tag="hi",
5119
- can_width=can_width if (len(kwargs) > 2 and kwargs[2]) else None,
5120
5137
  )
5138
+ if isinstance(kwargs, ProgressBar):
5139
+ if kwargs.del_when_done and kwargs.percent >= 100:
5140
+ del self.progress_bars[(datarn, datacn)]
5141
+ else:
5142
+ redrawn = highlight_fn(
5143
+ can_width=None,
5144
+ pc=kwargs.percent,
5145
+ )
5146
+ else:
5147
+ redrawn = highlight_fn(
5148
+ can_width=can_width if (len(kwargs) > 2 and kwargs[2]) else None,
5149
+ pc=None,
5150
+ )
5121
5151
  elif not kwargs:
5122
5152
  if "cells" in selections and (r, c) in selections["cells"]:
5123
5153
  tf = self.PAR.ops.table_selected_cells_fg
@@ -5130,13 +5160,15 @@ class MainTable(tk.Canvas):
5130
5160
  return tf, redrawn
5131
5161
 
5132
5162
  def redraw_highlight(self, x1, y1, x2, y2, fill, outline, tag, can_width=None, pc=None):
5133
- if not is_type_int(pc) or pc >= 100 or pc <= 0:
5163
+ if not is_type_int(pc) or pc >= 100:
5134
5164
  coords = (
5135
5165
  x1 - 1 if outline else x1,
5136
5166
  y1 - 1 if outline else y1,
5137
5167
  x2 if can_width is None else x2 + can_width,
5138
5168
  y2,
5139
5169
  )
5170
+ elif pc <= 0:
5171
+ coords = (x1, y1, x1, y2)
5140
5172
  else:
5141
5173
  coords = (x1, y1, (x2 - x1) * (pc / 100), y2)
5142
5174
  if self.hidd_high:
@@ -6334,6 +6366,18 @@ class MainTable(tk.Canvas):
6334
6366
  for c in range(box.coords.from_c, box.coords.upto_c)
6335
6367
  }
6336
6368
 
6369
+ def gen_selected_cells(
6370
+ self,
6371
+ get_rows: bool = False,
6372
+ get_cols: bool = False,
6373
+ ) -> Generator[tuple[int, int]]:
6374
+ yield from (
6375
+ (r, c)
6376
+ for item, box in self.get_selection_items(rows=get_rows, columns=get_cols)
6377
+ for r in range(box.coords.from_r, box.coords.upto_r)
6378
+ for c in range(box.coords.from_c, box.coords.upto_c)
6379
+ )
6380
+
6337
6381
  def get_all_selection_boxes(self) -> tuple[tuple[int, int, int, int]]:
6338
6382
  return tuple(box.coords for item, box in self.get_selection_items())
6339
6383
 
@@ -36,7 +36,6 @@ Highlight = namedtuple(
36
36
  DrawnItem = namedtuple("DrawnItem", "iid showing")
37
37
  TextCfg = namedtuple("TextCfg", "txt tf font align")
38
38
  DraggedRowColumn = namedtuple("DraggedRowColumn", "dragged to_move")
39
- ProgressBar = namedtuple("ProgressBar", "bg fg pc name")
40
39
 
41
40
 
42
41
  def num2alpha(n: int) -> str | None:
@@ -470,3 +469,31 @@ Selected = namedtuple(
470
469
  None,
471
470
  ),
472
471
  )
472
+
473
+
474
+ class ProgressBar:
475
+ __slots__ = ("bg", "fg", "name", "percent", "del_when_done")
476
+
477
+ def __init__(self, bg: str, fg: str, name: Hashable, percent: int, del_when_done: bool) -> None:
478
+ self.bg = bg
479
+ self.fg = fg
480
+ self.name = name
481
+ self.percent = percent
482
+ self.del_when_done = del_when_done
483
+
484
+ def __len__(self):
485
+ return 2
486
+
487
+ def __getitem__(self, key: Hashable) -> object:
488
+ if key == 0:
489
+ return self.bg
490
+ elif key == 1:
491
+ return self.fg
492
+ elif key == 2:
493
+ return self.name
494
+ elif key == 3:
495
+ return self.percent
496
+ elif key == 4:
497
+ return self.del_when_done
498
+ else:
499
+ return self.__getattribute__(key)
@@ -1539,7 +1539,7 @@ class RowIndex(tk.Canvas):
1539
1539
  ]
1540
1540
  for r in range(grid_start_row, grid_end_row):
1541
1541
  draw_y = self.MT.row_positions[r]
1542
- if self.height_resizing_enabled:
1542
+ if r and self.height_resizing_enabled:
1543
1543
  self.visible_row_dividers[r] = (1, draw_y - 2, xend, draw_y + 2)
1544
1544
  points.extend(
1545
1545
  (
@@ -2488,6 +2488,14 @@ class RowIndex(tk.Canvas):
2488
2488
  ):
2489
2489
  yield from self.get_iid_descendants(cnode.iid, check_open)
2490
2490
 
2491
+ def items_parent(self, iid: str) -> str:
2492
+ if self.tree[iid].parent:
2493
+ return self.tree[iid].parent.iid
2494
+ return ""
2495
+
2496
+ def gen_top_nodes(self) -> Generator[Node]:
2497
+ yield from (node for node in self.MT._row_index if node.parent == "")
2498
+
2491
2499
  def get_treeview_indent(self, iid: str) -> int:
2492
2500
  if isinstance(self.PAR.ops.treeview_indent, str):
2493
2501
  indent = self.MT.index_txt_width * int(self.PAR.ops.treeview_indent)
@@ -3,7 +3,13 @@ from __future__ import annotations
3
3
  import tkinter as tk
4
4
  from bisect import bisect_left
5
5
  from collections import defaultdict, deque
6
- from collections.abc import Callable, Generator, Iterator, Sequence
6
+ from collections.abc import (
7
+ Callable,
8
+ Generator,
9
+ Hashable,
10
+ Iterator,
11
+ Sequence,
12
+ )
7
13
  from itertools import accumulate, chain, islice, product, repeat
8
14
  from timeit import default_timer
9
15
  from tkinter import ttk
@@ -13,6 +19,7 @@ from .column_headers import ColumnHeaders
13
19
  from .functions import (
14
20
  add_highlight,
15
21
  add_to_options,
22
+ consecutive_ranges,
16
23
  convert_align,
17
24
  data_to_displayed_idxs,
18
25
  del_from_options,
@@ -47,6 +54,7 @@ from .other_classes import (
47
54
  FontTuple,
48
55
  GeneratedMouseEvent,
49
56
  Node,
57
+ ProgressBar,
50
58
  Selected,
51
59
  SelectionBox,
52
60
  Span,
@@ -2433,23 +2441,29 @@ class Sheet(tk.Frame):
2433
2441
  include_header=include_header,
2434
2442
  )
2435
2443
 
2436
- def full_move_rows_idxs(self, data_idxs: dict[int, int]) -> dict[int, int]:
2444
+ def full_move_rows_idxs(self, data_idxs: dict[int, int], max_idx: int | None = None) -> dict[int, int]:
2437
2445
  """
2438
2446
  Converts the dict provided by moving rows event data
2439
2447
  Under the keys ['moved']['rows']['data']
2440
2448
  Into a dict of {old index: new index} for every row
2441
2449
  Includes row numbers in cell options, spans, etc.
2442
2450
  """
2443
- return self.MT.get_full_new_idxs(self.MT.get_max_row_idx(), data_idxs)
2451
+ return self.MT.get_full_new_idxs(
2452
+ self.MT.get_max_row_idx() if max_idx is None else max_idx,
2453
+ data_idxs,
2454
+ )
2444
2455
 
2445
- def full_move_columns_idxs(self, data_idxs: dict[int, int]) -> dict[int, int]:
2456
+ def full_move_columns_idxs(self, data_idxs: dict[int, int], max_idx: int | None = None) -> dict[int, int]:
2446
2457
  """
2447
2458
  Converts the dict provided by moving columns event data
2448
2459
  Under the keys ['moved']['columns']['data']
2449
2460
  Into a dict of {old index: new index} for every column
2450
2461
  Includes column numbers in cell options, spans, etc.
2451
2462
  """
2452
- return self.MT.get_full_new_idxs(self.MT.get_max_column_idx(), data_idxs)
2463
+ return self.MT.get_full_new_idxs(
2464
+ self.MT.get_max_column_idx() if max_idx is None else max_idx,
2465
+ data_idxs,
2466
+ )
2453
2467
 
2454
2468
  # Highlighting Cells
2455
2469
 
@@ -2998,8 +3012,8 @@ class Sheet(tk.Frame):
2998
3012
  box=selected[3],
2999
3013
  )
3000
3014
  else:
3001
- self.MT.deselect()
3002
- return self
3015
+ self.MT.deselect(redraw=False)
3016
+ return self.set_refresh_timer()
3003
3017
 
3004
3018
  def get_selected_rows(
3005
3019
  self,
@@ -3046,6 +3060,13 @@ class Sheet(tk.Frame):
3046
3060
  )
3047
3061
  return self.MT.get_selected_cells(get_rows=get_rows, get_cols=get_columns)
3048
3062
 
3063
+ def gen_selected_cells(
3064
+ self,
3065
+ get_rows: bool = False,
3066
+ get_columns: bool = False,
3067
+ ) -> Generator[tuple[int, int]]:
3068
+ yield from self.MT.gen_selected_cells(get_rows=get_rows, get_cols=get_columns)
3069
+
3049
3070
  def get_all_selection_boxes(self) -> tuple[tuple[int, int, int, int]]:
3050
3071
  return self.MT.get_all_selection_boxes()
3051
3072
 
@@ -3058,11 +3079,11 @@ class Sheet(tk.Frame):
3058
3079
 
3059
3080
  @boxes.setter
3060
3081
  def boxes(self, boxes: Sequence[tuple[tuple[int, int, int, int], str]]) -> Sheet:
3061
- self.MT.deselect()
3082
+ self.MT.deselect(redraw=False)
3062
3083
  self.MT.reselect_from_get_boxes(
3063
3084
  boxes={box[0] if isinstance(box[0], tuple) else tuple(box[0]): box[1] for box in boxes}
3064
3085
  )
3065
- return self
3086
+ return self.set_refresh_timer()
3066
3087
 
3067
3088
  @property
3068
3089
  def canvas_boxes(self) -> dict[int, SelectionBox]:
@@ -4445,6 +4466,60 @@ class Sheet(tk.Frame):
4445
4466
 
4446
4467
  refresh = redraw
4447
4468
 
4469
+ # Progress Bars
4470
+
4471
+ def create_progress_bar(
4472
+ self,
4473
+ row: int,
4474
+ column: int,
4475
+ bg: str,
4476
+ fg: str,
4477
+ name: Hashable,
4478
+ percent: int = 0,
4479
+ del_when_done: bool = False,
4480
+ ) -> Sheet:
4481
+ self.MT.progress_bars[(row, column)] = ProgressBar(
4482
+ bg=bg,
4483
+ fg=fg,
4484
+ name=name,
4485
+ percent=percent,
4486
+ del_when_done=del_when_done,
4487
+ )
4488
+ return self.set_refresh_timer()
4489
+
4490
+ def progress_bar(
4491
+ self,
4492
+ name: Hashable | None = None,
4493
+ cell: tuple[int, int] | None = None,
4494
+ percent: int | None = None,
4495
+ bg: str | None = None,
4496
+ fg: str | None = None,
4497
+ ) -> Sheet:
4498
+ if name is not None:
4499
+ bars = (bar for bar in self.MT.progress_bars.values() if bar.name == name)
4500
+ elif cell is not None:
4501
+ bars = (self.MT.progress_bars[cell],)
4502
+ for bar in bars:
4503
+ if isinstance(percent, int):
4504
+ bar.percent = percent
4505
+ if isinstance(bg, str):
4506
+ bar.bg = bg
4507
+ if isinstance(fg, str):
4508
+ bar.fg = fg
4509
+ return self.set_refresh_timer()
4510
+
4511
+ def del_progress_bar(
4512
+ self,
4513
+ name: Hashable | None = None,
4514
+ cell: tuple[int, int] | None = None,
4515
+ ) -> Sheet:
4516
+ if name is not None:
4517
+ for cell in tuple(cell for cell, bar in self.MT.progress_bars.items() if bar.name == name):
4518
+ del self.MT.progress_bars[cell]
4519
+ elif cell is not None:
4520
+ del self.MT.progress_bars[cell]
4521
+ return self.set_refresh_timer()
4522
+
4448
4523
  # Tags
4449
4524
 
4450
4525
  def tag(
@@ -4639,6 +4714,7 @@ class Sheet(tk.Frame):
4639
4714
  create_selections=False,
4640
4715
  fill=False,
4641
4716
  push_ops=push_ops,
4717
+ redraw=False,
4642
4718
  )
4643
4719
  self.MT.all_rows_displayed = False
4644
4720
  self.MT.displayed_rows = list(range(len(self.MT._row_index)))
@@ -4679,13 +4755,12 @@ class Sheet(tk.Frame):
4679
4755
  open_ids = set(filter(self.exists, map(str.lower, open_ids)))
4680
4756
  self.RI.tree_open_ids = set()
4681
4757
  if open_ids:
4682
- to_open = self._tree_open(open_ids)
4683
4758
  self.show_rows(
4684
- rows=to_open,
4759
+ rows=self._tree_open(open_ids),
4685
4760
  redraw=False,
4686
4761
  deselect_all=False,
4687
4762
  )
4688
- return self.set_refresh_timer(True)
4763
+ return self.set_refresh_timer()
4689
4764
 
4690
4765
  def _tree_open(self, items: set[str]) -> list[int]:
4691
4766
  """
@@ -4920,8 +4995,7 @@ class Sheet(tk.Frame):
4920
4995
  if self.RI.tree[iid].parent and len(self.RI.tree[iid].parent.children) == 1:
4921
4996
  self.RI.tree_open_ids.discard(self.RI.tree[iid].parent.iid)
4922
4997
  del self.RI.tree[iid]
4923
- self.set_refresh_timer(True)
4924
- return self
4998
+ return self.set_refresh_timer()
4925
4999
 
4926
5000
  def set_children(self, parent: str, *newchildren) -> Sheet:
4927
5001
  """
@@ -4931,13 +5005,11 @@ class Sheet(tk.Frame):
4931
5005
  self.move(iid, parent)
4932
5006
  return self
4933
5007
 
4934
- def find_rn_at_top_index(self, index: int) -> int:
4935
- wo_par = 0
4936
- for rn, n in enumerate(self.MT._row_index):
4937
- if not n.parent:
4938
- if wo_par == index:
4939
- return rn
4940
- wo_par += 1
5008
+ def top_index_row(self, index: int) -> int:
5009
+ try:
5010
+ return next(self.RI.tree_rns[n.iid] for i, n in enumerate(self.RI.gen_top_nodes()) if i == index)
5011
+ except Exception:
5012
+ return None
4941
5013
 
4942
5014
  def move(self, item: str, parent: str, index: int | None = None) -> Sheet:
4943
5015
  """
@@ -4960,8 +5032,6 @@ class Sheet(tk.Frame):
4960
5032
  index = len(parent_node.children) - 1
4961
5033
  item_r = self.RI.tree_rns[item]
4962
5034
  new_r = self.RI.tree_rns[parent_node.children[index].iid]
4963
- if item_node not in parent_node.children:
4964
- new_r += 1
4965
5035
  new_r_desc = sum(1 for _ in self.RI.get_iid_descendants(parent_node.children[index].iid))
4966
5036
  item_desc = sum(1 for _ in self.RI.get_iid_descendants(item))
4967
5037
  if item_r < new_r:
@@ -4969,6 +5039,8 @@ class Sheet(tk.Frame):
4969
5039
  else:
4970
5040
  r_ctr = new_r
4971
5041
  else:
5042
+ if index is None:
5043
+ index = 0
4972
5044
  r_ctr = self.RI.tree_rns[parent_node.iid] + 1
4973
5045
  mapping[item_r] = r_ctr
4974
5046
  if parent in self.RI.tree_open_ids and self.item_displayed(parent):
@@ -4979,11 +5051,19 @@ class Sheet(tk.Frame):
4979
5051
  if to_show and self.RI.ancestors_all_open(did, item_node.parent):
4980
5052
  to_show.append(r_ctr)
4981
5053
  r_ctr += 1
4982
- self.RI.remove_node_from_parents_children(item_node)
4983
- item_node.parent = parent_node
4984
- parent_node.children.append(item_node)
5054
+ if parent == self.RI.items_parent(item):
5055
+ pop_index = parent_node.children.index(item_node)
5056
+ parent_node.children.insert(index, parent_node.children.pop(pop_index))
5057
+ else:
5058
+ self.RI.remove_node_from_parents_children(item_node)
5059
+ item_node.parent = parent_node
5060
+ parent_node.children.insert(index, item_node)
4985
5061
  else:
4986
- new_r = self.find_rn_at_top_index((sum(1 for _ in self.get_children("")) - 1) if index is None else index)
5062
+ if index is None:
5063
+ new_r = self.top_index_row((sum(1 for _ in self.RI.gen_top_nodes()) - 1))
5064
+ else:
5065
+ if (new_r := self.top_index_row(index)) is None:
5066
+ new_r = self.top_index_row((sum(1 for _ in self.RI.gen_top_nodes()) - 1))
4987
5067
  item_r = self.RI.tree_rns[item]
4988
5068
  if item_r < new_r:
4989
5069
  par_desc = sum(1 for _ in self.RI.get_iid_descendants(self.rowitem(new_r, data_index=True)))
@@ -5010,8 +5090,7 @@ class Sheet(tk.Frame):
5010
5090
  if parent and (parent not in self.RI.tree_open_ids or not self.item_displayed(parent)):
5011
5091
  self.hide_rows(set(mapping.values()), data_indexes=True)
5012
5092
  self.show_rows(to_show)
5013
- self.set_refresh_timer(True)
5014
- return self
5093
+ return self.set_refresh_timer()
5015
5094
 
5016
5095
  reattach = move
5017
5096
 
@@ -5027,7 +5106,7 @@ class Sheet(tk.Frame):
5027
5106
  if (item := item.lower()) not in self.RI.tree:
5028
5107
  raise ValueError(f"Item '{item}' does not exist.")
5029
5108
  if not self.RI.tree[item].parent:
5030
- return sorted(self.RI.tree_rns[iid] for iid in self.get_children("")).index(self.RI.tree_rns[item])
5109
+ return next(index for index, node in enumerate(self.RI.gen_top_nodes()) if node == self.RI.tree[item])
5031
5110
  return self.RI.tree[item].parent.children.index(self.RI.tree[item])
5032
5111
 
5033
5112
  def item_displayed(self, item: str) -> bool:
@@ -5039,7 +5118,7 @@ class Sheet(tk.Frame):
5039
5118
  raise ValueError(f"Item '{item}' does not exist.")
5040
5119
  return self.RI.tree_rns[item] in self.MT.displayed_rows
5041
5120
 
5042
- def display_item(self, item: str) -> Sheet:
5121
+ def display_item(self, item: str, redraw=False) -> Sheet:
5043
5122
  """
5044
5123
  Ensure that item is displayed in the tree
5045
5124
  - Opens all of an item's ancestors
@@ -5050,16 +5129,22 @@ class Sheet(tk.Frame):
5050
5129
  raise ValueError(f"Item '{item}' does not exist.")
5051
5130
  if self.RI.tree[item].parent:
5052
5131
  for iid in self.RI.get_iid_ancestors(item):
5053
- self.item(iid, open_=True)
5132
+ self.item(iid, open_=True, redraw=False)
5133
+ return self.set_refresh_timer(redraw)
5054
5134
 
5055
- def scroll_to_item(self, item: str) -> Sheet:
5135
+ def scroll_to_item(self, item: str, redraw=False) -> Sheet:
5056
5136
  """
5057
5137
  Scrolls to an item and ensures that it is displayed
5058
5138
  """
5059
5139
  if (item := item.lower()) not in self.RI.tree:
5060
5140
  raise ValueError(f"Item '{item}' does not exist.")
5061
- self.display_item(item)
5062
- self.see(row=bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]), keep_xscroll=True)
5141
+ self.display_item(item, redraw=False)
5142
+ self.see(
5143
+ row=bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]),
5144
+ keep_xscroll=True,
5145
+ redraw=False,
5146
+ )
5147
+ return self.set_refresh_timer(redraw)
5063
5148
 
5064
5149
  def selection(self, cells: bool = False) -> list[str]:
5065
5150
  """
@@ -5070,45 +5155,68 @@ class Sheet(tk.Frame):
5070
5155
  for rn in self.get_selected_rows(get_cells_as_rows=cells)
5071
5156
  ]
5072
5157
 
5073
- def selection_set(self, *items) -> Sheet:
5158
+ def selection_set(self, *items, redraw: bool = True) -> Sheet:
5074
5159
  if any(item.lower() in self.RI.tree for item in unpack(items)):
5075
- self.deselect()
5076
- self.selection_add(*items)
5077
- return self
5160
+ self.deselect(redraw=False)
5161
+ self.selection_add(*items, redraw=False)
5162
+ return self.set_refresh_timer(redraw)
5078
5163
 
5079
- def selection_add(self, *items) -> Sheet:
5164
+ def selection_add(self, *items, redraw: bool = True) -> Sheet:
5165
+ to_open = []
5166
+ quick_displayed_check = set(self.MT.displayed_rows)
5080
5167
  for item in unpack(items):
5081
- if (item := item.lower()) not in self.RI.tree:
5082
- continue
5083
- if not self.item_displayed(item):
5084
- self.display_item(item)
5085
- self.add_row_selection(bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]))
5086
- return self
5168
+ if self.RI.tree_rns[(item := item.lower())] not in quick_displayed_check and self.RI.tree[item].parent:
5169
+ to_open.extend(list(self.RI.get_iid_ancestors(item)))
5170
+ if to_open:
5171
+ self.show_rows(
5172
+ rows=self._tree_open(to_open),
5173
+ redraw=False,
5174
+ deselect_all=False,
5175
+ )
5176
+ for startr, endr in consecutive_ranges(
5177
+ sorted(
5178
+ bisect_left(
5179
+ self.MT.displayed_rows,
5180
+ self.RI.tree_rns[item.lower()],
5181
+ )
5182
+ for item in unpack(items)
5183
+ )
5184
+ ):
5185
+ self.MT.create_selection_box(
5186
+ startr,
5187
+ 0,
5188
+ endr,
5189
+ len(self.MT.col_positions) - 1,
5190
+ "rows",
5191
+ set_current=True,
5192
+ ext=True,
5193
+ )
5194
+ self.MT.run_selection_binding("rows")
5195
+ return self.set_refresh_timer(redraw)
5087
5196
 
5088
- def selection_remove(self, *items) -> Sheet:
5197
+ def selection_remove(self, *items, redraw: bool = True) -> Sheet:
5089
5198
  for item in unpack(items):
5090
5199
  if (item := item.lower()) not in self.RI.tree:
5091
5200
  continue
5092
5201
  try:
5093
- self.deselect(bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]))
5202
+ self.deselect(bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]), redraw=False)
5094
5203
  except Exception:
5095
5204
  continue
5096
- return self
5205
+ return self.set_refresh_timer(redraw)
5097
5206
 
5098
- def selection_toggle(self, *items) -> Sheet:
5207
+ def selection_toggle(self, *items, redraw: bool = True) -> Sheet:
5099
5208
  selected = set(self.MT._row_index[self.displayed_row_to_data(rn)].iid for rn in self.get_selected_rows())
5100
5209
  add = []
5101
5210
  remove = []
5102
5211
  for item in unpack(items):
5103
- if (item := item.lower()) not in self.RI.tree:
5104
- continue
5105
- if item in selected:
5106
- remove.append(item)
5107
- else:
5108
- add.append(item)
5109
- self.selection_remove(*remove)
5110
- self.selection_add(*add)
5111
- return self
5212
+ if (item := item.lower()) in self.RI.tree:
5213
+ if item in selected:
5214
+ remove.append(item)
5215
+ else:
5216
+ add.append(item)
5217
+ self.selection_remove(*remove, redraw=False)
5218
+ self.selection_add(*add, redraw=False)
5219
+ return self.set_refresh_timer(redraw)
5112
5220
 
5113
5221
  # Functions not in docs
5114
5222
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tksheet
3
- Version: 7.2.3
3
+ Version: 7.2.5
4
4
  Summary: Tkinter table / sheet widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
41
41
  License-File: LICENSE.txt
42
42
 
43
43
  <p align="center" width="100%">
44
- <img width="33%" src="https://github.com/ragardner/tksheet/assets/26602401/4124c3ce-cf62-4925-9158-c5bdf712765b">
44
+ <img width="33%" src="https://github.com/ragardner/tksheet/assets/26602401/4124c3ce-cf62-4925-9158-c5bdf712765b">
45
45
  </p>
46
46
 
47
47
  # <div align="center">tksheet - python tkinter table widget</div>
@@ -82,7 +82,7 @@ This library is maintained with the help of **[others](https://github.com/ragard
82
82
  - Expand row heights and column widths
83
83
  - Change fonts and font size (not for individual cells)
84
84
  - Change any colors in the sheet
85
- - Create an unlimited number of high performance dropdown and check boxes
85
+ - Dropdowns, check boxes, progress bars
86
86
  - [Hide rows and/or columns](https://github.com/ragardner/tksheet/wiki/Version-7#example-header-dropdown-boxes-and-row-filtering)
87
87
  - Left `"w"`, Center `"center"` or Right `"e"` text alignment for any cell/row/column
88
88
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes