tksheet 7.3.3__py3-none-any.whl → 7.4.0__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/main_table.py CHANGED
@@ -3,58 +3,34 @@ from __future__ import annotations
3
3
  import csv as csv
4
4
  import io
5
5
  import tkinter as tk
6
- from bisect import (
7
- bisect_left,
8
- bisect_right,
9
- )
10
- from collections import (
11
- defaultdict,
12
- deque,
13
- )
14
- from collections.abc import (
15
- Callable,
16
- Generator,
17
- Hashable,
18
- Sequence,
19
- )
20
- from functools import (
21
- partial,
22
- )
23
- from itertools import (
24
- accumulate,
25
- chain,
26
- cycle,
27
- filterfalse,
28
- islice,
29
- repeat,
30
- )
31
- from math import (
32
- ceil,
33
- floor,
34
- )
6
+ from bisect import bisect_left, bisect_right
7
+ from collections import defaultdict, deque
8
+ from collections.abc import Callable, Generator, Hashable, Sequence
9
+ from functools import partial
10
+ from itertools import accumulate, chain, cycle, filterfalse, islice, repeat
11
+ from math import ceil, floor
35
12
  from operator import itemgetter
36
13
  from tkinter import TclError
37
14
  from typing import Literal
38
15
 
39
- from .colors import (
40
- color_map,
41
- )
16
+ from .colors import color_map
17
+ from .column_headers import ColumnHeaders
42
18
  from .constants import (
43
19
  USER_OS,
20
+ _test_str,
44
21
  bind_add_columns,
45
22
  bind_add_rows,
46
23
  bind_del_columns,
47
24
  bind_del_rows,
48
25
  ctrl_key,
26
+ font_value_error,
49
27
  rc_binding,
50
28
  text_editor_close_bindings,
51
29
  text_editor_newline_bindings,
52
30
  text_editor_to_unbind,
53
31
  val_modifying_options,
54
32
  )
55
- from .find_window import (
56
- FindWindow,
57
- )
33
+ from .find_window import FindWindow
58
34
  from .formatters import (
59
35
  data_to_str,
60
36
  format_data,
@@ -72,6 +48,7 @@ from .functions import (
72
48
  cell_right_within_box,
73
49
  color_tup,
74
50
  consecutive_ranges,
51
+ data_to_displayed_idxs,
75
52
  diff_gen,
76
53
  diff_list,
77
54
  down_cell_within_box,
@@ -97,11 +74,13 @@ from .functions import (
97
74
  move_elements_by_mapping,
98
75
  new_tk_event,
99
76
  next_cell,
77
+ push_n,
100
78
  rounded_box_coords,
101
79
  span_idxs_post_move,
102
80
  stored_event_dict,
103
81
  try_binding,
104
82
  unpickle_obj,
83
+ wrap_text,
105
84
  )
106
85
  from .other_classes import (
107
86
  Box_nt,
@@ -119,24 +98,30 @@ from .other_classes import (
119
98
  SelectionBox,
120
99
  TextEditorStorage,
121
100
  )
122
- from .text_editor import (
123
- TextEditor,
124
- )
125
- from .types import (
126
- AnyIter,
127
- )
101
+ from .row_index import RowIndex
102
+ from .sorting import sort_selection
103
+ from .text_editor import TextEditor
104
+ from .tksheet_types import AnyIter
128
105
 
129
106
 
130
107
  class MainTable(tk.Canvas):
131
- def __init__(self, *args, **kwargs):
108
+ def __init__(
109
+ self,
110
+ parent,
111
+ row_index_canvas: RowIndex,
112
+ column_headers_canvas: ColumnHeaders,
113
+ **kwargs,
114
+ ):
132
115
  super().__init__(
133
- kwargs["parent"],
134
- background=kwargs["parent"].ops.table_bg,
116
+ parent,
117
+ background=parent.ops.table_bg,
135
118
  highlightthickness=0,
136
119
  )
137
- self.PAR = kwargs["parent"]
120
+ self.PAR = parent
138
121
  self.PAR_width = 0
139
122
  self.PAR_height = 0
123
+ self.cells_cache = None
124
+ self.table_txt_height, self.index_txt_height, self.header_txt_height = 0, 0, 0
140
125
  self.scrollregion = tuple()
141
126
  self.current_cursor = ""
142
127
  self.ctrl_b1_pressed = False
@@ -207,6 +192,9 @@ class MainTable(tk.Canvas):
207
192
 
208
193
  self.edit_validation_func = None
209
194
 
195
+ self.extra_begin_sort_cells_func = None
196
+ self.extra_end_sort_cells_func = None
197
+
210
198
  self.extra_begin_ctrl_c_func = None
211
199
  self.extra_end_ctrl_c_func = None
212
200
 
@@ -270,14 +258,19 @@ class MainTable(tk.Canvas):
270
258
  self.rc_insert_column_enabled = False
271
259
  self.rc_delete_row_enabled = False
272
260
  self.rc_insert_row_enabled = False
261
+ self.rc_sort_cells_enabled = False
262
+ self.rc_sort_row_enabled = False
263
+ self.rc_sort_column_enabled = False
264
+ self.rc_sort_rows_enabled = False
265
+ self.rc_sort_columns_enabled = False
273
266
  self.rc_popup_menus_enabled = False
274
267
  self.edit_cell_enabled = False
275
- self.CH = kwargs["column_headers_canvas"]
268
+ self.CH = column_headers_canvas
276
269
  self.CH.MT = self
277
- self.CH.RI = kwargs["row_index_canvas"]
278
- self.RI = kwargs["row_index_canvas"]
270
+ self.CH.RI = row_index_canvas
271
+ self.RI = row_index_canvas
279
272
  self.RI.MT = self
280
- self.RI.CH = kwargs["column_headers_canvas"]
273
+ self.RI.CH = column_headers_canvas
281
274
  self.TL = None # is set from within TopLeftRectangle() __init__
282
275
  self.all_columns_displayed = True
283
276
  self.all_rows_displayed = True
@@ -311,9 +304,12 @@ class MainTable(tk.Canvas):
311
304
  self.txt_measure_canvas_text = self.txt_measure_canvas.create_text(0, 0, text="", font=self.PAR.ops.table_font)
312
305
 
313
306
  self.RI.set_width(self.PAR.ops.default_row_index_width)
307
+
308
+ self.char_widths = {}
314
309
  self.set_table_font_help()
315
310
  self.set_header_font_help()
316
311
  self.set_index_font_help()
312
+
317
313
  self.data = kwargs["data_reference"]
318
314
  if isinstance(self.data, (list, tuple)):
319
315
  self.data = kwargs["data_reference"]
@@ -362,10 +358,8 @@ class MainTable(tk.Canvas):
362
358
  reset_col_positions=False,
363
359
  deselect_all=False,
364
360
  )
361
+ self.rc_popup_menu, self.empty_rc_popup_menu = None, None
365
362
  self.reset_col_positions()
366
-
367
- self.rc_popup_menu = None
368
- self.empty_rc_popup_menu = None
369
363
  self.basic_bindings()
370
364
  self.create_rc_menus()
371
365
 
@@ -377,7 +371,7 @@ class MainTable(tk.Canvas):
377
371
  super().event_generate(*args, **kwargs)
378
372
 
379
373
  def refresh(self, event: object = None) -> None:
380
- self.main_table_redraw_grid_and_text(True, True)
374
+ self.PAR.set_refresh_timer()
381
375
 
382
376
  def window_configured(self, event: object) -> None:
383
377
  w = self.PAR.winfo_width()
@@ -483,7 +477,7 @@ class MainTable(tk.Canvas):
483
477
  dash=dash,
484
478
  width=3,
485
479
  outline=self.PAR.ops.resizing_line_fg if outline is None else outline,
486
- tag="ctrl",
480
+ tags="ctrl",
487
481
  )
488
482
  if delete_on_timer:
489
483
  self.after(1500, self.delete_ctrl_outlines)
@@ -649,6 +643,8 @@ class MainTable(tk.Canvas):
649
643
  find: str,
650
644
  reverse: bool = False,
651
645
  ) -> tuple[int, int, int] | None:
646
+ if not self.selected:
647
+ return None
652
648
  current_box = self.selection_boxes[self.selected.fill_iid]
653
649
  current_id = self.selected.fill_iid
654
650
  if is_last_cell(*current_box.coords, self.selected.row, self.selected.column, reverse=reverse):
@@ -742,13 +738,13 @@ class MainTable(tk.Canvas):
742
738
  dash: tuple[int, int],
743
739
  width: int,
744
740
  outline: str,
745
- tag: str | tuple[str, ...],
741
+ tags: str | tuple[str, ...],
746
742
  ) -> None:
747
743
  if self.hidd_ctrl_outline:
748
744
  t, sh = self.hidd_ctrl_outline.popitem()
749
745
  self.coords(t, x1, y1, x2, y2)
750
746
  if sh:
751
- self.itemconfig(t, fill=fill, dash=dash, width=width, outline=outline, tag=tag)
747
+ self.itemconfig(t, fill=fill, dash=dash, width=width, outline=outline, tags=tags)
752
748
  else:
753
749
  self.itemconfig(
754
750
  t,
@@ -756,7 +752,7 @@ class MainTable(tk.Canvas):
756
752
  dash=dash,
757
753
  width=width,
758
754
  outline=outline,
759
- tag=tag,
755
+ tags=tags,
760
756
  state="normal",
761
757
  )
762
758
  self.lift(t)
@@ -770,7 +766,7 @@ class MainTable(tk.Canvas):
770
766
  dash=dash,
771
767
  width=width,
772
768
  outline=outline,
773
- tag=tag,
769
+ tags=tags,
774
770
  )
775
771
  self.disp_ctrl_outline[t] = True
776
772
 
@@ -809,15 +805,20 @@ class MainTable(tk.Canvas):
809
805
  )
810
806
  return s, writer
811
807
 
812
- def ctrl_c(self, event=None) -> None | EventDataDict:
813
- if not self.selected:
814
- return
815
- event_data = event_dict(
816
- name="begin_ctrl_c",
808
+ def new_event_dict(self, name: str, boxes: dict | None = None, state: bool = False) -> EventDataDict:
809
+ return event_dict(
810
+ name=name,
817
811
  sheet=self.PAR.name,
818
812
  widget=self,
813
+ boxes=self.get_boxes() if boxes is None else boxes,
819
814
  selected=self.selected,
815
+ sheet_state=self.copy_sheet_state() if state else None,
820
816
  )
817
+
818
+ def ctrl_c(self, event=None) -> None | EventDataDict:
819
+ if not self.selected:
820
+ return
821
+ event_data = self.new_event_dict("begin_ctrl_c")
821
822
  boxes, maxrows = self.get_ctrl_x_c_boxes()
822
823
  event_data["selection_boxes"] = boxes
823
824
  s, writer = self.io_csv_writer()
@@ -862,12 +863,7 @@ class MainTable(tk.Canvas):
862
863
  def ctrl_x(self, event=None, validation: bool = True) -> None | EventDataDict:
863
864
  if not self.selected:
864
865
  return
865
- event_data = event_dict(
866
- name="edit_table",
867
- sheet=self.PAR.name,
868
- widget=self,
869
- selected=self.selected,
870
- )
866
+ event_data = self.new_event_dict("edit_table")
871
867
  boxes, maxrows = self.get_ctrl_x_c_boxes()
872
868
  event_data["selection_boxes"] = boxes
873
869
  s, writer = self.io_csv_writer()
@@ -943,17 +939,64 @@ class MainTable(tk.Canvas):
943
939
  self.PAR.emit_event("<<Cut>>", event_data)
944
940
  return event_data
945
941
 
942
+ def sort_boxes(
943
+ self,
944
+ event: tk.Event | None = None,
945
+ boxes: AnyIter[int, int, int, int] | None = None,
946
+ reverse: bool = False,
947
+ row_wise: bool = False,
948
+ validation: bool = True,
949
+ key: Callable | None = None,
950
+ undo: bool = True,
951
+ ) -> EventDataDict:
952
+ if boxes is None:
953
+ boxes = self.get_boxes()
954
+ if not boxes:
955
+ boxes = [(0, 0, len(self.row_positions) - 1, len(self.col_positions) - 1)]
956
+ event_data = self.new_event_dict("edit_table", boxes=boxes)
957
+ try_binding(self.extra_begin_sort_cells_func, event_data)
958
+ for r1, c1, r2, c2 in boxes:
959
+ data = sort_selection(
960
+ [[self.get_cell_data(self.datarn(r), self.datacn(c)) for c in range(c1, c2)] for r in range(r1, r2)],
961
+ reverse=reverse,
962
+ key=key,
963
+ row_wise=row_wise,
964
+ )
965
+ for ir, r in enumerate(range(r1, r2)):
966
+ data_r = self.datarn(r)
967
+ for ic, c in enumerate(range(c1, c2)):
968
+ data_c = self.datacn(c)
969
+ val = data[ir][ic]
970
+ if (
971
+ not self.edit_validation_func
972
+ or not validation
973
+ or (
974
+ self.edit_validation_func
975
+ and (val := self.edit_validation_func(mod_event_val(event_data, val, (data_r, data_c))))
976
+ is not None
977
+ )
978
+ ):
979
+ event_data = self.event_data_set_cell(
980
+ datarn=data_r,
981
+ datacn=data_c,
982
+ value=val,
983
+ event_data=event_data,
984
+ )
985
+ if event_data["cells"]["table"]:
986
+ if undo and self.undo_enabled:
987
+ self.undo_stack.append(stored_event_dict(event_data))
988
+ try_binding(self.extra_end_sort_cells_func, event_data, "end_edit_table")
989
+ self.sheet_modified(event_data)
990
+ self.PAR.emit_event("<<SheetModified>>", event_data)
991
+ self.refresh()
992
+ return event_data
993
+
946
994
  def ctrl_v(self, event: object = None, validation: bool = True) -> None | EventDataDict:
947
995
  if not self.PAR.ops.paste_can_expand_x and len(self.col_positions) == 1:
948
996
  return
949
997
  if not self.PAR.ops.paste_can_expand_y and len(self.row_positions) == 1:
950
998
  return
951
- event_data = event_dict(
952
- name="edit_table",
953
- sheet=self.PAR.name,
954
- widget=self,
955
- selected=self.selected,
956
- )
999
+ event_data = self.new_event_dict("edit_table", state=True)
957
1000
  if self.selected:
958
1001
  selected_r = self.selected.box.from_r
959
1002
  selected_c = self.selected.box.from_c
@@ -1210,12 +1253,7 @@ class MainTable(tk.Canvas):
1210
1253
  def delete_key(self, event: object = None, validation: bool = True) -> None | EventDataDict:
1211
1254
  if not self.selected:
1212
1255
  return
1213
- event_data = event_dict(
1214
- name="edit_table",
1215
- sheet=self.PAR.name,
1216
- widget=self,
1217
- selected=self.selected,
1218
- )
1256
+ event_data = self.new_event_dict("edit_table")
1219
1257
  boxes = self.get_boxes()
1220
1258
  event_data["selection_boxes"] = boxes
1221
1259
  if not try_binding(self.extra_begin_delete_key_func, event_data, "begin_delete"):
@@ -1290,7 +1328,6 @@ class MainTable(tk.Canvas):
1290
1328
  move_data: bool = True,
1291
1329
  move_widths: bool = True,
1292
1330
  create_selections: bool = True,
1293
- data_indexes: bool = False,
1294
1331
  event_data: EventDataDict | None = None,
1295
1332
  ) -> tuple[dict[int, int], dict[int, int], EventDataDict]:
1296
1333
  self.saved_column_widths = {}
@@ -1299,22 +1336,17 @@ class MainTable(tk.Canvas):
1299
1336
  if totalcols:
1300
1337
  totalcols += 1
1301
1338
  totalcols = self.equalize_data_row_lengths(at_least_cols=totalcols)
1302
- if event_data is None:
1303
- event_data = event_dict(
1304
- name="move_columns",
1305
- sheet=self.PAR.name,
1306
- widget=self,
1307
- boxes=self.get_boxes(),
1308
- selected=self.selected,
1309
- )
1339
+ if not event_data:
1340
+ event_data = self.new_event_dict("move_columns", state=True)
1341
+ if not event_data["moved"]["columns"]:
1310
1342
  event_data["moved"]["columns"] = {
1311
1343
  "data": data_new_idxs,
1312
1344
  "displayed": {} if disp_new_idxs is None else disp_new_idxs,
1313
1345
  }
1314
1346
  event_data["options"] = self.copy_options()
1315
1347
  event_data["named_spans"] = {k: span.pickle_self() for k, span in self.named_spans.items()}
1316
- if move_widths and disp_new_idxs and (not data_indexes or self.all_columns_displayed):
1317
- self.deselect("all", run_binding=False, redraw=False)
1348
+
1349
+ if move_widths and disp_new_idxs:
1318
1350
  self.set_col_positions(
1319
1351
  itr=move_elements_by_mapping(
1320
1352
  self.get_column_widths(),
@@ -1328,6 +1360,7 @@ class MainTable(tk.Canvas):
1328
1360
  )
1329
1361
  )
1330
1362
  if create_selections:
1363
+ self.deselect("all", run_binding=False, redraw=False)
1331
1364
  for boxst, boxend in consecutive_ranges(sorted(disp_new_idxs.values())):
1332
1365
  self.create_selection_box(
1333
1366
  0,
@@ -1337,6 +1370,8 @@ class MainTable(tk.Canvas):
1337
1370
  "columns",
1338
1371
  run_binding=True,
1339
1372
  )
1373
+ else:
1374
+ self.recreate_all_selection_boxes()
1340
1375
  if move_data:
1341
1376
  self.data = list(
1342
1377
  map(
@@ -1490,29 +1525,54 @@ class MainTable(tk.Canvas):
1490
1525
  data_indexes: bool = False,
1491
1526
  ) -> tuple[dict[int, int], dict[int, int], int, dict[int, int]]:
1492
1527
  if not data_indexes or self.all_rows_displayed:
1493
- disp_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1528
+ disp_new_idxs = get_new_indexes(
1529
+ move_to=move_to,
1530
+ to_move=to_move,
1531
+ )
1532
+ data_new_idxs = dict(disp_new_idxs)
1494
1533
  else:
1495
1534
  disp_new_idxs = {}
1535
+ data_new_idxs = get_new_indexes(
1536
+ move_to=move_to,
1537
+ to_move=to_move,
1538
+ )
1496
1539
  # move_to can be len and fix_data_len() takes index so - 1
1497
1540
  fix_len = (move_to - 1) if move_to else move_to
1498
1541
  if not self.all_rows_displayed and not data_indexes:
1499
1542
  fix_len = self.datarn(fix_len)
1500
1543
  self.fix_data_len(fix_len)
1501
1544
  totalrows = max(self.total_data_rows(), len(self.row_positions) - 1)
1502
- data_new_idxs = get_new_indexes(move_to=move_to, to_move=to_move)
1503
1545
  if not self.all_rows_displayed and not data_indexes:
1504
- data_new_idxs = dict(
1505
- zip(
1546
+ keep = set(map(self.datarn, to_move))
1547
+ data_new_idxs = {
1548
+ k: v
1549
+ for k, v in zip(
1506
1550
  move_elements_by_mapping(
1507
1551
  self.displayed_rows,
1508
1552
  data_new_idxs,
1509
1553
  dict(zip(data_new_idxs.values(), data_new_idxs)),
1510
1554
  ),
1511
1555
  self.displayed_rows,
1512
- ),
1513
- )
1556
+ )
1557
+ if k in keep
1558
+ }
1514
1559
  return data_new_idxs, dict(zip(data_new_idxs.values(), data_new_idxs)), totalrows, disp_new_idxs
1515
1560
 
1561
+ def move_rows_data(
1562
+ self,
1563
+ data_new_idxs: dict[int, int],
1564
+ data_old_idxs: dict[int, int],
1565
+ maxidx: int,
1566
+ ) -> None:
1567
+ self.data = move_elements_by_mapping(
1568
+ self.data,
1569
+ data_new_idxs,
1570
+ data_old_idxs,
1571
+ )
1572
+ self.RI.fix_index(maxidx)
1573
+ if isinstance(self._row_index, list) and self._row_index:
1574
+ self._row_index = move_elements_by_mapping(self._row_index, data_new_idxs, data_old_idxs)
1575
+
1516
1576
  def move_rows_adjust_options_dict(
1517
1577
  self,
1518
1578
  data_new_idxs: dict[int, int],
@@ -1522,8 +1582,9 @@ class MainTable(tk.Canvas):
1522
1582
  move_data: bool = True,
1523
1583
  move_heights: bool = True,
1524
1584
  create_selections: bool = True,
1525
- data_indexes: bool = False,
1526
1585
  event_data: EventDataDict | None = None,
1586
+ undo_modification: EventDataDict | None = None,
1587
+ node_change: None | tuple[str, str, int] = None,
1527
1588
  ) -> tuple[dict[int, int], dict[int, int], EventDataDict]:
1528
1589
  self.saved_row_heights = {}
1529
1590
  if not isinstance(totalrows, int):
@@ -1533,54 +1594,33 @@ class MainTable(tk.Canvas):
1533
1594
  max(data_new_idxs.values(), default=0),
1534
1595
  )
1535
1596
  self.fix_data_len(totalrows - 1)
1536
- if event_data is None:
1537
- event_data = event_dict(
1538
- name="move_rows",
1539
- sheet=self.PAR.name,
1540
- widget=self,
1541
- boxes=self.get_boxes(),
1542
- selected=self.selected,
1543
- )
1597
+ if not event_data:
1598
+ event_data = self.new_event_dict("move_rows", state=True)
1599
+ if not event_data["moved"]["rows"]:
1544
1600
  event_data["moved"]["rows"] = {
1545
1601
  "data": data_new_idxs,
1546
1602
  "displayed": {} if disp_new_idxs is None else disp_new_idxs,
1547
1603
  }
1548
1604
  event_data["options"] = self.copy_options()
1549
1605
  event_data["named_spans"] = {k: span.pickle_self() for k, span in self.named_spans.items()}
1550
- if move_heights and disp_new_idxs and (not data_indexes or self.all_rows_displayed):
1551
- self.deselect("all", run_binding=False, redraw=False)
1552
- self.set_row_positions(
1553
- itr=move_elements_by_mapping(
1554
- self.get_row_heights(),
1555
- disp_new_idxs,
1556
- dict(
1557
- zip(
1558
- disp_new_idxs.values(),
1559
- disp_new_idxs,
1560
- )
1561
- ),
1562
- )
1563
- )
1564
- if create_selections:
1565
- for boxst, boxend in consecutive_ranges(sorted(disp_new_idxs.values())):
1566
- self.create_selection_box(
1567
- boxst,
1568
- 0,
1569
- boxend,
1570
- len(self.col_positions) - 1,
1571
- "rows",
1572
- run_binding=True,
1573
- )
1606
+
1574
1607
  if move_data:
1575
- self.data = move_elements_by_mapping(
1576
- self.data,
1577
- data_new_idxs,
1578
- data_old_idxs,
1579
- )
1580
1608
  maxidx = len_to_idx(totalrows)
1581
- self.RI.fix_index(maxidx)
1582
- if isinstance(self._row_index, list) and self._row_index:
1583
- self._row_index = move_elements_by_mapping(self._row_index, data_new_idxs, data_old_idxs)
1609
+ if self.PAR.ops.treeview:
1610
+ two_step_move = self.RI.move_rows_mod_nodes(
1611
+ data_new_idxs=data_new_idxs,
1612
+ data_old_idxs=data_old_idxs,
1613
+ disp_new_idxs=disp_new_idxs,
1614
+ maxidx=maxidx,
1615
+ event_data=event_data,
1616
+ undo_modification=undo_modification,
1617
+ node_change=node_change,
1618
+ )
1619
+ data_new_idxs, data_old_idxs, disp_new_idxs, event_data = next(two_step_move)
1620
+ if not data_new_idxs and not disp_new_idxs:
1621
+ return data_new_idxs, disp_new_idxs, event_data
1622
+ else:
1623
+ self.move_rows_data(data_new_idxs, data_old_idxs, maxidx)
1584
1624
  maxidx = self.get_max_row_idx(maxidx)
1585
1625
  full_new_idxs = self.get_full_new_idxs(
1586
1626
  max_idx=maxidx,
@@ -1598,6 +1638,9 @@ class MainTable(tk.Canvas):
1598
1638
  self.RI.cell_options = {full_new_idxs[k]: v for k, v in self.RI.cell_options.items()}
1599
1639
  self.RI.tree_rns = {v: full_new_idxs[k] for v, k in self.RI.tree_rns.items()}
1600
1640
  self.displayed_rows = sorted(full_new_idxs[k] for k in self.displayed_rows)
1641
+ if self.PAR.ops.treeview:
1642
+ next(two_step_move)
1643
+
1601
1644
  if self.named_spans:
1602
1645
  totalcols = self.total_data_cols()
1603
1646
  new_ops = self.PAR.create_options_from_span
@@ -1696,6 +1739,33 @@ class MainTable(tk.Canvas):
1696
1739
  del self.cell_options[(full_new_idxs[k], c)][span["type_"]]
1697
1740
  # finally, change the span coords
1698
1741
  span["from_r"], span["upto_r"] = newfrom, newupto
1742
+
1743
+ if move_heights and disp_new_idxs:
1744
+ self.set_row_positions(
1745
+ itr=move_elements_by_mapping(
1746
+ self.get_row_heights(),
1747
+ disp_new_idxs,
1748
+ dict(
1749
+ zip(
1750
+ disp_new_idxs.values(),
1751
+ disp_new_idxs,
1752
+ )
1753
+ ),
1754
+ )
1755
+ )
1756
+ if create_selections:
1757
+ self.deselect("all", run_binding=False, redraw=False)
1758
+ for boxst, boxend in consecutive_ranges(sorted(disp_new_idxs.values())):
1759
+ self.create_selection_box(
1760
+ boxst,
1761
+ 0,
1762
+ boxend,
1763
+ len(self.col_positions) - 1,
1764
+ "rows",
1765
+ run_binding=True,
1766
+ )
1767
+ else:
1768
+ self.recreate_all_selection_boxes()
1699
1769
  return data_new_idxs, disp_new_idxs, event_data
1700
1770
 
1701
1771
  def get_max_row_idx(self, maxidx: int | None = None) -> int:
@@ -1812,7 +1882,7 @@ class MainTable(tk.Canvas):
1812
1882
  event_data["cells"]["table"][k] = self.get_cell_data(k[0], k[1])
1813
1883
  return event_data
1814
1884
 
1815
- def restore_options_named_spans(self, modification: EventDataDict) -> None:
1885
+ def restore_sheet_state(self, modification: EventDataDict) -> None:
1816
1886
  if "cell_options" in modification["options"]:
1817
1887
  self.cell_options = modification["options"]["cell_options"]
1818
1888
  if "column_options" in modification["options"]:
@@ -1829,17 +1899,25 @@ class MainTable(tk.Canvas):
1829
1899
  self.tagged_rows = modification["options"]["tagged_rows"]
1830
1900
  if "tagged_columns" in modification["options"]:
1831
1901
  self.tagged_columns = modification["options"]["tagged_columns"]
1832
- self.named_spans = {
1833
- k: mod_span_widget(unpickle_obj(v), self.PAR) for k, v in modification["named_spans"].items()
1834
- }
1902
+ if modification["named_spans"]:
1903
+ self.named_spans = {
1904
+ k: mod_span_widget(unpickle_obj(v), self.PAR) for k, v in modification["named_spans"].items()
1905
+ }
1906
+ if modification["sheet_state"]:
1907
+ self.RI.tree_open_ids = modification["sheet_state"]["tree_open_ids"]
1908
+ self.row_positions = modification["sheet_state"]["row_positions"]
1909
+ self.col_positions = modification["sheet_state"]["col_positions"]
1910
+ self.displayed_rows = modification["sheet_state"]["displayed_rows"]
1911
+ self.displayed_columns = modification["sheet_state"]["displayed_columns"]
1912
+ self.all_rows_displayed = modification["sheet_state"]["all_rows_displayed"]
1913
+ self.all_columns_displayed = modification["sheet_state"]["all_columns_displayed"]
1914
+ self.saved_row_heights = modification["sheet_state"]["saved_row_heights"]
1915
+ self.saved_column_widths = modification["sheet_state"]["saved_column_widths"]
1916
+ self.recreate_all_selection_boxes()
1835
1917
 
1836
1918
  def undo_modification_invert_event(self, modification: EventDataDict, name: str = "undo") -> EventDataDict:
1837
1919
  self.deselect("all", redraw=False)
1838
- event_data = event_dict(
1839
- name=modification["eventname"],
1840
- sheet=self.PAR.name,
1841
- widget=self,
1842
- )
1920
+ event_data = self.new_event_dict(modification["eventname"], state=True)
1843
1921
  event_data["selection_boxes"] = modification["selection_boxes"]
1844
1922
  event_data["selected"] = modification["selected"]
1845
1923
  saved_cells = False
@@ -1870,7 +1948,6 @@ class MainTable(tk.Canvas):
1870
1948
  "data": data_new_idxs,
1871
1949
  "displayed": disp_new_idxs,
1872
1950
  }
1873
- self.restore_options_named_spans(modification)
1874
1951
 
1875
1952
  if modification["moved"]["rows"]:
1876
1953
  totalrows = max(self.total_data_rows(), max(modification["moved"]["rows"]["data"].values()))
@@ -1890,12 +1967,12 @@ class MainTable(tk.Canvas):
1890
1967
  )
1891
1968
  ),
1892
1969
  event_data=event_data,
1970
+ undo_modification=modification,
1893
1971
  )
1894
1972
  event_data["moved"]["rows"] = {
1895
1973
  "data": data_new_idxs,
1896
1974
  "displayed": disp_new_idxs,
1897
1975
  }
1898
- self.restore_options_named_spans(modification)
1899
1976
 
1900
1977
  if modification["added"]["rows"]:
1901
1978
  self.deselect("all", run_binding=False, redraw=False)
@@ -1906,8 +1983,8 @@ class MainTable(tk.Canvas):
1906
1983
  event_data = self.delete_rows_displayed(
1907
1984
  rows=tuple(reversed(modification["added"]["rows"]["row_heights"])),
1908
1985
  event_data=event_data,
1986
+ restored_state=True,
1909
1987
  )
1910
- self.displayed_rows = modification["added"]["rows"]["displayed_rows"]
1911
1988
 
1912
1989
  if modification["added"]["columns"]:
1913
1990
  self.deselect("all", run_binding=False, redraw=False)
@@ -1918,21 +1995,21 @@ class MainTable(tk.Canvas):
1918
1995
  event_data = self.delete_columns_displayed(
1919
1996
  cols=tuple(reversed(modification["added"]["columns"]["column_widths"])),
1920
1997
  event_data=event_data,
1998
+ restored_state=True,
1921
1999
  )
1922
- self.displayed_columns = modification["added"]["columns"]["displayed_columns"]
1923
2000
 
1924
2001
  if modification["deleted"]["rows"] or modification["deleted"]["row_heights"]:
2002
+ event_data["treeview"] = modification["treeview"]
1925
2003
  self.add_rows(
1926
2004
  rows=modification["deleted"]["rows"],
1927
2005
  index=modification["deleted"]["index"],
1928
2006
  row_heights=modification["deleted"]["row_heights"],
1929
2007
  event_data=event_data,
1930
- displayed_rows=modification["deleted"]["displayed_rows"],
1931
2008
  create_ops=False,
1932
- create_selections=not modification["eventname"].startswith("edit"),
2009
+ create_selections=False,
1933
2010
  add_col_positions=False,
2011
+ restored_state=True,
1934
2012
  )
1935
- self.restore_options_named_spans(modification)
1936
2013
 
1937
2014
  if modification["deleted"]["columns"] or modification["deleted"]["column_widths"]:
1938
2015
  self.add_columns(
@@ -1940,12 +2017,11 @@ class MainTable(tk.Canvas):
1940
2017
  header=modification["deleted"]["header"],
1941
2018
  column_widths=modification["deleted"]["column_widths"],
1942
2019
  event_data=event_data,
1943
- displayed_columns=modification["deleted"]["displayed_columns"],
1944
2020
  create_ops=False,
1945
- create_selections=not modification["eventname"].startswith("edit"),
2021
+ create_selections=False,
1946
2022
  add_row_positions=False,
2023
+ restored_state=True,
1947
2024
  )
1948
- self.restore_options_named_spans(modification)
1949
2025
 
1950
2026
  if modification["eventname"].startswith(("edit", "move")):
1951
2027
  if not saved_cells:
@@ -1958,6 +2034,8 @@ class MainTable(tk.Canvas):
1958
2034
  elif modification["eventname"].startswith("delete"):
1959
2035
  event_data["eventname"] = modification["eventname"].replace("delete", "add")
1960
2036
 
2037
+ self.restore_sheet_state(modification)
2038
+
1961
2039
  if not modification["eventname"].startswith("move") and (
1962
2040
  len(self.row_positions) > 1 or len(self.col_positions) > 1
1963
2041
  ):
@@ -2014,9 +2092,9 @@ class MainTable(tk.Canvas):
2014
2092
  need_redraw = True
2015
2093
  else:
2016
2094
  if r is not None and not keep_yscroll:
2017
- y = int(self.row_positions[r] + ((self.row_positions[r + 1] - self.row_positions[r]) * r_pc)) - 2
2018
- if y < 0:
2019
- y = 0
2095
+ y = max(
2096
+ 0, int(self.row_positions[r] + ((self.row_positions[r + 1] - self.row_positions[r]) * r_pc)) - 2
2097
+ )
2020
2098
  y = y / (self.row_positions[-1] + self.PAR.ops.empty_vertical)
2021
2099
  args = [
2022
2100
  "moveto",
@@ -2041,9 +2119,9 @@ class MainTable(tk.Canvas):
2041
2119
  need_redraw = True
2042
2120
  else:
2043
2121
  if c is not None and not keep_xscroll:
2044
- x = int(self.col_positions[c] + ((self.col_positions[c + 1] - self.col_positions[c]) * c_pc)) - 2
2045
- if x < 0:
2046
- x = 0
2122
+ x = max(
2123
+ 0, int(self.col_positions[c] + ((self.col_positions[c + 1] - self.col_positions[c]) * c_pc)) - 2
2124
+ )
2047
2125
  x = x / (self.col_positions[-1] + self.PAR.ops.empty_horizontal)
2048
2126
  args = [
2049
2127
  "moveto",
@@ -2394,15 +2472,11 @@ class MainTable(tk.Canvas):
2394
2472
  def page_UP(self, event: object = None) -> None:
2395
2473
  height = self.winfo_height()
2396
2474
  top = self.canvasy(0)
2397
- scrollto_y = top - height
2398
- if scrollto_y < 0:
2399
- scrollto_y = 0
2475
+ scrollto_y = max(0, top - height)
2400
2476
  if self.PAR.ops.page_up_down_select_row:
2401
2477
  r = bisect_left(self.row_positions, scrollto_y)
2402
2478
  if self.selected and self.selected.row == r:
2403
- r -= 1
2404
- if r < 0:
2405
- r = 0
2479
+ r = max(0, r - 1)
2406
2480
  if self.RI.row_selection_enabled and (
2407
2481
  self.anything_selected(exclude_columns=True, exclude_cells=True) or not self.anything_selected()
2408
2482
  ):
@@ -2854,7 +2928,7 @@ class MainTable(tk.Canvas):
2854
2928
  self.menu_add_command(
2855
2929
  self.CH.ch_rc_popup_menu,
2856
2930
  label=self.PAR.ops.delete_columns_label,
2857
- command=self.rc_delete_columns,
2931
+ command=self.delete_columns,
2858
2932
  **mnkwgs,
2859
2933
  )
2860
2934
  if self.rc_insert_column_enabled:
@@ -2880,7 +2954,7 @@ class MainTable(tk.Canvas):
2880
2954
  self.menu_add_command(
2881
2955
  self.RI.ri_rc_popup_menu,
2882
2956
  label=self.PAR.ops.delete_rows_label,
2883
- command=self.rc_delete_rows,
2957
+ command=self.delete_rows,
2884
2958
  **mnkwgs,
2885
2959
  )
2886
2960
  if self.rc_insert_row_enabled:
@@ -2902,6 +2976,99 @@ class MainTable(tk.Canvas):
2902
2976
  command=lambda: self.rc_add_rows("below"),
2903
2977
  **mnkwgs,
2904
2978
  )
2979
+ if self.rc_sort_cells_enabled:
2980
+ self.menu_add_command(
2981
+ self.rc_popup_menu,
2982
+ label=self.PAR.ops.sort_cells_label,
2983
+ accelerator=self.PAR.ops.sort_cells_accelerator,
2984
+ command=self.sort_boxes,
2985
+ **mnkwgs,
2986
+ )
2987
+ self.menu_add_command(
2988
+ self.rc_popup_menu,
2989
+ label=self.PAR.ops.sort_cells_reverse_label,
2990
+ accelerator=self.PAR.ops.sort_cells_reverse_accelerator,
2991
+ command=lambda: self.sort_boxes(reverse=True),
2992
+ **mnkwgs,
2993
+ )
2994
+ self.menu_add_command(
2995
+ self.rc_popup_menu,
2996
+ label=self.PAR.ops.sort_cells_x_label,
2997
+ accelerator=self.PAR.ops.sort_cells_x_accelerator,
2998
+ command=lambda: self.sort_boxes(row_wise=True),
2999
+ **mnkwgs,
3000
+ )
3001
+ self.menu_add_command(
3002
+ self.rc_popup_menu,
3003
+ label=self.PAR.ops.sort_cells_x_reverse_label,
3004
+ accelerator=self.PAR.ops.sort_cells_x_reverse_accelerator,
3005
+ command=lambda: self.sort_boxes(reverse=True, row_wise=True),
3006
+ **mnkwgs,
3007
+ )
3008
+ # row index sort rows cells
3009
+ if self.rc_sort_row_enabled:
3010
+ self.menu_add_command(
3011
+ self.RI.ri_rc_popup_menu,
3012
+ label=self.PAR.ops.sort_row_label,
3013
+ accelerator=self.PAR.ops.sort_row_accelerator,
3014
+ command=self.RI._sort_rows,
3015
+ **mnkwgs,
3016
+ )
3017
+ self.menu_add_command(
3018
+ self.RI.ri_rc_popup_menu,
3019
+ label=self.PAR.ops.sort_row_reverse_label,
3020
+ accelerator=self.PAR.ops.sort_row_reverse_accelerator,
3021
+ command=lambda: self.RI._sort_rows(reverse=True),
3022
+ **mnkwgs,
3023
+ )
3024
+ # header sort columns cells
3025
+ if self.rc_sort_column_enabled:
3026
+ self.menu_add_command(
3027
+ self.CH.ch_rc_popup_menu,
3028
+ label=self.PAR.ops.sort_column_label,
3029
+ accelerator=self.PAR.ops.sort_column_accelerator,
3030
+ command=self.CH._sort_columns,
3031
+ **mnkwgs,
3032
+ )
3033
+ self.menu_add_command(
3034
+ self.CH.ch_rc_popup_menu,
3035
+ label=self.PAR.ops.sort_column_reverse_label,
3036
+ accelerator=self.PAR.ops.sort_column_reverse_accelerator,
3037
+ command=lambda: self.CH._sort_columns(reverse=True),
3038
+ **mnkwgs,
3039
+ )
3040
+ # row index sort columns by row
3041
+ if self.rc_sort_columns_enabled:
3042
+ self.menu_add_command(
3043
+ self.RI.ri_rc_popup_menu,
3044
+ label=self.PAR.ops.sort_columns_label,
3045
+ accelerator=self.PAR.ops.sort_columns_accelerator,
3046
+ command=self.RI._sort_columns_by_row,
3047
+ **mnkwgs,
3048
+ )
3049
+ self.menu_add_command(
3050
+ self.RI.ri_rc_popup_menu,
3051
+ label=self.PAR.ops.sort_columns_reverse_label,
3052
+ accelerator=self.PAR.ops.sort_columns_reverse_accelerator,
3053
+ command=lambda: self.RI._sort_columns_by_row(reverse=True),
3054
+ **mnkwgs,
3055
+ )
3056
+ # header sort rows by column
3057
+ if self.rc_sort_rows_enabled:
3058
+ self.menu_add_command(
3059
+ self.CH.ch_rc_popup_menu,
3060
+ label=self.PAR.ops.sort_rows_label,
3061
+ accelerator=self.PAR.ops.sort_rows_accelerator,
3062
+ command=self.CH._sort_rows_by_column,
3063
+ **mnkwgs,
3064
+ )
3065
+ self.menu_add_command(
3066
+ self.CH.ch_rc_popup_menu,
3067
+ label=self.PAR.ops.sort_rows_reverse_label,
3068
+ accelerator=self.PAR.ops.sort_rows_reverse_accelerator,
3069
+ command=lambda: self.CH._sort_rows_by_column(reverse=True),
3070
+ **mnkwgs,
3071
+ )
2905
3072
  for label, func in self.extra_table_rc_menu_funcs.items():
2906
3073
  self.menu_add_command(
2907
3074
  self.rc_popup_menu,
@@ -3072,6 +3239,16 @@ class MainTable(tk.Canvas):
3072
3239
  self.rc_insert_row_enabled = True
3073
3240
  self.rc_popup_menus_enabled = True
3074
3241
  self.rc_select_enabled = True
3242
+ if binding in ("all", "sort_cells"):
3243
+ self.rc_sort_cells_enabled = True
3244
+ if binding in ("all", "sort_row"):
3245
+ self.rc_sort_row_enabled = True
3246
+ if binding in ("all", "sort_column", "sort_col"):
3247
+ self.rc_sort_column_enabled = True
3248
+ if binding in ("all", "sort_columns", "sort_cols"):
3249
+ self.rc_sort_columns_enabled = True
3250
+ if binding in ("all", "sort_rows"):
3251
+ self.rc_sort_rows_enabled = True
3075
3252
  if binding in ("all", "right_click_popup_menu", "rc_popup_menu", "rc_menu"):
3076
3253
  self.rc_popup_menus_enabled = True
3077
3254
  self.rc_select_enabled = True
@@ -3145,6 +3322,16 @@ class MainTable(tk.Canvas):
3145
3322
  self.rc_insert_column_enabled = False
3146
3323
  if binding in bind_add_rows:
3147
3324
  self.rc_insert_row_enabled = False
3325
+ if binding in ("all", "sort_cells"):
3326
+ self.rc_sort_cells_enabled = False
3327
+ if binding in ("all", "sort_row"):
3328
+ self.rc_sort_row_enabled = False
3329
+ if binding in ("all", "sort_column", "sort_col"):
3330
+ self.rc_sort_column_enabled = False
3331
+ if binding in ("all", "sort_columns", "sort_cols"):
3332
+ self.rc_sort_columns_enabled = False
3333
+ if binding in ("all", "sort_rows"):
3334
+ self.rc_sort_rows_enabled = False
3148
3335
  if binding in ("all", "right_click_popup_menu", "rc_popup_menu", "rc_menu"):
3149
3336
  self.rc_popup_menus_enabled = False
3150
3337
  if binding in ("all", "right_click_select", "rc_select"):
@@ -3290,12 +3477,12 @@ class MainTable(tk.Canvas):
3290
3477
  t, sh = self.hidd_resize_lines.popitem()
3291
3478
  self.coords(t, x1, y1, x2, y2)
3292
3479
  if sh:
3293
- self.itemconfig(t, width=width, fill=fill, tag=tag)
3480
+ self.itemconfig(t, width=width, fill=fill, tags=tag)
3294
3481
  else:
3295
- self.itemconfig(t, width=width, fill=fill, tag=tag, state="normal")
3482
+ self.itemconfig(t, width=width, fill=fill, tags=tag, state="normal")
3296
3483
  self.lift(t)
3297
3484
  else:
3298
- t = self.create_line(x1, y1, x2, y2, width=width, fill=fill, tag=tag)
3485
+ t = self.create_line(x1, y1, x2, y2, width=width, fill=fill, tags=tag)
3299
3486
  self.disp_resize_lines[t] = True
3300
3487
 
3301
3488
  def delete_resize_lines(self):
@@ -3803,38 +3990,14 @@ class MainTable(tk.Canvas):
3803
3990
  c_pc = 0.0
3804
3991
  old_min_row_height = int(self.min_row_height)
3805
3992
  old_default_row_height = int(self.get_default_row_height())
3806
- self.set_table_font(
3807
- table_font,
3808
- reset_row_positions=False,
3809
- )
3810
- self.set_index_font(index_font)
3993
+ self.set_table_font(table_font, row_heights=False)
3994
+ self.set_index_font(index_font, row_heights=False)
3811
3995
  self.set_header_font(header_font)
3812
3996
  if self.PAR.ops.set_cell_sizes_on_zoom:
3813
3997
  self.set_all_cell_sizes_to_text()
3814
3998
  self.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
3815
3999
  elif not self.PAR.ops.set_cell_sizes_on_zoom:
3816
- default_row_height = self.get_default_row_height()
3817
- self.row_positions = list(
3818
- accumulate(
3819
- chain(
3820
- [0],
3821
- (
3822
- (
3823
- self.min_row_height
3824
- if h == old_min_row_height
3825
- else (
3826
- default_row_height
3827
- if h == old_default_row_height
3828
- else self.min_row_height
3829
- if h < self.min_row_height
3830
- else h
3831
- )
3832
- )
3833
- for h in self.gen_row_heights()
3834
- ),
3835
- )
3836
- )
3837
- )
4000
+ self.change_font_manage_row_heights(old_min_row_height, old_default_row_height)
3838
4001
  self.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
3839
4002
  self.recreate_all_selection_boxes()
3840
4003
  self.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
@@ -3878,13 +4041,20 @@ class MainTable(tk.Canvas):
3878
4041
  return b[2] - b[0], b[3] - b[1]
3879
4042
 
3880
4043
  def get_lines_cell_height(self, n: int, font: None | FontTuple = None) -> int:
3881
- return (
3882
- self.get_txt_h(
4044
+ if font == self.PAR.ops.table_font:
4045
+ return 3 + (n * self.table_txt_height)
4046
+
4047
+ elif font == self.PAR.ops.index_font:
4048
+ return 3 + (n * self.index_txt_height)
4049
+
4050
+ elif font == self.PAR.ops.header_font:
4051
+ return 3 + (n * self.header_txt_height)
4052
+
4053
+ else:
4054
+ return 3 + self.get_txt_h(
3883
4055
  txt="\n".join("|" for _ in range(n)) if n > 1 else "|",
3884
- font=self.PAR.ops.table_font if font is None else font,
4056
+ font=font,
3885
4057
  )
3886
- + 5
3887
- )
3888
4058
 
3889
4059
  def set_min_column_width(self, width: int) -> None:
3890
4060
  if width:
@@ -3915,92 +4085,101 @@ class MainTable(tk.Canvas):
3915
4085
  )
3916
4086
  return self.PAR.ops.default_header_height
3917
4087
 
3918
- def set_table_font(self, newfont: tuple | None = None, reset_row_positions: bool = False) -> tuple[str, int, str]:
4088
+ def check_font(self, newfont: tuple) -> None:
4089
+ if (
4090
+ not isinstance(newfont, tuple)
4091
+ or len(newfont) != 3
4092
+ or not isinstance(newfont[0], str)
4093
+ or not isinstance(newfont[1], int)
4094
+ or not isinstance(newfont[2], str)
4095
+ ):
4096
+ raise ValueError(font_value_error)
4097
+
4098
+ def set_table_font(self, newfont: tuple | None = None, row_heights: bool = True) -> tuple[str, int, str]:
3919
4099
  if newfont:
3920
- if not isinstance(newfont, tuple):
3921
- raise ValueError("Argument must be tuple e.g. ('Carlito', 12, 'normal')")
3922
- if len(newfont) != 3:
3923
- raise ValueError("Argument must be three-tuple")
3924
- if not isinstance(newfont[0], str) or not isinstance(newfont[1], int) or not isinstance(newfont[2], str):
3925
- raise ValueError(
3926
- "Argument must be font, size and 'normal', 'bold' or'italic' e.g. ('Carlito',12,'normal')"
3927
- )
4100
+ self.check_font(newfont)
3928
4101
  self.PAR.ops.table_font = FontTuple(*newfont)
4102
+ old_min_row_height = int(self.min_row_height)
4103
+ old_default_row_height = int(self.get_default_row_height())
3929
4104
  self.set_table_font_help()
3930
- if reset_row_positions:
3931
- if isinstance(reset_row_positions, bool):
3932
- self.reset_row_positions()
3933
- else:
3934
- self.set_row_positions(itr=reset_row_positions)
4105
+ if row_heights:
4106
+ self.change_font_manage_row_heights(old_min_row_height, old_default_row_height)
3935
4107
  self.recreate_all_selection_boxes()
3936
4108
  return self.PAR.ops.table_font
3937
4109
 
3938
4110
  def set_table_font_help(self):
4111
+ self.table_font = self.PAR.ops.table_font
4112
+ if self.PAR.ops.table_font not in self.char_widths:
4113
+ self.char_widths[self.PAR.ops.table_font] = {}
4114
+ self.table_test_str_w = self.get_txt_w(_test_str)
3939
4115
  self.table_txt_width, self.table_txt_height = self.get_txt_dimensions("|", self.PAR.ops.table_font)
3940
- self.table_half_txt_height = ceil(self.table_txt_height / 2)
3941
- if not self.table_half_txt_height % 2:
3942
- self.table_first_ln_ins = self.table_half_txt_height + 2
3943
- else:
3944
- self.table_first_ln_ins = self.table_half_txt_height + 3
3945
- self.min_row_height = int(self.table_first_ln_ins * 2.22)
3946
- self.table_xtra_lines_increment = int(self.table_txt_height)
3947
- if self.min_row_height < 12:
3948
- self.min_row_height = 12
4116
+ self.min_row_height = max(6, self.table_txt_height, self.index_txt_height) + 6
3949
4117
 
3950
- def set_header_font(self, newfont: tuple | None = None) -> tuple[str, int, str]:
4118
+ def set_index_font(self, newfont: tuple | None = None, row_heights: bool = True) -> tuple[str, int, str]:
3951
4119
  if newfont:
3952
- if not isinstance(newfont, tuple):
3953
- raise ValueError("Argument must be tuple e.g. ('Carlito', 12, 'normal')")
3954
- if len(newfont) != 3:
3955
- raise ValueError("Argument must be three-tuple")
3956
- if not isinstance(newfont[0], str) or not isinstance(newfont[1], int) or not isinstance(newfont[2], str):
3957
- raise ValueError(
3958
- "Argument must be font, size and 'normal', 'bold' or'italic' e.g. ('Carlito',12,'normal')"
3959
- )
3960
- self.PAR.ops.header_font = FontTuple(*newfont)
3961
- self.set_header_font_help()
3962
- self.recreate_all_selection_boxes()
3963
- return self.PAR.ops.header_font
4120
+ self.check_font(newfont)
4121
+ self.PAR.ops.index_font = FontTuple(*newfont)
4122
+ old_min_row_height = int(self.min_row_height)
4123
+ old_default_row_height = int(self.get_default_row_height())
4124
+ self.set_index_font_help()
4125
+ if row_heights:
4126
+ self.change_font_manage_row_heights(old_min_row_height, old_default_row_height)
4127
+ self.recreate_all_selection_boxes()
4128
+ return self.PAR.ops.index_font
3964
4129
 
3965
- def set_header_font_help(self):
3966
- self.header_txt_width, self.header_txt_height = self.get_txt_dimensions("|", self.PAR.ops.header_font)
3967
- self.header_half_txt_height = ceil(self.header_txt_height / 2)
3968
- if not self.header_half_txt_height % 2:
3969
- self.header_first_ln_ins = self.header_half_txt_height + 2
3970
- else:
3971
- self.header_first_ln_ins = self.header_half_txt_height + 3
3972
- self.header_xtra_lines_increment = self.header_txt_height
3973
- self.min_header_height = int(self.header_first_ln_ins * 2.22)
3974
- if (
3975
- isinstance(self.PAR.ops.default_header_height, int)
3976
- and self.PAR.ops.default_header_height < self.min_header_height
3977
- ):
3978
- self.PAR.ops.default_header_height = int(self.min_header_height)
3979
- self.CH.set_height(self.get_default_header_height(), set_TL=True)
4130
+ def set_index_font_help(self):
4131
+ self.RI.index_font = self.PAR.ops.index_font
4132
+ if self.PAR.ops.index_font not in self.char_widths:
4133
+ self.char_widths[self.PAR.ops.index_font] = {}
4134
+ self.RI.index_test_str_w = self.get_txt_w(_test_str, self.PAR.ops.index_font)
4135
+ self.index_txt_width, self.index_txt_height = self.get_txt_dimensions("|", self.PAR.ops.index_font)
4136
+ self.min_row_height = max(6, self.table_txt_height, self.index_txt_height) + 6
3980
4137
 
3981
- def set_index_font(self, newfont: tuple | None = None) -> tuple[str, int, str]:
3982
- if newfont:
3983
- if not isinstance(newfont, tuple):
3984
- raise ValueError("Argument must be tuple e.g. ('Carlito', 12, 'normal')")
3985
- if len(newfont) != 3:
3986
- raise ValueError("Argument must be three-tuple")
3987
- if not isinstance(newfont[0], str) or not isinstance(newfont[1], int) or not isinstance(newfont[2], str):
3988
- raise ValueError(
3989
- "Argument must be font, size and 'normal', 'bold' or'italic' e.g. ('Carlito',12,'normal')"
4138
+ def change_font_manage_row_heights(self, old_min_row_height: int, old_default_row_height: int) -> None:
4139
+ default_row_height = self.get_default_row_height()
4140
+ self.row_positions = list(
4141
+ accumulate(
4142
+ chain(
4143
+ [0],
4144
+ (
4145
+ (
4146
+ self.min_row_height
4147
+ if h == old_min_row_height
4148
+ else (
4149
+ default_row_height
4150
+ if h == old_default_row_height
4151
+ else self.min_row_height
4152
+ if h < self.min_row_height
4153
+ else h
4154
+ )
4155
+ )
4156
+ for h in self.gen_row_heights()
4157
+ ),
3990
4158
  )
3991
- self.PAR.ops.index_font = FontTuple(*newfont)
3992
- self.set_index_font_help()
3993
- return self.PAR.ops.index_font
4159
+ )
4160
+ )
3994
4161
 
3995
- def set_index_font_help(self):
3996
- self.index_txt_width, self.index_txt_height = self.get_txt_dimensions("|", self.PAR.ops.index_font)
3997
- self.index_half_txt_height = ceil(self.index_txt_height / 2)
3998
- if not self.index_half_txt_height % 2:
3999
- self.index_first_ln_ins = self.index_half_txt_height + 2
4000
- else:
4001
- self.index_first_ln_ins = self.index_half_txt_height + 3
4002
- self.index_xtra_lines_increment = self.index_txt_height
4003
- self.min_index_width = 5
4162
+ def set_header_font(self, newfont: tuple | None = None) -> tuple[str, int, str]:
4163
+ if newfont:
4164
+ self.check_font(newfont)
4165
+ self.PAR.ops.header_font = FontTuple(*newfont)
4166
+ self.set_header_font_help()
4167
+ self.recreate_all_selection_boxes()
4168
+ return self.PAR.ops.header_font
4169
+
4170
+ def set_header_font_help(self):
4171
+ self.CH.header_font = self.PAR.ops.header_font
4172
+ if self.PAR.ops.header_font not in self.char_widths:
4173
+ self.char_widths[self.PAR.ops.header_font] = {}
4174
+ self.CH.header_test_str_w = self.get_txt_w(_test_str, self.PAR.ops.header_font)
4175
+ self.header_txt_width, self.header_txt_height = self.get_txt_dimensions("|", self.PAR.ops.header_font)
4176
+ self.min_header_height = self.header_txt_height + 6
4177
+ if (
4178
+ isinstance(self.PAR.ops.default_header_height, int)
4179
+ and self.PAR.ops.default_header_height < self.min_header_height
4180
+ ):
4181
+ self.PAR.ops.default_header_height = int(self.min_header_height)
4182
+ self.CH.set_height(self.get_default_header_height(), set_TL=True)
4004
4183
 
4005
4184
  def purge_undo_and_redo_stack(self):
4006
4185
  self.undo_stack = deque(maxlen=self.PAR.ops.max_undos)
@@ -4123,6 +4302,58 @@ class MainTable(tk.Canvas):
4123
4302
  else:
4124
4303
  return False
4125
4304
 
4305
+ def get_cell_max_width(self, datarn: int, dispcn: int) -> int:
4306
+ datacn = self.datacn(dispcn)
4307
+ col_width = self.col_positions[dispcn + 1] - self.col_positions[dispcn]
4308
+ if kwargs := self.get_cell_kwargs(datarn, datacn, "dropdown"):
4309
+ max_width = col_width - self.table_txt_height - 5
4310
+ else:
4311
+ max_width = col_width - 2
4312
+ if (kwargs := self.get_cell_kwargs(datarn, datacn, "dropdown")) and max_width > self.table_txt_height + 1:
4313
+ box_w = self.table_txt_height + 1
4314
+ max_width -= box_w + 4
4315
+ if self.PAR.ops.allow_cell_overflow and not kwargs:
4316
+ if self.cells_cache is None:
4317
+ disprn = self.disprn(datarn)
4318
+ self.cells_cache = self._redraw_precache_cells(disprn, disprn + 1, 0, len(self.col_positions) - 1)
4319
+ if not (align := self.get_cell_kwargs(datarn, datacn, key="align")):
4320
+ align = self.align
4321
+ if align.endswith("w"):
4322
+ max_width += sum(
4323
+ self._overflow(
4324
+ self.cells_cache,
4325
+ range(dispcn + 1, len(self.col_positions) - 1),
4326
+ datarn,
4327
+ )
4328
+ )
4329
+ elif align.endswith("e"):
4330
+ max_width += sum(
4331
+ self._overflow(
4332
+ self.cells_cache,
4333
+ reversed(range(0, dispcn)),
4334
+ datarn,
4335
+ )
4336
+ )
4337
+ return max_width
4338
+
4339
+ def get_wrapped_cell_height(self, datarn: int, datacn: int) -> int:
4340
+ dispcn = self.dispcn(datacn)
4341
+ n_lines = max(
4342
+ 1,
4343
+ sum(
4344
+ 1
4345
+ for _ in wrap_text(
4346
+ text=self.get_valid_cell_data_as_str(datarn, datacn, get_displayed=True),
4347
+ max_width=self.get_cell_max_width(datarn, dispcn),
4348
+ max_lines=float("inf"),
4349
+ char_width_fn=self.wrap_get_char_w,
4350
+ widths=self.char_widths[self.table_font],
4351
+ wrap=self.PAR.ops.table_wrap,
4352
+ )
4353
+ ),
4354
+ )
4355
+ return 3 + (n_lines * self.table_txt_height)
4356
+
4126
4357
  def set_all_cell_sizes_to_text(
4127
4358
  self,
4128
4359
  width: int | None = None,
@@ -4206,26 +4437,28 @@ class MainTable(tk.Canvas):
4206
4437
  def set_col_positions(self, itr: AnyIter[float]) -> None:
4207
4438
  self.col_positions = list(accumulate(chain([0], itr)))
4208
4439
 
4209
- def reset_col_positions(self, ncols: int | None = None):
4210
- colpos = self.PAR.ops.default_column_width
4440
+ def reset_col_positions(self, ncols: int | None = None, width: int | None = None) -> None:
4441
+ if width is None:
4442
+ width = self.PAR.ops.default_column_width
4211
4443
  if isinstance(ncols, int):
4212
- self.set_col_positions(itr=repeat(colpos, ncols))
4444
+ self.set_col_positions(itr=repeat(width, ncols))
4213
4445
  elif self.all_columns_displayed:
4214
- self.set_col_positions(itr=repeat(colpos, self.total_data_cols()))
4446
+ self.set_col_positions(itr=repeat(width, self.total_data_cols()))
4215
4447
  else:
4216
- self.set_col_positions(itr=repeat(colpos, len(self.displayed_columns)))
4448
+ self.set_col_positions(itr=repeat(width, len(self.displayed_columns)))
4217
4449
 
4218
4450
  def set_row_positions(self, itr: AnyIter[float]) -> None:
4219
4451
  self.row_positions = list(accumulate(chain([0], itr)))
4220
4452
 
4221
- def reset_row_positions(self, nrows: int | None = None):
4222
- rowpos = self.get_default_row_height()
4453
+ def reset_row_positions(self, nrows: int | None = None, height: int | None = None) -> None:
4454
+ if height is None:
4455
+ height = self.get_default_row_height()
4223
4456
  if isinstance(nrows, int):
4224
- self.set_row_positions(itr=repeat(rowpos, nrows))
4457
+ self.set_row_positions(itr=repeat(height, nrows))
4225
4458
  elif self.all_rows_displayed:
4226
- self.set_row_positions(itr=repeat(rowpos, self.total_data_rows()))
4459
+ self.set_row_positions(itr=repeat(height, self.total_data_rows()))
4227
4460
  else:
4228
- self.set_row_positions(itr=repeat(rowpos, len(self.displayed_rows)))
4461
+ self.set_row_positions(itr=repeat(height, len(self.displayed_rows)))
4229
4462
 
4230
4463
  def del_col_position(self, idx: int, deselect_all: bool = False):
4231
4464
  if deselect_all:
@@ -4277,46 +4510,6 @@ class MainTable(tk.Canvas):
4277
4510
  def gen_row_heights(self) -> Generator[int]:
4278
4511
  return diff_gen(self.row_positions)
4279
4512
 
4280
- def insert_col_position(
4281
- self,
4282
- idx: Literal["end"] | int = "end",
4283
- width: int | None = None,
4284
- deselect_all: bool = False,
4285
- ) -> None:
4286
- if deselect_all:
4287
- self.deselect("all", redraw=False)
4288
- if width is None:
4289
- w = self.PAR.ops.default_column_width
4290
- else:
4291
- w = width
4292
- if idx == "end" or len(self.col_positions) == idx + 1:
4293
- self.col_positions.append(self.col_positions[-1] + w)
4294
- else:
4295
- idx += 1
4296
- self.col_positions.insert(idx, self.col_positions[idx - 1] + w)
4297
- idx += 1
4298
- self.col_positions[idx:] = [e + w for e in islice(self.col_positions, idx, len(self.col_positions))]
4299
-
4300
- def insert_row_position(
4301
- self,
4302
- idx: Literal["end"] | int = "end",
4303
- height: int | None = None,
4304
- deselect_all: bool = False,
4305
- ) -> None:
4306
- if deselect_all:
4307
- self.deselect("all", redraw=False)
4308
- if height is None:
4309
- h = self.get_default_row_height()
4310
- else:
4311
- h = height
4312
- if idx == "end" or len(self.row_positions) == idx + 1:
4313
- self.row_positions.append(self.row_positions[-1] + h)
4314
- else:
4315
- idx += 1
4316
- self.row_positions.insert(idx, self.row_positions[idx - 1] + h)
4317
- idx += 1
4318
- self.row_positions[idx:] = [e + h for e in islice(self.row_positions, idx, len(self.row_positions))]
4319
-
4320
4513
  def insert_col_positions(
4321
4514
  self,
4322
4515
  idx: Literal["end"] | int = "end",
@@ -4403,25 +4596,13 @@ class MainTable(tk.Canvas):
4403
4596
  create_ops: bool = True,
4404
4597
  ) -> None:
4405
4598
  self.tagged_cells = {
4406
- tags: {(r, c if not (num := bisect_right(cols, c)) else c + num) for (r, c) in tagged}
4407
- for tags, tagged in self.tagged_cells.items()
4408
- }
4409
- self.cell_options = {
4410
- (r, c if not (num := bisect_right(cols, c)) else c + num): v for (r, c), v in self.cell_options.items()
4411
- }
4412
- self.progress_bars = {
4413
- (r, c if not (num := bisect_right(cols, c)) else c + num): v for (r, c), v in self.progress_bars.items()
4414
- }
4415
- self.tagged_columns = {
4416
- tags: {c if not (num := bisect_right(cols, c)) else c + num for c in tagged}
4417
- for tags, tagged in self.tagged_columns.items()
4418
- }
4419
- self.col_options = {
4420
- c if not (num := bisect_right(cols, c)) else c + num: v for c, v in self.col_options.items()
4421
- }
4422
- self.CH.cell_options = {
4423
- c if not (num := bisect_right(cols, c)) else c + num: v for c, v in self.CH.cell_options.items()
4599
+ tags: {(r, push_n(c, cols)) for (r, c) in tagged} for tags, tagged in self.tagged_cells.items()
4424
4600
  }
4601
+ self.cell_options = {(r, push_n(c, cols)): v for (r, c), v in self.cell_options.items()}
4602
+ self.progress_bars = {(r, push_n(c, cols)): v for (r, c), v in self.progress_bars.items()}
4603
+ self.tagged_columns = {tags: {push_n(c, cols) for c in tagged} for tags, tagged in self.tagged_columns.items()}
4604
+ self.col_options = {push_n(c, cols): v for c, v in self.col_options.items()}
4605
+ self.CH.cell_options = {push_n(c, cols): v for c, v in self.CH.cell_options.items()}
4425
4606
  # if there are named spans where columns were added
4426
4607
  # add options to gap which was created by adding columns
4427
4608
  totalrows = None
@@ -4471,32 +4652,18 @@ class MainTable(tk.Canvas):
4471
4652
 
4472
4653
  def adjust_options_post_add_rows(
4473
4654
  self,
4474
- rows: list | tuple,
4655
+ rows: list[int] | tuple[int],
4475
4656
  create_ops: bool = True,
4476
4657
  ) -> None:
4477
4658
  self.tagged_cells = {
4478
- tags: {(r if not (num := bisect_right(rows, r)) else r + num, c) for (r, c) in tagged}
4479
- for tags, tagged in self.tagged_cells.items()
4480
- }
4481
- self.cell_options = {
4482
- (r if not (num := bisect_right(rows, r)) else r + num, c): v for (r, c), v in self.cell_options.items()
4483
- }
4484
- self.progress_bars = {
4485
- (r if not (num := bisect_right(rows, r)) else r + num, c): v for (r, c), v in self.progress_bars.items()
4486
- }
4487
- self.tagged_rows = {
4488
- tags: {r if not (num := bisect_right(rows, r)) else r + num for r in tagged}
4489
- for tags, tagged in self.tagged_rows.items()
4490
- }
4491
- self.row_options = {
4492
- r if not (num := bisect_right(rows, r)) else r + num: v for r, v in self.row_options.items()
4493
- }
4494
- self.RI.cell_options = {
4495
- r if not (num := bisect_right(rows, r)) else r + num: v for r, v in self.RI.cell_options.items()
4496
- }
4497
- self.RI.tree_rns = {
4498
- v: r if not (num := bisect_right(rows, r)) else r + num for v, r in self.RI.tree_rns.items()
4659
+ tags: {(push_n(r, rows), c) for (r, c) in tagged} for tags, tagged in self.tagged_cells.items()
4499
4660
  }
4661
+ self.cell_options = {(push_n(r, rows), c): v for (r, c), v in self.cell_options.items()}
4662
+ self.progress_bars = {(push_n(r, rows), c): v for (r, c), v in self.progress_bars.items()}
4663
+ self.tagged_rows = {tags: {push_n(r, rows) for r in tagged} for tags, tagged in self.tagged_rows.items()}
4664
+ self.row_options = {push_n(r, rows): v for r, v in self.row_options.items()}
4665
+ self.RI.cell_options = {push_n(r, rows): v for r, v in self.RI.cell_options.items()}
4666
+ self.RI.tree_rns = {k: push_n(r, rows) for k, r in self.RI.tree_rns.items()}
4500
4667
  # if there are named spans where rows were added
4501
4668
  # add options to gap which was created by adding rows
4502
4669
  totalcols = None
@@ -4731,29 +4898,27 @@ class MainTable(tk.Canvas):
4731
4898
  header: dict,
4732
4899
  column_widths: dict,
4733
4900
  event_data: dict,
4734
- displayed_columns: None | list[int] = None,
4735
4901
  create_ops: bool = True,
4736
4902
  create_selections: bool = True,
4737
4903
  add_row_positions: bool = True,
4738
4904
  push_ops: bool = True,
4739
4905
  mod_event_boxes: bool = True,
4906
+ restored_state: bool = False,
4740
4907
  ) -> EventDataDict:
4741
4908
  self.saved_column_widths = {}
4742
- saved_displayed_columns = list(self.displayed_columns)
4743
- if isinstance(displayed_columns, list):
4744
- self.displayed_columns = displayed_columns
4745
- elif not self.all_columns_displayed:
4909
+ if not restored_state and not self.all_columns_displayed:
4746
4910
  self.displayed_columns = add_to_displayed(self.displayed_columns, columns)
4747
4911
  cws = self.get_column_widths()
4748
4912
  if column_widths and next(reversed(column_widths)) > len(cws):
4749
4913
  for i in reversed(range(len(cws), len(cws) + next(reversed(column_widths)) - len(cws))):
4750
4914
  column_widths[i] = self.PAR.ops.default_column_width
4751
- self.set_col_positions(
4752
- itr=insert_items(
4753
- cws,
4754
- column_widths,
4915
+ if not restored_state:
4916
+ self.set_col_positions(
4917
+ itr=insert_items(
4918
+ cws,
4919
+ column_widths,
4920
+ )
4755
4921
  )
4756
- )
4757
4922
  # rn needed for indexing but cn insert
4758
4923
  maxrn = 0
4759
4924
  for cn, rowdict in reversed(columns.items()):
@@ -4772,14 +4937,14 @@ class MainTable(tk.Canvas):
4772
4937
  "table": {},
4773
4938
  "index": {},
4774
4939
  "row_heights": {rn: default_height for rn in range(len(self.row_positions) - 1, maxrn + 1)},
4775
- "displayed_rows": self.displayed_rows,
4776
4940
  }
4777
- self.set_row_positions(
4778
- itr=chain(
4779
- self.gen_row_heights(),
4780
- repeat(default_height, maxrn + 1 - (len(self.row_positions) - 1)),
4941
+ if not restored_state:
4942
+ self.set_row_positions(
4943
+ itr=chain(
4944
+ self.gen_row_heights(),
4945
+ repeat(default_height, maxrn + 1 - (len(self.row_positions) - 1)),
4946
+ )
4781
4947
  )
4782
- )
4783
4948
  if isinstance(self._headers, list) and header:
4784
4949
  self._headers = insert_items(self._headers, header, self.CH.fix_header)
4785
4950
  if push_ops:
@@ -4805,7 +4970,6 @@ class MainTable(tk.Canvas):
4805
4970
  "table": columns,
4806
4971
  "header": header,
4807
4972
  "column_widths": column_widths,
4808
- "displayed_columns": saved_displayed_columns,
4809
4973
  }
4810
4974
  return event_data
4811
4975
 
@@ -4842,13 +5006,7 @@ class MainTable(tk.Canvas):
4842
5006
  numcols = self.PAR.ops.paste_insert_column_limit - len(self.col_positions) - 1
4843
5007
  if numcols < 1:
4844
5008
  return
4845
- event_data = event_dict(
4846
- name="add_columns",
4847
- sheet=self.PAR.name,
4848
- widget=self,
4849
- boxes=self.get_boxes(),
4850
- selected=self.selected,
4851
- )
5009
+ event_data = self.new_event_dict("add_columns", state=True)
4852
5010
  if not try_binding(self.extra_begin_insert_cols_rc_func, event_data, "begin_add_columns"):
4853
5011
  return
4854
5012
  event_data = self.add_columns(
@@ -4863,34 +5021,33 @@ class MainTable(tk.Canvas):
4863
5021
 
4864
5022
  def add_rows(
4865
5023
  self,
4866
- rows: dict,
4867
- index: dict,
4868
- row_heights: dict,
4869
- event_data: dict,
4870
- displayed_rows: None | list[int] = None,
5024
+ rows: dict[int, list[object]],
5025
+ index: dict[int, object],
5026
+ row_heights: dict[int, float | int],
5027
+ event_data: EventDataDict,
4871
5028
  create_ops: bool = True,
4872
5029
  create_selections: bool = True,
4873
5030
  add_col_positions: bool = True,
4874
5031
  push_ops: bool = True,
5032
+ tree: bool = True,
4875
5033
  mod_event_boxes: bool = True,
5034
+ restored_state: bool = False,
4876
5035
  ) -> EventDataDict:
4877
5036
  self.saved_row_heights = {}
4878
- saved_displayed_rows = list(self.displayed_rows)
4879
- if isinstance(displayed_rows, list):
4880
- self.displayed_rows = displayed_rows
4881
- elif not self.all_rows_displayed:
5037
+ if not restored_state and not self.all_rows_displayed:
4882
5038
  self.displayed_rows = add_to_displayed(self.displayed_rows, rows)
4883
5039
  rhs = self.get_row_heights()
4884
5040
  if row_heights and next(reversed(row_heights)) > len(rhs):
4885
5041
  default_row_height = self.get_default_row_height()
4886
5042
  for i in reversed(range(len(rhs), len(rhs) + next(reversed(row_heights)) - len(rhs))):
4887
5043
  row_heights[i] = default_row_height
4888
- self.set_row_positions(
4889
- itr=insert_items(
4890
- rhs,
4891
- row_heights,
5044
+ if not restored_state:
5045
+ self.set_row_positions(
5046
+ itr=insert_items(
5047
+ rhs,
5048
+ row_heights,
5049
+ )
4892
5050
  )
4893
- )
4894
5051
  maxcn = 0
4895
5052
  # rn needed for insert but cn indexing
4896
5053
  for rn, row in reversed(rows.items()):
@@ -4909,14 +5066,14 @@ class MainTable(tk.Canvas):
4909
5066
  "table": {},
4910
5067
  "header": {},
4911
5068
  "column_widths": {cn: default_width for cn in range(len(self.col_positions) - 1, maxcn + 1)},
4912
- "displayed_columns": self.displayed_columns,
4913
5069
  }
4914
- self.set_col_positions(
4915
- itr=chain(
4916
- self.gen_column_widths(),
4917
- repeat(default_width, maxcn + 1 - (len(self.col_positions) - 1)),
5070
+ if not restored_state:
5071
+ self.set_col_positions(
5072
+ itr=chain(
5073
+ self.gen_column_widths(),
5074
+ repeat(default_width, maxcn + 1 - (len(self.col_positions) - 1)),
5075
+ )
4918
5076
  )
4919
- )
4920
5077
  if push_ops:
4921
5078
  self.adjust_options_post_add_rows(
4922
5079
  rows=tuple(reversed(rows)),
@@ -4940,8 +5097,9 @@ class MainTable(tk.Canvas):
4940
5097
  "table": rows,
4941
5098
  "index": index,
4942
5099
  "row_heights": row_heights,
4943
- "displayed_rows": saved_displayed_rows,
4944
5100
  }
5101
+ if tree and self.PAR.ops.treeview:
5102
+ event_data = self.RI.tree_add_rows(event_data=event_data)
4945
5103
  return event_data
4946
5104
 
4947
5105
  def rc_add_rows(self, event: object = None):
@@ -4977,13 +5135,7 @@ class MainTable(tk.Canvas):
4977
5135
  numrows = self.PAR.ops.paste_insert_row_limit - len(self.row_positions) - 1
4978
5136
  if numrows < 1:
4979
5137
  return
4980
- event_data = event_dict(
4981
- name="add_rows",
4982
- sheet=self.PAR.name,
4983
- widget=self,
4984
- boxes=self.get_boxes(),
4985
- selected=self.selected,
4986
- )
5138
+ event_data = self.new_event_dict("add_rows", state=True)
4987
5139
  if not try_binding(self.extra_begin_insert_rows_rc_func, event_data, "begin_add_rows"):
4988
5140
  return
4989
5141
  event_data = self.add_rows(
@@ -5064,10 +5216,14 @@ class MainTable(tk.Canvas):
5064
5216
  for datarn, v in zip(reversed(range(data_ins_row, data_ins_row + numrows)), reversed(rows))
5065
5217
  }
5066
5218
  else:
5067
- index_data = {
5068
- datarn: self.RI.get_value_for_empty_cell(datarn, r_ops=False)
5069
- for datarn in reversed(range(data_ins_row, data_ins_row + numrows))
5070
- }
5219
+ if self.PAR.ops.treeview:
5220
+ nodes = [self.RI.get_value_for_empty_cell(data_ins_row, r_ops=False) for _ in range(numrows)]
5221
+ index_data = dict(zip(reversed(range(data_ins_row, data_ins_row + numrows)), reversed(nodes)))
5222
+ else:
5223
+ index_data = {
5224
+ datarn: self.RI.get_value_for_empty_cell(datarn, r_ops=False)
5225
+ for datarn in reversed(range(data_ins_row, data_ins_row + numrows))
5226
+ }
5071
5227
  if rows is None:
5072
5228
  if total_data_cols is None:
5073
5229
  total_data_cols = self.total_data_cols()
@@ -5107,11 +5263,27 @@ class MainTable(tk.Canvas):
5107
5263
  "tagged_columns": {f"{tag}": set(s) for tag, s in self.tagged_columns.items()},
5108
5264
  }
5109
5265
 
5110
- def delete_columns_data(self, cols: list, event_data: dict) -> EventDataDict:
5266
+ def copy_sheet_state(self) -> dict:
5267
+ return {
5268
+ "row_positions": list(self.row_positions),
5269
+ "col_positions": list(self.col_positions),
5270
+ "displayed_rows": int(self.displayed_rows)
5271
+ if isinstance(self.displayed_rows, int)
5272
+ else list(self.displayed_rows),
5273
+ "displayed_columns": int(self.displayed_columns)
5274
+ if isinstance(self.displayed_columns, int)
5275
+ else list(self.displayed_columns),
5276
+ "all_rows_displayed": bool(self.all_rows_displayed),
5277
+ "saved_row_heights": dict(self.saved_row_heights),
5278
+ "saved_column_widths": dict(self.saved_column_widths),
5279
+ "all_columns_displayed": bool(self.all_columns_displayed),
5280
+ "tree_open_ids": set(self.RI.tree_open_ids),
5281
+ }
5282
+
5283
+ def delete_columns_data(self, cols: list[int], event_data: EventDataDict | None = None) -> EventDataDict:
5111
5284
  self.mouseclick_outside_editor_or_dropdown_all_canvases()
5112
- event_data["deleted"]["displayed_columns"] = (
5113
- list(self.displayed_columns) if not isinstance(self.displayed_columns, int) else int(self.displayed_columns)
5114
- )
5285
+ if not event_data:
5286
+ event_data = self.new_event_dict("delete_columns", state=True)
5115
5287
  event_data["options"] = self.copy_options()
5116
5288
  event_data["named_spans"] = {k: span.pickle_self() for k, span in self.named_spans.items()}
5117
5289
  for datacn in reversed(cols):
@@ -5139,41 +5311,62 @@ class MainTable(tk.Canvas):
5139
5311
  ]
5140
5312
  return event_data
5141
5313
 
5142
- def delete_columns_displayed(self, cols: list, event_data: dict) -> EventDataDict:
5314
+ def delete_columns_displayed(
5315
+ self,
5316
+ cols: list[int],
5317
+ event_data: EventDataDict | None = None,
5318
+ restored_state: bool = False,
5319
+ ) -> EventDataDict:
5320
+ if not event_data:
5321
+ event_data = self.new_event_dict("delete_columns", state=True)
5143
5322
  self.saved_column_widths = {}
5144
- cols_set = set(cols)
5145
- for c in reversed(cols):
5146
- event_data["deleted"]["column_widths"][c] = self.col_positions[c + 1] - self.col_positions[c]
5147
- self.set_col_positions(itr=(width for c, width in enumerate(self.gen_column_widths()) if c not in cols_set))
5323
+ if cols:
5324
+ for c in reversed(cols):
5325
+ if len(self.col_positions) > c + 1:
5326
+ event_data["deleted"]["column_widths"][c] = self.col_positions[c + 1] - self.col_positions[c]
5327
+ if not restored_state:
5328
+ cols_set = set(cols)
5329
+ self.set_col_positions(
5330
+ itr=(width for c, width in enumerate(self.gen_column_widths()) if c not in cols_set)
5331
+ )
5148
5332
  return event_data
5149
5333
 
5150
- def rc_delete_columns(self, event: object = None):
5151
- selected = sorted(self.get_selected_cols())
5152
- if not self.selected:
5334
+ def delete_columns(
5335
+ self,
5336
+ event: object = None,
5337
+ columns: list[int] | None = None,
5338
+ data_indexes: bool = False,
5339
+ undo: bool = True,
5340
+ emit_event: bool = True,
5341
+ ext: bool = False,
5342
+ ) -> EventDataDict:
5343
+ event_data = self.new_event_dict("delete_columns", state=True)
5344
+ if not columns:
5345
+ if not (columns := sorted(self.get_selected_cols())):
5346
+ return event_data
5347
+ if not ext and not try_binding(self.extra_begin_del_cols_rc_func, event_data, "begin_delete_columns"):
5153
5348
  return
5154
- event_data = event_dict(
5155
- name="delete_columns",
5156
- sheet=self.PAR.name,
5157
- widget=self,
5158
- boxes=self.get_boxes(),
5159
- selected=self.selected,
5349
+ event_data = self.delete_columns_displayed(
5350
+ data_to_displayed_idxs(columns, self.MT.displayed_columns) if data_indexes else columns,
5351
+ event_data,
5160
5352
  )
5161
- if not try_binding(self.extra_begin_del_cols_rc_func, event_data, "begin_delete_columns"):
5162
- return
5163
- event_data = self.delete_columns_displayed(selected, event_data)
5164
- data_cols = selected if self.all_columns_displayed else [self.displayed_columns[c] for c in selected]
5165
- event_data = self.delete_columns_data(data_cols, event_data)
5166
- if self.undo_enabled:
5353
+ event_data = self.delete_columns_data(
5354
+ columns if data_indexes or self.all_columns_displayed else [self.displayed_columns[c] for c in columns],
5355
+ event_data,
5356
+ )
5357
+ if undo and self.undo_enabled:
5167
5358
  self.undo_stack.append(stored_event_dict(event_data))
5359
+ if not ext:
5360
+ try_binding(self.extra_end_del_cols_rc_func, event_data, "end_delete_columns")
5361
+ if emit_event:
5362
+ self.sheet_modified(event_data)
5168
5363
  self.deselect("all")
5169
- try_binding(self.extra_end_del_cols_rc_func, event_data, "end_delete_columns")
5170
- self.sheet_modified(event_data)
5364
+ return event_data
5171
5365
 
5172
- def delete_rows_data(self, rows: list, event_data: dict) -> EventDataDict:
5366
+ def delete_rows_data(self, rows: list[int], event_data: EventDataDict | None = None) -> EventDataDict:
5173
5367
  self.mouseclick_outside_editor_or_dropdown_all_canvases()
5174
- event_data["deleted"]["displayed_rows"] = (
5175
- list(self.displayed_rows) if not isinstance(self.displayed_rows, int) else int(self.displayed_rows)
5176
- )
5368
+ if not event_data:
5369
+ event_data = self.new_event_dict("delete_rows", state=True)
5177
5370
  event_data["options"] = self.copy_options()
5178
5371
  event_data["named_spans"] = {k: span.pickle_self() for k, span in self.named_spans.items()}
5179
5372
  for datarn in reversed(rows):
@@ -5182,6 +5375,8 @@ class MainTable(tk.Canvas):
5182
5375
  event_data["deleted"]["index"][datarn] = self._row_index.pop(datarn)
5183
5376
  except Exception:
5184
5377
  continue
5378
+ if self.PAR.ops.treeview:
5379
+ event_data = self.RI.tree_del_rows(event_data=event_data)
5185
5380
  rows_set = set(rows)
5186
5381
  self.adjust_options_post_delete_rows(
5187
5382
  to_del=rows_set,
@@ -5195,35 +5390,72 @@ class MainTable(tk.Canvas):
5195
5390
  ]
5196
5391
  return event_data
5197
5392
 
5198
- def delete_rows_displayed(self, rows: list, event_data: dict) -> EventDataDict:
5393
+ def delete_rows_displayed(
5394
+ self,
5395
+ rows: list[int],
5396
+ event_data: EventDataDict | None = None,
5397
+ restored_state: bool = False,
5398
+ ) -> EventDataDict:
5399
+ if not event_data:
5400
+ event_data = self.new_event_dict("delete_rows", state=True)
5199
5401
  self.saved_row_heights = {}
5200
- rows_set = set(rows)
5201
- for r in reversed(rows):
5202
- event_data["deleted"]["row_heights"][r] = self.row_positions[r + 1] - self.row_positions[r]
5203
- self.set_row_positions(itr=(height for r, height in enumerate(self.gen_row_heights()) if r not in rows_set))
5402
+ if rows:
5403
+ for r in reversed(rows):
5404
+ if len(self.row_positions) > r + 1:
5405
+ event_data["deleted"]["row_heights"][r] = self.row_positions[r + 1] - self.row_positions[r]
5406
+ if not restored_state:
5407
+ rows_set = set(rows)
5408
+ self.set_row_positions(
5409
+ itr=(height for r, height in enumerate(self.gen_row_heights()) if r not in rows_set)
5410
+ )
5204
5411
  return event_data
5205
5412
 
5206
- def rc_delete_rows(self, event: object = None):
5207
- selected = sorted(self.get_selected_rows())
5208
- if not self.selected:
5413
+ def delete_rows(
5414
+ self,
5415
+ event: object = None,
5416
+ rows: list[int] | None = None,
5417
+ data_indexes: bool = False,
5418
+ undo: bool = True,
5419
+ emit_event: bool = True,
5420
+ ext: bool = False,
5421
+ ) -> EventDataDict:
5422
+ event_data = self.new_event_dict("delete_rows", state=True)
5423
+ if not rows:
5424
+ if not (rows := sorted(self.get_selected_rows())):
5425
+ return
5426
+ if not ext and not try_binding(self.extra_begin_del_rows_rc_func, event_data, "begin_delete_rows"):
5209
5427
  return
5210
- event_data = event_dict(
5211
- name="delete_rows",
5212
- sheet=self.PAR.name,
5213
- widget=self,
5214
- boxes=self.get_boxes(),
5215
- selected=self.selected,
5428
+ if data_indexes or self.all_rows_displayed:
5429
+ data_rows = rows
5430
+ else:
5431
+ data_rows = [self.displayed_rows[r] for r in rows]
5432
+ if self.PAR.ops.treeview:
5433
+ data_rows = sorted(
5434
+ chain(
5435
+ data_rows,
5436
+ (
5437
+ self.RI.tree_rns[did]
5438
+ for r in data_rows
5439
+ for did in self.RI.get_iid_descendants(self._row_index[r].iid)
5440
+ ),
5441
+ )
5442
+ )
5443
+ event_data = self.delete_rows_displayed(
5444
+ data_to_displayed_idxs(data_rows, self.displayed_rows) if self.PAR.ops.treeview or data_indexes else rows,
5445
+ event_data,
5216
5446
  )
5217
- if not try_binding(self.extra_begin_del_rows_rc_func, event_data, "begin_delete_rows"):
5218
- return
5219
- event_data = self.delete_rows_displayed(selected, event_data)
5220
- data_rows = selected if self.all_rows_displayed else [self.displayed_rows[r] for r in selected]
5221
- event_data = self.delete_rows_data(data_rows, event_data)
5222
- if self.undo_enabled:
5447
+ event_data = self.delete_rows_data(
5448
+ data_rows,
5449
+ event_data,
5450
+ )
5451
+ if undo and self.undo_enabled:
5223
5452
  self.undo_stack.append(stored_event_dict(event_data))
5453
+ if not ext:
5454
+ try_binding(self.extra_end_del_rows_rc_func, event_data, "end_delete_rows")
5455
+ if emit_event:
5456
+ self.sheet_modified(event_data)
5224
5457
  self.deselect("all")
5225
- try_binding(self.extra_end_del_rows_rc_func, event_data, "end_delete_rows")
5226
- self.sheet_modified(event_data)
5458
+ return event_data
5227
5459
 
5228
5460
  def move_row_position(self, idx1: int, idx2: int):
5229
5461
  if not len(self.row_positions) <= 2:
@@ -5495,7 +5727,7 @@ class MainTable(tk.Canvas):
5495
5727
  can_width: int | None,
5496
5728
  dont_blend: bool,
5497
5729
  alternate_color: Highlight | None,
5498
- ) -> str:
5730
+ ) -> tuple[str, bool]:
5499
5731
  redrawn = False
5500
5732
  if (datarn, datacn) in self.progress_bars:
5501
5733
  kwargs = self.progress_bars[(datarn, datacn)]
@@ -5574,7 +5806,6 @@ class MainTable(tk.Canvas):
5574
5806
  if self.get_cell_kwargs(datarn, datacn, key="dropdown") and self.PAR.ops.show_dropdown_borders
5575
5807
  else ""
5576
5808
  ),
5577
- tag="hi",
5578
5809
  )
5579
5810
  if isinstance(kwargs, ProgressBar):
5580
5811
  if kwargs.del_when_done and kwargs.percent >= 100:
@@ -5600,7 +5831,17 @@ class MainTable(tk.Canvas):
5600
5831
  txtfg = self.PAR.ops.table_fg
5601
5832
  return txtfg, redrawn
5602
5833
 
5603
- def redraw_highlight(self, x1, y1, x2, y2, fill, outline, tag, can_width=None, pc=None):
5834
+ def redraw_highlight(
5835
+ self,
5836
+ x1: int | float,
5837
+ y1: int | float,
5838
+ x2: int | float,
5839
+ y2: int | float,
5840
+ fill: str,
5841
+ outline: str,
5842
+ can_width: None | float = None,
5843
+ pc: None | float = None,
5844
+ ) -> bool:
5604
5845
  if not is_type_int(pc) or pc >= 100:
5605
5846
  coords = (
5606
5847
  x1 - 1 if outline else x1,
@@ -5618,66 +5859,59 @@ class MainTable(tk.Canvas):
5618
5859
  if showing:
5619
5860
  self.itemconfig(iid, fill=fill, outline=outline)
5620
5861
  else:
5621
- self.itemconfig(iid, fill=fill, outline=outline, tag=tag, state="normal")
5862
+ self.itemconfig(iid, fill=fill, outline=outline, state="normal")
5622
5863
  else:
5623
- iid = self.create_rectangle(coords, fill=fill, outline=outline, tag=tag)
5864
+ iid = self.create_rectangle(coords, fill=fill, outline=outline, tags="h")
5624
5865
  self.disp_high[iid] = True
5625
5866
  return True
5626
5867
 
5627
- def redraw_gridline(
5628
- self,
5629
- points,
5630
- fill,
5631
- width,
5632
- tag,
5633
- ):
5634
- if self.hidd_grid:
5635
- iid, sh = self.hidd_grid.popitem()
5636
- self.coords(iid, points)
5637
- if sh:
5638
- self.itemconfig(
5639
- iid,
5640
- fill=fill,
5641
- width=width,
5642
- capstyle=tk.BUTT,
5643
- joinstyle=tk.ROUND,
5644
- )
5868
+ def redraw_gridline(self, points: list[float]) -> None:
5869
+ if points:
5870
+ if self.hidd_grid:
5871
+ iid, sh = self.hidd_grid.popitem()
5872
+ self.coords(iid, points)
5873
+ if sh:
5874
+ self.itemconfig(
5875
+ iid,
5876
+ fill=self.PAR.ops.table_grid_fg,
5877
+ width=1,
5878
+ capstyle=tk.BUTT,
5879
+ joinstyle=tk.ROUND,
5880
+ )
5881
+ else:
5882
+ self.itemconfig(
5883
+ iid,
5884
+ fill=self.PAR.ops.table_grid_fg,
5885
+ width=1,
5886
+ capstyle=tk.BUTT,
5887
+ joinstyle=tk.ROUND,
5888
+ state="normal",
5889
+ )
5645
5890
  else:
5646
- self.itemconfig(
5647
- iid,
5648
- fill=fill,
5649
- width=width,
5891
+ iid = self.create_line(
5892
+ points,
5893
+ fill=self.PAR.ops.table_grid_fg,
5894
+ width=1,
5650
5895
  capstyle=tk.BUTT,
5651
5896
  joinstyle=tk.ROUND,
5652
- state="normal",
5897
+ tag="g",
5653
5898
  )
5654
- else:
5655
- iid = self.create_line(
5656
- points,
5657
- fill=fill,
5658
- width=width,
5659
- capstyle=tk.BUTT,
5660
- joinstyle=tk.ROUND,
5661
- tag=tag,
5662
- )
5663
- self.disp_grid[iid] = True
5664
- return iid
5899
+ self.disp_grid[iid] = True
5665
5900
 
5666
5901
  def redraw_dropdown(
5667
5902
  self,
5668
- x1,
5669
- y1,
5670
- x2,
5671
- y2,
5672
- fill,
5673
- outline,
5674
- tag,
5675
- draw_outline=True,
5676
- draw_arrow=True,
5677
- open_=False,
5678
- ):
5903
+ x1: int | float,
5904
+ y1: int | float,
5905
+ x2: int | float,
5906
+ y2: int | float,
5907
+ fill: str,
5908
+ outline: str,
5909
+ draw_outline: bool = True,
5910
+ draw_arrow: bool = True,
5911
+ open_: bool = False,
5912
+ ) -> None:
5679
5913
  if draw_outline and self.PAR.ops.show_dropdown_borders:
5680
- self.redraw_highlight(x1 + 1, y1 + 1, x2, y2, fill="", outline=self.PAR.ops.table_fg, tag=tag)
5914
+ self.redraw_highlight(x1 + 1, y1 + 1, x2, y2, fill="", outline=self.PAR.ops.table_fg)
5681
5915
  if draw_arrow:
5682
5916
  mod = (self.table_txt_height - 1) if self.table_txt_height % 2 else self.table_txt_height
5683
5917
  small_mod = int(mod / 5)
@@ -5708,13 +5942,12 @@ class MainTable(tk.Canvas):
5708
5942
  if sh:
5709
5943
  self.itemconfig(t, fill=fill)
5710
5944
  else:
5711
- self.itemconfig(t, fill=fill, tag=tag, state="normal")
5945
+ self.itemconfig(t, fill=fill, state="normal")
5712
5946
  self.lift(t)
5713
5947
  else:
5714
5948
  t = self.create_line(
5715
5949
  points,
5716
5950
  fill=fill,
5717
- tag=tag,
5718
5951
  width=2,
5719
5952
  capstyle=tk.ROUND,
5720
5953
  joinstyle=tk.BEVEL,
@@ -5729,7 +5962,6 @@ class MainTable(tk.Canvas):
5729
5962
  y2: int | float,
5730
5963
  fill: str,
5731
5964
  outline: str,
5732
- tag: str | tuple,
5733
5965
  draw_check: bool = False,
5734
5966
  ) -> None:
5735
5967
  points = rounded_box_coords(x1, y1, x2, y2)
@@ -5739,10 +5971,10 @@ class MainTable(tk.Canvas):
5739
5971
  if sh:
5740
5972
  self.itemconfig(t, fill=outline, outline=fill)
5741
5973
  else:
5742
- self.itemconfig(t, fill=outline, outline=fill, tag=tag, state="normal")
5974
+ self.itemconfig(t, fill=outline, outline=fill, state="normal")
5743
5975
  self.lift(t)
5744
5976
  else:
5745
- t = self.create_polygon(points, fill=outline, outline=fill, tag=tag, smooth=True)
5977
+ t = self.create_polygon(points, fill=outline, outline=fill, smooth=True)
5746
5978
  self.disp_checkbox[t] = True
5747
5979
  if draw_check:
5748
5980
  x1 = x1 + 4
@@ -5756,28 +5988,13 @@ class MainTable(tk.Canvas):
5756
5988
  if sh:
5757
5989
  self.itemconfig(t, fill=fill, outline=outline)
5758
5990
  else:
5759
- self.itemconfig(t, fill=fill, outline=outline, tag=tag, state="normal")
5991
+ self.itemconfig(t, fill=fill, outline=outline, state="normal")
5760
5992
  self.lift(t)
5761
5993
  else:
5762
- t = self.create_polygon(points, fill=fill, outline=outline, tag=tag, smooth=True)
5994
+ t = self.create_polygon(points, fill=fill, outline=outline, smooth=True)
5763
5995
  self.disp_checkbox[t] = True
5764
5996
 
5765
- def main_table_redraw_grid_and_text(
5766
- self,
5767
- redraw_header: bool = False,
5768
- redraw_row_index: bool = False,
5769
- redraw_table: bool = True,
5770
- setting_views: bool = False,
5771
- ) -> bool:
5772
- try:
5773
- can_width = self.winfo_width()
5774
- can_height = self.winfo_height()
5775
- except Exception:
5776
- return False
5777
- row_pos_exists = self.row_positions != [0] and self.row_positions
5778
- col_pos_exists = self.col_positions != [0] and self.col_positions
5779
- resized_cols = False
5780
- resized_rows = False
5997
+ def _auto_resize_columns(self, can_width: float, col_pos_exists: bool) -> bool:
5781
5998
  if self.PAR.ops.auto_resize_columns and self.allow_auto_resize_columns and col_pos_exists:
5782
5999
  max_w = can_width - self.PAR.ops.empty_horizontal
5783
6000
  if self.PAR.ops.auto_resize_columns < self.PAR.ops.min_column_width:
@@ -5785,7 +6002,6 @@ class MainTable(tk.Canvas):
5785
6002
  else:
5786
6003
  min_column_width = self.PAR.ops.auto_resize_columns
5787
6004
  if (len(self.col_positions) - 1) * min_column_width < max_w:
5788
- resized_cols = True
5789
6005
  change = int((max_w - self.col_positions[-1]) / (len(self.col_positions) - 1))
5790
6006
  widths = [
5791
6007
  int(b - a) + change - 1
@@ -5802,6 +6018,10 @@ class MainTable(tk.Canvas):
5802
6018
  if i not in diffs:
5803
6019
  widths[i] -= change
5804
6020
  self.col_positions = list(accumulate(chain([0], widths)))
6021
+ return True
6022
+ return False
6023
+
6024
+ def _auto_resize_rows(self, can_height: float, row_pos_exists: bool) -> bool:
5805
6025
  if self.PAR.ops.auto_resize_rows and self.allow_auto_resize_rows and row_pos_exists:
5806
6026
  max_h = can_height - self.PAR.ops.empty_vertical
5807
6027
  if self.PAR.ops.auto_resize_rows < self.min_row_height:
@@ -5809,7 +6029,6 @@ class MainTable(tk.Canvas):
5809
6029
  else:
5810
6030
  min_row_height = self.PAR.ops.auto_resize_rows
5811
6031
  if (len(self.row_positions) - 1) * min_row_height < max_h:
5812
- resized_rows = True
5813
6032
  change = int((max_h - self.row_positions[-1]) / (len(self.row_positions) - 1))
5814
6033
  heights = [
5815
6034
  int(b - a) + change - 1
@@ -5826,6 +6045,10 @@ class MainTable(tk.Canvas):
5826
6045
  if i not in diffs:
5827
6046
  heights[i] -= change
5828
6047
  self.row_positions = list(accumulate(chain([0], heights)))
6048
+ return True
6049
+ return False
6050
+
6051
+ def _manage_scroll_bars(self, can_height: float, can_width: float) -> None:
5829
6052
  if (
5830
6053
  self.PAR.ops.auto_resize_row_index is not True
5831
6054
  and can_width >= self.col_positions[-1] + self.PAR.ops.empty_horizontal
@@ -5852,6 +6075,81 @@ class MainTable(tk.Canvas):
5852
6075
  ):
5853
6076
  self.PAR.yscroll.grid(row=0, column=2, rowspan=3, sticky="nswe")
5854
6077
  self.PAR.yscroll_showing = True
6078
+
6079
+ def _overflow(
6080
+ self,
6081
+ cells: dict,
6082
+ rnge: Generator[int],
6083
+ datarn: int,
6084
+ ) -> Generator[float]:
6085
+ for c_ in rnge:
6086
+ if (
6087
+ cells[(datarn, cells["datacn"][c_])]
6088
+ or (datarn, cells["datacn"][c_]) in cells["dropdown"]
6089
+ or (datarn, cells["datacn"][c_]) in cells["checkbox"]
6090
+ ):
6091
+ return
6092
+ else:
6093
+ yield self.col_positions[c_ + 1] - self.col_positions[c_]
6094
+
6095
+ def _redraw_precache_cells(
6096
+ self,
6097
+ text_start_row: int,
6098
+ text_end_row: int,
6099
+ text_start_col: int,
6100
+ text_end_col: int,
6101
+ ) -> dict:
6102
+ cells = {"datarn": {}, "datacn": {}, "dropdown": {}, "checkbox": {}}
6103
+ for r in range(text_start_row, text_end_row):
6104
+ datarn = r if self.all_rows_displayed else self.displayed_rows[r]
6105
+ cells["datarn"][r] = datarn
6106
+ for c in range(text_start_col, text_end_col):
6107
+ if c in cells["datacn"]:
6108
+ datacn = cells["datacn"][c]
6109
+ else:
6110
+ datacn = c if self.all_columns_displayed else self.displayed_columns[c]
6111
+ cells["datacn"][c] = datacn
6112
+ if kwargs := self.get_cell_kwargs(datarn, datacn, key="dropdown"):
6113
+ cells["dropdown"][(datarn, datacn)] = kwargs
6114
+ elif kwargs := self.get_cell_kwargs(datarn, datacn, key="checkbox"):
6115
+ cells["checkbox"][(datarn, datacn)] = kwargs
6116
+ cells[(datarn, datacn)] = self.get_valid_cell_data_as_str(datarn, datacn, get_displayed=True)
6117
+ return cells
6118
+
6119
+ def wrap_get_char_w(self, c: str) -> int:
6120
+ self.txt_measure_canvas.itemconfig(
6121
+ self.txt_measure_canvas_text,
6122
+ text=_test_str + c,
6123
+ font=self.table_font,
6124
+ )
6125
+ b = self.txt_measure_canvas.bbox(self.txt_measure_canvas_text)
6126
+ if c in self.char_widths[self.table_font]:
6127
+ return self.char_widths[self.table_font][c]
6128
+ else:
6129
+ wd = b[2] - b[0] - self.table_test_str_w
6130
+ self.char_widths[self.table_font][c] = wd
6131
+ return wd
6132
+
6133
+ def main_table_redraw_grid_and_text(
6134
+ self,
6135
+ redraw_header: bool = False,
6136
+ redraw_row_index: bool = False,
6137
+ redraw_table: bool = True,
6138
+ setting_views: bool = False,
6139
+ set_scrollregion: bool = True,
6140
+ ) -> bool:
6141
+ try:
6142
+ can_width = self.winfo_width()
6143
+ can_height = self.winfo_height()
6144
+ except Exception:
6145
+ return False
6146
+ row_pos_exists = self.row_positions != [0] and self.row_positions
6147
+ col_pos_exists = self.col_positions != [0] and self.col_positions
6148
+
6149
+ resized_cols = self._auto_resize_columns(can_width=can_width, col_pos_exists=col_pos_exists)
6150
+ resized_rows = self._auto_resize_rows(can_height=can_height, row_pos_exists=row_pos_exists)
6151
+ self._manage_scroll_bars(can_height=can_height, can_width=can_width)
6152
+
5855
6153
  last_col_line_pos = self.col_positions[-1] + 1
5856
6154
  last_row_line_pos = self.row_positions[-1] + 1
5857
6155
  scrollregion = (
@@ -5860,28 +6158,33 @@ class MainTable(tk.Canvas):
5860
6158
  last_col_line_pos + self.PAR.ops.empty_horizontal + 2,
5861
6159
  last_row_line_pos + self.PAR.ops.empty_vertical + 2,
5862
6160
  )
5863
- if setting_views or scrollregion != self.scrollregion:
5864
- self.configure(scrollregion=scrollregion)
6161
+ if set_scrollregion and (setting_views or scrollregion != self.scrollregion):
6162
+ try:
6163
+ self.configure(scrollregion=scrollregion)
6164
+ except Exception:
6165
+ return False
5865
6166
  self.scrollregion = scrollregion
5866
- self.CH.configure_scrollregion(last_col_line_pos)
5867
- self.RI.configure_scrollregion(last_row_line_pos)
5868
- if setting_views:
6167
+ if (
6168
+ not self.CH.configure_scrollregion(last_col_line_pos)
6169
+ or not self.RI.configure_scrollregion(last_row_line_pos)
6170
+ or setting_views
6171
+ ):
5869
6172
  return False
6173
+
5870
6174
  scrollpos_top = self.canvasy(0)
5871
6175
  scrollpos_bot = self.canvasy(can_height)
5872
6176
  scrollpos_left = self.canvasx(0)
5873
6177
  scrollpos_right = self.canvasx(can_width)
5874
-
5875
6178
  grid_start_row = bisect_left(self.row_positions, scrollpos_top)
5876
6179
  grid_end_row = bisect_right(self.row_positions, scrollpos_bot)
5877
6180
  grid_start_col = bisect_left(self.col_positions, scrollpos_left)
5878
6181
  grid_end_col = bisect_right(self.col_positions, scrollpos_right)
5879
-
5880
6182
  text_start_row = grid_start_row - 1 if grid_start_row else grid_start_row
5881
6183
  text_end_row = grid_end_row - 1 if grid_end_row == len(self.row_positions) else grid_end_row
5882
6184
  text_start_col = grid_start_col - 1 if grid_start_col else grid_start_col
5883
6185
  text_end_col = grid_end_col - 1 if grid_end_col == len(self.col_positions) else grid_end_col
5884
6186
 
6187
+ # check if auto resizing row index
5885
6188
  changed_w = False
5886
6189
  if self.PAR.ops.auto_resize_row_index and redraw_row_index and self.show_index:
5887
6190
  changed_w = self.RI.auto_set_index_width(
@@ -5894,6 +6197,10 @@ class MainTable(tk.Canvas):
5894
6197
  for widget in (self, self.RI, self.CH, self.TL):
5895
6198
  widget.update_idletasks()
5896
6199
  return False
6200
+ # important vars
6201
+ x_stop = min(last_col_line_pos, scrollpos_right)
6202
+ y_stop = min(last_row_line_pos, scrollpos_bot)
6203
+ # manage find window
5897
6204
  if self.find_window.open:
5898
6205
  w, h, x, y = self.get_find_window_dimensions_coords(w_width=self.winfo_width())
5899
6206
  self.coords(self.find_window.canvas_id, x, y)
@@ -5903,89 +6210,73 @@ class MainTable(tk.Canvas):
5903
6210
  height=h,
5904
6211
  state="normal",
5905
6212
  )
5906
- self.hidd_text.update(self.disp_text)
5907
- self.disp_text = {}
5908
- self.hidd_high.update(self.disp_high)
5909
- self.disp_high = {}
5910
- self.hidd_grid.update(self.disp_grid)
5911
- self.disp_grid = {}
5912
- self.hidd_dropdown.update(self.disp_dropdown)
5913
- self.disp_dropdown = {}
5914
- self.hidd_checkbox.update(self.disp_checkbox)
5915
- self.disp_checkbox = {}
5916
- if last_col_line_pos > scrollpos_right:
5917
- x_stop = scrollpos_right
5918
- else:
5919
- x_stop = last_col_line_pos
5920
- if last_row_line_pos > scrollpos_bot:
5921
- y_stop = scrollpos_bot
5922
- else:
5923
- y_stop = last_row_line_pos
5924
- if redraw_table and self.PAR.ops.show_horizontal_grid and row_pos_exists:
5925
- if self.PAR.ops.horizontal_grid_to_end_of_window:
5926
- x_grid_stop = scrollpos_right + can_width
5927
- else:
5928
- if last_col_line_pos > scrollpos_right:
5929
- x_grid_stop = x_stop + 1
6213
+ # redraw table
6214
+ if redraw_table:
6215
+ # reset canvas item storage
6216
+ self.hidd_text.update(self.disp_text)
6217
+ self.disp_text = {}
6218
+ self.hidd_high.update(self.disp_high)
6219
+ self.disp_high = {}
6220
+ self.hidd_grid.update(self.disp_grid)
6221
+ self.disp_grid = {}
6222
+ self.hidd_dropdown.update(self.disp_dropdown)
6223
+ self.disp_dropdown = {}
6224
+ self.hidd_checkbox.update(self.disp_checkbox)
6225
+ self.disp_checkbox = {}
6226
+ # manage horizontal grid lines
6227
+ if self.PAR.ops.show_horizontal_grid and row_pos_exists:
6228
+ if self.PAR.ops.horizontal_grid_to_end_of_window:
6229
+ x_grid_stop = scrollpos_right + can_width
5930
6230
  else:
5931
- x_grid_stop = x_stop - 1
5932
- points = list(
5933
- chain.from_iterable(
5934
- [
5935
- (
5936
- scrollpos_left - 1,
5937
- self.row_positions[r],
5938
- x_grid_stop,
5939
- self.row_positions[r],
5940
- scrollpos_left - 1,
5941
- self.row_positions[r],
5942
- scrollpos_left - 1,
5943
- self.row_positions[r + 1] if len(self.row_positions) - 1 > r else self.row_positions[r],
5944
- )
5945
- for r in range(grid_start_row, grid_end_row)
5946
- ]
5947
- )
5948
- )
5949
- if points:
6231
+ if last_col_line_pos > scrollpos_right:
6232
+ x_grid_stop = x_stop + 1
6233
+ else:
6234
+ x_grid_stop = x_stop - 1
5950
6235
  self.redraw_gridline(
5951
- points=points,
5952
- fill=self.PAR.ops.table_grid_fg,
5953
- width=1,
5954
- tag="g",
5955
- )
5956
- if redraw_table and self.PAR.ops.show_vertical_grid and col_pos_exists:
5957
- if self.PAR.ops.vertical_grid_to_end_of_window:
5958
- y_grid_stop = scrollpos_bot + can_height
5959
- else:
5960
- if last_row_line_pos > scrollpos_bot:
5961
- y_grid_stop = y_stop + 1
5962
- else:
5963
- y_grid_stop = y_stop - 1
5964
- points = list(
5965
- chain.from_iterable(
5966
- [
5967
- (
5968
- self.col_positions[c],
5969
- scrollpos_top - 1,
5970
- self.col_positions[c],
5971
- y_grid_stop,
5972
- self.col_positions[c],
5973
- scrollpos_top - 1,
5974
- self.col_positions[c + 1] if len(self.col_positions) - 1 > c else self.col_positions[c],
5975
- scrollpos_top - 1,
6236
+ points=tuple(
6237
+ chain.from_iterable(
6238
+ (
6239
+ scrollpos_left - 1,
6240
+ self.row_positions[r],
6241
+ x_grid_stop,
6242
+ self.row_positions[r],
6243
+ scrollpos_left - 1,
6244
+ self.row_positions[r],
6245
+ scrollpos_left - 1,
6246
+ self.row_positions[r + 1] if len(self.row_positions) - 1 > r else self.row_positions[r],
6247
+ )
6248
+ for r in range(grid_start_row, grid_end_row)
5976
6249
  )
5977
- for c in range(grid_start_col, grid_end_col)
5978
- ]
6250
+ )
5979
6251
  )
5980
- )
5981
- if points:
6252
+ # manage vertical grid lines
6253
+ if self.PAR.ops.show_vertical_grid and col_pos_exists:
6254
+ if self.PAR.ops.vertical_grid_to_end_of_window:
6255
+ y_grid_stop = scrollpos_bot + can_height
6256
+ else:
6257
+ if last_row_line_pos > scrollpos_bot:
6258
+ y_grid_stop = y_stop + 1
6259
+ else:
6260
+ y_grid_stop = y_stop - 1
5982
6261
  self.redraw_gridline(
5983
- points=points,
5984
- fill=self.PAR.ops.table_grid_fg,
5985
- width=1,
5986
- tag="g",
6262
+ points=tuple(
6263
+ chain.from_iterable(
6264
+ (
6265
+ self.col_positions[c],
6266
+ scrollpos_top - 1,
6267
+ self.col_positions[c],
6268
+ y_grid_stop,
6269
+ self.col_positions[c],
6270
+ scrollpos_top - 1,
6271
+ self.col_positions[c + 1] if len(self.col_positions) - 1 > c else self.col_positions[c],
6272
+ scrollpos_top - 1,
6273
+ )
6274
+ for c in range(grid_start_col, grid_end_col)
6275
+ )
6276
+ ),
5987
6277
  )
5988
- if redraw_table:
6278
+ font = self.PAR.ops.table_font
6279
+ dd_coords = self.dropdown.get_coords()
5989
6280
  selections = self.get_redraw_selections(text_start_row, grid_end_row, text_start_col, grid_end_col)
5990
6281
  sel_cells_bg = color_tup(self.PAR.ops.table_selected_cells_bg)
5991
6282
  sel_cols_bg = color_tup(self.PAR.ops.table_selected_columns_bg)
@@ -6007,7 +6298,6 @@ class MainTable(tk.Canvas):
6007
6298
  else:
6008
6299
  alternate_color = None
6009
6300
  dont_blend = tuple()
6010
-
6011
6301
  if not self.PAR.ops.show_selected_cells_border:
6012
6302
  override = (
6013
6303
  color_tup(self.PAR.ops.table_selected_cells_fg),
@@ -6016,22 +6306,23 @@ class MainTable(tk.Canvas):
6016
6306
  )
6017
6307
  else:
6018
6308
  override = tuple()
6309
+ allow_overflow = self.PAR.ops.allow_cell_overflow
6310
+ wrap = self.PAR.ops.table_wrap
6311
+ cells = self._redraw_precache_cells(
6312
+ text_start_row=text_start_row,
6313
+ text_end_row=text_end_row,
6314
+ text_start_col=text_start_col,
6315
+ text_end_col=text_end_col,
6316
+ )
6317
+ for r in range(text_start_row, text_end_row):
6318
+ rtopgridln = self.row_positions[r]
6319
+ rbotgridln = self.row_positions[r + 1]
6320
+ datarn = cells["datarn"][r]
6019
6321
 
6020
- rows_ = tuple(range(text_start_row, text_end_row))
6021
- font = self.PAR.ops.table_font
6022
- dd_coords = self.dropdown.get_coords()
6023
- for c in range(text_start_col, text_end_col):
6024
- for r in rows_:
6025
- rtopgridln = self.row_positions[r]
6026
- rbotgridln = self.row_positions[r + 1]
6027
- if rbotgridln - rtopgridln < self.table_txt_height:
6028
- continue
6322
+ for c in range(text_start_col, text_end_col):
6029
6323
  cleftgridln = self.col_positions[c]
6030
6324
  crightgridln = self.col_positions[c + 1]
6031
-
6032
- datarn = self.datarn(r)
6033
- datacn = self.datacn(c)
6034
-
6325
+ datacn = cells["datacn"][c]
6035
6326
  fill, dd_drawn = self.redraw_highlight_get_text_fg(
6036
6327
  r=r,
6037
6328
  c=c,
@@ -6049,79 +6340,48 @@ class MainTable(tk.Canvas):
6049
6340
  dont_blend=(r, c) == dont_blend,
6050
6341
  alternate_color=alternate_color,
6051
6342
  )
6052
- align = self.get_cell_kwargs(datarn, datacn, key="align")
6053
- if align:
6054
- align = align
6055
- else:
6343
+ if not (align := self.get_cell_kwargs(datarn, datacn, key="align")):
6056
6344
  align = self.align
6057
- kwargs = self.get_cell_kwargs(datarn, datacn, key="dropdown")
6058
- if align == "w":
6059
- draw_x = cleftgridln + 3
6060
- if kwargs:
6061
- mw = crightgridln - cleftgridln - self.table_txt_height - 2
6062
- self.redraw_dropdown(
6063
- cleftgridln,
6064
- rtopgridln,
6065
- crightgridln,
6066
- self.row_positions[r + 1],
6067
- fill=fill if kwargs["state"] != "disabled" else self.PAR.ops.table_grid_fg,
6068
- outline=fill,
6069
- tag=f"dd_{r}_{c}",
6070
- draw_outline=not dd_drawn,
6071
- draw_arrow=mw >= 5,
6072
- open_=dd_coords == (r, c),
6073
- )
6074
- else:
6075
- mw = crightgridln - cleftgridln - 1
6076
- elif align == "e":
6077
- if kwargs:
6078
- mw = crightgridln - cleftgridln - self.table_txt_height - 2
6345
+
6346
+ if kwargs := cells["dropdown"].get((datarn, datacn), None):
6347
+ max_width = crightgridln - cleftgridln - self.table_txt_height - 5
6348
+ if align.endswith("w"):
6349
+ draw_x = cleftgridln + 2
6350
+ elif align.endswith("e"):
6079
6351
  draw_x = crightgridln - 5 - self.table_txt_height
6080
- self.redraw_dropdown(
6081
- cleftgridln,
6082
- rtopgridln,
6083
- crightgridln,
6084
- self.row_positions[r + 1],
6085
- fill=fill if kwargs["state"] != "disabled" else self.PAR.ops.table_grid_fg,
6086
- outline=fill,
6087
- tag=f"dd_{r}_{c}",
6088
- draw_outline=not dd_drawn,
6089
- draw_arrow=mw >= 5,
6090
- open_=dd_coords == (r, c),
6091
- )
6092
- else:
6093
- mw = crightgridln - cleftgridln - 1
6094
- draw_x = crightgridln - 3
6095
- elif align == "center":
6096
- if kwargs:
6097
- mw = crightgridln - cleftgridln - self.table_txt_height - 2
6352
+ elif align.endswith("n"):
6098
6353
  draw_x = cleftgridln + ceil((crightgridln - cleftgridln - self.table_txt_height) / 2)
6099
- self.redraw_dropdown(
6100
- cleftgridln,
6101
- rtopgridln,
6102
- crightgridln,
6103
- self.row_positions[r + 1],
6104
- fill=fill if kwargs["state"] != "disabled" else self.PAR.ops.table_grid_fg,
6105
- outline=fill,
6106
- tag=f"dd_{r}_{c}",
6107
- draw_outline=not dd_drawn,
6108
- draw_arrow=mw >= 5,
6109
- open_=dd_coords == (r, c),
6110
- )
6111
- else:
6112
- mw = crightgridln - cleftgridln - 1
6354
+ self.redraw_dropdown(
6355
+ cleftgridln,
6356
+ rtopgridln,
6357
+ crightgridln,
6358
+ self.row_positions[r + 1],
6359
+ fill=fill if kwargs["state"] != "disabled" else self.PAR.ops.table_grid_fg,
6360
+ outline=fill,
6361
+ draw_outline=not dd_drawn,
6362
+ draw_arrow=max_width >= 5,
6363
+ open_=dd_coords == (r, c),
6364
+ )
6365
+ else:
6366
+ max_width = crightgridln - cleftgridln - 2
6367
+ if align.endswith("w"):
6368
+ draw_x = cleftgridln + 2
6369
+ elif align.endswith("e"):
6370
+ draw_x = crightgridln - 2
6371
+ elif align.endswith("n"):
6113
6372
  draw_x = cleftgridln + floor((crightgridln - cleftgridln) / 2)
6114
- if not kwargs:
6115
- kwargs = self.get_cell_kwargs(datarn, datacn, key="checkbox")
6116
- if kwargs and mw > self.table_txt_height + 1:
6373
+
6374
+ if (
6375
+ kwargs := cells["checkbox"].get((datarn, datacn), None)
6376
+ ) and max_width > self.table_txt_height + 1:
6117
6377
  box_w = self.table_txt_height + 1
6118
- if align == "w":
6378
+ if align.endswith("w"):
6119
6379
  draw_x += box_w + 3
6120
- elif align == "center":
6380
+ elif align.endswith("n"):
6121
6381
  draw_x += ceil(box_w / 2) + 1
6122
- mw -= box_w + 3
6382
+ max_width -= box_w + 4
6123
6383
  try:
6124
- draw_check = self.data[datarn][datacn]
6384
+ draw_check = bool(self.data[datarn][datacn])
6125
6385
  except Exception:
6126
6386
  draw_check = False
6127
6387
  self.redraw_checkbox(
@@ -6131,92 +6391,105 @@ class MainTable(tk.Canvas):
6131
6391
  rtopgridln + self.table_txt_height + 3,
6132
6392
  fill=fill if kwargs["state"] == "normal" else self.PAR.ops.table_grid_fg,
6133
6393
  outline="",
6134
- tag="cb",
6135
6394
  draw_check=draw_check,
6136
6395
  )
6137
- lns = self.get_valid_cell_data_as_str(datarn, datacn, get_displayed=True).split("\n")
6396
+ text = cells[(datarn, datacn)]
6138
6397
  if (
6139
- lns != [""]
6140
- and mw > self.table_txt_width
6141
- and not (
6142
- (align == "w" and draw_x > scrollpos_right)
6143
- or (align == "e" and cleftgridln + 5 > scrollpos_right)
6144
- or (align == "center" and cleftgridln + 5 > scrollpos_right)
6145
- )
6398
+ not text
6399
+ or (align.endswith("w") and draw_x > scrollpos_right)
6400
+ or (align.endswith("e") and cleftgridln + 5 > scrollpos_right)
6401
+ or (align.endswith("n") and cleftgridln + 5 > scrollpos_right)
6146
6402
  ):
6147
- draw_y = rtopgridln + self.table_first_ln_ins
6148
- start_ln = int((scrollpos_top - rtopgridln) / self.table_xtra_lines_increment)
6149
- if start_ln < 0:
6150
- start_ln = 0
6151
- draw_y += start_ln * self.table_xtra_lines_increment
6152
- if draw_y + self.table_half_txt_height - 1 <= rbotgridln and len(lns) > start_ln:
6153
- for txt in islice(lns, start_ln, None):
6154
- if self.hidd_text:
6155
- iid, showing = self.hidd_text.popitem()
6156
- self.coords(iid, draw_x, draw_y)
6157
- if showing:
6158
- self.itemconfig(
6159
- iid,
6160
- text=txt,
6161
- fill=fill,
6162
- font=font,
6163
- anchor=align,
6164
- )
6165
- else:
6166
- self.itemconfig(
6167
- iid,
6168
- text=txt,
6169
- fill=fill,
6170
- font=font,
6171
- anchor=align,
6172
- state="normal",
6173
- )
6174
- self.tag_raise(iid)
6403
+ continue
6404
+ if allow_overflow and not kwargs:
6405
+ if align.endswith("w"):
6406
+ max_width += sum(self._overflow(cells, range(c + 1, text_end_col), datarn))
6407
+ elif align.endswith("e"):
6408
+ max_width += sum(self._overflow(cells, reversed(range(text_start_col, c)), datarn))
6409
+ elif align.endswith("n"):
6410
+ ...
6411
+ if max_width <= 1:
6412
+ continue
6413
+ start_line = max(0, int((scrollpos_top - rtopgridln) / self.table_txt_height))
6414
+ draw_y = rtopgridln + 3 + (start_line * self.table_txt_height)
6415
+ gen_lines = wrap_text(
6416
+ text=text,
6417
+ max_width=max_width,
6418
+ max_lines=int((rbotgridln - rtopgridln - 2) / self.table_txt_height),
6419
+ char_width_fn=self.wrap_get_char_w,
6420
+ widths=self.char_widths[font],
6421
+ wrap=wrap,
6422
+ start_line=start_line,
6423
+ )
6424
+ if align.endswith(("w", "e")):
6425
+ if self.hidd_text:
6426
+ iid, showing = self.hidd_text.popitem()
6427
+ self.coords(iid, draw_x, draw_y)
6428
+ if showing:
6429
+ self.itemconfig(
6430
+ iid,
6431
+ text="\n".join(gen_lines),
6432
+ fill=fill,
6433
+ font=font,
6434
+ anchor=align,
6435
+ )
6436
+ else:
6437
+ self.itemconfig(
6438
+ iid,
6439
+ text="\n".join(gen_lines),
6440
+ fill=fill,
6441
+ font=font,
6442
+ anchor=align,
6443
+ state="normal",
6444
+ )
6445
+ self.tag_raise(iid)
6446
+ else:
6447
+ iid = self.create_text(
6448
+ draw_x,
6449
+ draw_y,
6450
+ text="\n".join(gen_lines),
6451
+ fill=fill,
6452
+ font=font,
6453
+ anchor=align,
6454
+ tags="t",
6455
+ )
6456
+ self.disp_text[iid] = True
6457
+
6458
+ elif align.endswith("n"):
6459
+ for text in gen_lines:
6460
+ if self.hidd_text:
6461
+ iid, showing = self.hidd_text.popitem()
6462
+ self.coords(iid, draw_x, draw_y)
6463
+ if showing:
6464
+ self.itemconfig(
6465
+ iid,
6466
+ text=text,
6467
+ fill=fill,
6468
+ font=font,
6469
+ anchor=align,
6470
+ )
6175
6471
  else:
6176
- iid = self.create_text(
6177
- draw_x,
6178
- draw_y,
6179
- text=txt,
6472
+ self.itemconfig(
6473
+ iid,
6474
+ text=text,
6180
6475
  fill=fill,
6181
6476
  font=font,
6182
6477
  anchor=align,
6183
- tag="t",
6478
+ state="normal",
6184
6479
  )
6185
- self.disp_text[iid] = True
6186
- wd = self.bbox(iid)
6187
- wd = wd[2] - wd[0]
6188
- if wd > mw:
6189
- if align == "w":
6190
- txt = txt[: int(len(txt) * (mw / wd))]
6191
- self.itemconfig(iid, text=txt)
6192
- wd = self.bbox(iid)
6193
- while wd[2] - wd[0] > mw:
6194
- txt = txt[:-1]
6195
- self.itemconfig(iid, text=txt)
6196
- wd = self.bbox(iid)
6197
- elif align == "e":
6198
- txt = txt[len(txt) - int(len(txt) * (mw / wd)) :]
6199
- self.itemconfig(iid, text=txt)
6200
- wd = self.bbox(iid)
6201
- while wd[2] - wd[0] > mw:
6202
- txt = txt[1:]
6203
- self.itemconfig(iid, text=txt)
6204
- wd = self.bbox(iid)
6205
- elif align == "center":
6206
- self.c_align_cyc = cycle(self.centre_alignment_text_mod_indexes)
6207
- tmod = ceil((len(txt) - int(len(txt) * (mw / wd))) / 2)
6208
- txt = txt[tmod - 1 : -tmod]
6209
- self.itemconfig(iid, text=txt)
6210
- wd = self.bbox(iid)
6211
- while wd[2] - wd[0] > mw:
6212
- txt = txt[next(self.c_align_cyc)]
6213
- self.itemconfig(iid, text=txt)
6214
- wd = self.bbox(iid)
6215
- self.coords(iid, draw_x, draw_y)
6216
- draw_y += self.table_xtra_lines_increment
6217
- if draw_y + self.table_half_txt_height - 1 > rbotgridln:
6218
- break
6219
- if redraw_table:
6480
+ self.tag_raise(iid)
6481
+ else:
6482
+ iid = self.create_text(
6483
+ draw_x,
6484
+ draw_y,
6485
+ text=text,
6486
+ fill=fill,
6487
+ font=font,
6488
+ anchor=align,
6489
+ tags="t",
6490
+ )
6491
+ self.disp_text[iid] = True
6492
+ draw_y += self.table_txt_height
6220
6493
  for dct in (
6221
6494
  self.hidd_text,
6222
6495
  self.hidd_high,
@@ -6234,6 +6507,10 @@ class MainTable(tk.Canvas):
6234
6507
  self.tag_raise(box.bd_iid)
6235
6508
  if self.selected:
6236
6509
  self.tag_raise(self.selected.iid)
6510
+ if self.RI.disp_resize_lines:
6511
+ self.tag_raise("rh")
6512
+ if self.CH.disp_resize_lines:
6513
+ self.tag_raise("rw")
6237
6514
  if redraw_header and self.show_header:
6238
6515
  self.CH.redraw_grid_and_text(
6239
6516
  last_col_line_pos=last_col_line_pos,
@@ -6245,6 +6522,7 @@ class MainTable(tk.Canvas):
6245
6522
  text_end_col=text_end_col,
6246
6523
  scrollpos_right=scrollpos_right,
6247
6524
  col_pos_exists=col_pos_exists,
6525
+ set_scrollregion=set_scrollregion,
6248
6526
  )
6249
6527
  if redraw_row_index and self.show_index:
6250
6528
  self.RI.redraw_grid_and_text(
@@ -6257,6 +6535,7 @@ class MainTable(tk.Canvas):
6257
6535
  text_end_row=text_end_row,
6258
6536
  scrollpos_bot=scrollpos_bot,
6259
6537
  row_pos_exists=row_pos_exists,
6538
+ set_scrollregion=set_scrollregion,
6260
6539
  )
6261
6540
  event_data = {"sheetname": "", "header": redraw_header, "row_index": redraw_row_index, "table": redraw_table}
6262
6541
  self.PAR.emit_event("<<SheetRedrawn>>", data=event_data)
@@ -6313,7 +6592,7 @@ class MainTable(tk.Canvas):
6313
6592
  # set current to a particular existing selection box
6314
6593
  if isinstance(item, int) and item in self.selection_boxes:
6315
6594
  selection_box = self.selection_boxes[item]
6316
- r1, c1, r2, c2 = selection_box.coords
6595
+ r1, c1, _, _ = selection_box.coords
6317
6596
  if box_created(r1 if r is None else r, c1 if c is None else c, selection_box):
6318
6597
  return
6319
6598
 
@@ -6732,7 +7011,7 @@ class MainTable(tk.Canvas):
6732
7011
 
6733
7012
  def get_redraw_selections(self, startr: int, endr: int, startc: int, endc: int) -> dict:
6734
7013
  d = defaultdict(set)
6735
- for item, box in self.get_selection_items():
7014
+ for _, box in self.get_selection_items():
6736
7015
  r1, c1, r2, c2 = box.coords
6737
7016
  if box.type_ == "cells":
6738
7017
  for r in range(startr, endr):
@@ -6770,7 +7049,7 @@ class MainTable(tk.Canvas):
6770
7049
  if get_cells:
6771
7050
  s = {
6772
7051
  (r, c)
6773
- for item, box in self.get_selection_items(cells=False, columns=False)
7052
+ for _, box in self.get_selection_items(cells=False, columns=False)
6774
7053
  for r in range(box.coords.from_r, box.coords.upto_r)
6775
7054
  for c in range(0, len(self.col_positions) - 1)
6776
7055
  }
@@ -6779,7 +7058,7 @@ class MainTable(tk.Canvas):
6779
7058
  else:
6780
7059
  s = {
6781
7060
  r
6782
- for item, box in self.get_selection_items(cells=False, columns=False)
7061
+ for _, box in self.get_selection_items(cells=False, columns=False)
6783
7062
  for r in range(box.coords.from_r, box.coords.upto_r)
6784
7063
  }
6785
7064
  if get_cells_as_rows:
@@ -6794,7 +7073,7 @@ class MainTable(tk.Canvas):
6794
7073
  if get_cells:
6795
7074
  s = {
6796
7075
  (r, c)
6797
- for item, box in self.get_selection_items(cells=False, rows=False)
7076
+ for _, box in self.get_selection_items(cells=False, rows=False)
6798
7077
  for r in range(0, len(self.row_positions) - 1)
6799
7078
  for c in range(box.coords.from_c, box.coords.upto_c)
6800
7079
  }
@@ -6803,7 +7082,7 @@ class MainTable(tk.Canvas):
6803
7082
  else:
6804
7083
  s = {
6805
7084
  c
6806
- for item, box in self.get_selection_items(cells=False, rows=False)
7085
+ for _, box in self.get_selection_items(cells=False, rows=False)
6807
7086
  for c in range(box.coords.from_c, box.coords.upto_c)
6808
7087
  }
6809
7088
  if get_cells_as_cols:
@@ -6817,7 +7096,7 @@ class MainTable(tk.Canvas):
6817
7096
  ) -> set[tuple[int, int]]:
6818
7097
  return {
6819
7098
  (r, c)
6820
- for item, box in self.get_selection_items(rows=get_rows, columns=get_cols)
7099
+ for _, box in self.get_selection_items(rows=get_rows, columns=get_cols)
6821
7100
  for r in range(box.coords.from_r, box.coords.upto_r)
6822
7101
  for c in range(box.coords.from_c, box.coords.upto_c)
6823
7102
  }
@@ -6829,16 +7108,16 @@ class MainTable(tk.Canvas):
6829
7108
  ) -> Generator[tuple[int, int]]:
6830
7109
  yield from (
6831
7110
  (r, c)
6832
- for item, box in self.get_selection_items(rows=get_rows, columns=get_cols)
7111
+ for _, box in self.get_selection_items(rows=get_rows, columns=get_cols)
6833
7112
  for r in range(box.coords.from_r, box.coords.upto_r)
6834
7113
  for c in range(box.coords.from_c, box.coords.upto_c)
6835
7114
  )
6836
7115
 
6837
7116
  def get_all_selection_boxes(self) -> tuple[tuple[int, int, int, int]]:
6838
- return tuple(box.coords for item, box in self.get_selection_items())
7117
+ return tuple(box.coords for _, box in self.get_selection_items())
6839
7118
 
6840
7119
  def get_all_selection_boxes_with_types(self) -> list[tuple[tuple[int, int, int, int], str]]:
6841
- return [Box_st(box.coords, box.type_) for item, box in self.get_selection_items()]
7120
+ return [Box_st(box.coords, box.type_) for _, box in self.get_selection_items()]
6842
7121
 
6843
7122
  def all_selected(self) -> bool:
6844
7123
  return any(
@@ -6858,7 +7137,7 @@ class MainTable(tk.Canvas):
6858
7137
  and isinstance(c, int)
6859
7138
  and any(
6860
7139
  box.coords.from_r <= r and box.coords.upto_r > r and box.coords.from_c <= c and box.coords.upto_c > c
6861
- for item, box in self.get_selection_items(
7140
+ for _, box in self.get_selection_items(
6862
7141
  rows=inc_rows,
6863
7142
  columns=inc_cols,
6864
7143
  )
@@ -6868,7 +7147,7 @@ class MainTable(tk.Canvas):
6868
7147
  def col_selected(self, c: int, cells: bool = False) -> bool:
6869
7148
  return isinstance(c, int) and any(
6870
7149
  box.coords.from_c <= c and box.coords.upto_c > c
6871
- for item, box in self.get_selection_items(
7150
+ for _, box in self.get_selection_items(
6872
7151
  cells=cells,
6873
7152
  rows=False,
6874
7153
  )
@@ -6877,7 +7156,7 @@ class MainTable(tk.Canvas):
6877
7156
  def row_selected(self, r: int, cells: bool = False) -> bool:
6878
7157
  return isinstance(r, int) and any(
6879
7158
  box.coords.from_r <= r and box.coords.upto_r > r
6880
- for item, box in self.get_selection_items(
7159
+ for _, box in self.get_selection_items(
6881
7160
  cells=cells,
6882
7161
  columns=False,
6883
7162
  )
@@ -6891,7 +7170,7 @@ class MainTable(tk.Canvas):
6891
7170
  ) -> list[int]:
6892
7171
  return [
6893
7172
  item
6894
- for item, box in self.get_selection_items(
7173
+ for item, _ in self.get_selection_items(
6895
7174
  columns=not exclude_columns,
6896
7175
  rows=not exclude_rows,
6897
7176
  cells=not exclude_cells,
@@ -6981,7 +7260,7 @@ class MainTable(tk.Canvas):
6981
7260
  return False
6982
7261
  self.hide_text_editor()
6983
7262
  if not self.see(r=r, c=c, check_cell_visibility=True):
6984
- self.refresh()
7263
+ self.main_table_redraw_grid_and_text(True, True)
6985
7264
  x = self.col_positions[c]
6986
7265
  y = self.row_positions[r]
6987
7266
  w = self.col_positions[c + 1] - x + 1
@@ -7049,10 +7328,10 @@ class MainTable(tk.Canvas):
7049
7328
  )
7050
7329
  > curr_height
7051
7330
  ):
7052
- new_height = curr_height + self.table_xtra_lines_increment
7053
- space_bot = self.get_space_bot(r)
7054
- if new_height > space_bot:
7055
- new_height = space_bot
7331
+ new_height = min(
7332
+ curr_height + self.table_txt_height,
7333
+ self.scrollregion[3] - self.scrollregion[1] - self.row_positions[r],
7334
+ )
7056
7335
  if new_height != curr_height:
7057
7336
  self.text_editor.window.config(height=new_height)
7058
7337
  if self.dropdown.open and self.dropdown.get_coords() == (r, c):
@@ -7287,10 +7566,8 @@ class MainTable(tk.Canvas):
7287
7566
  sheet_h = int(
7288
7567
  self.row_positions[-1] + 1 + self.PAR.ops.empty_vertical - (self.row_positions[r] + text_editor_h)
7289
7568
  )
7290
- if win_h > 0:
7291
- win_h -= 1
7292
- if sheet_h > 0:
7293
- sheet_h -= 1
7569
+ win_h = max(0, win_h - 1)
7570
+ sheet_h = max(0, sheet_h - 1)
7294
7571
  return win_h if win_h >= sheet_h else sheet_h
7295
7572
 
7296
7573
  def get_dropdown_height_anchor(self, r: int, c: int, text_editor_h: int | None = None) -> tuple:
@@ -7299,13 +7576,12 @@ class MainTable(tk.Canvas):
7299
7576
  for i, v in enumerate(self.get_cell_kwargs(datarn, datacn, key="dropdown")["values"]):
7300
7577
  v_numlines = len(v.split("\n") if isinstance(v, str) else f"{v}".split("\n"))
7301
7578
  if v_numlines > 1:
7302
- win_h += self.table_first_ln_ins + (v_numlines * self.table_xtra_lines_increment) + 5 # end of cell
7579
+ win_h += 8 + (v_numlines * self.table_txt_height) # end of cell
7303
7580
  else:
7304
7581
  win_h += self.min_row_height
7305
7582
  if i == 5:
7306
7583
  break
7307
- if win_h > 500:
7308
- win_h = 500
7584
+ win_h = min(win_h, 500)
7309
7585
  space_bot = self.get_space_bot(r, text_editor_h)
7310
7586
  space_top = int(self.row_positions[r])
7311
7587
  anchor = "nw"
@@ -7395,7 +7671,7 @@ class MainTable(tk.Canvas):
7395
7671
  self.itemconfig(self.dropdown.canvas_id, state="normal", anchor=anchor)
7396
7672
  self.dropdown.window.tkraise()
7397
7673
  else:
7398
- self.dropdown.window = self.PAR.dropdown_class(
7674
+ self.dropdown.window = self.PAR._dropdown_cls(
7399
7675
  self.winfo_toplevel(),
7400
7676
  **reset_kwargs,
7401
7677
  close_dropdown_window=self.close_dropdown_window,
@@ -7440,12 +7716,11 @@ class MainTable(tk.Canvas):
7440
7716
  datacn = self.datacn(c)
7441
7717
  datarn = self.datarn(r)
7442
7718
  kwargs = self.get_cell_kwargs(datarn, datacn, key="dropdown")
7443
- pre_edit_value = self.get_cell_data(datarn, datacn)
7444
7719
  event_data = event_dict(
7445
7720
  name="end_edit_table",
7446
7721
  sheet=self.PAR.name,
7447
7722
  widget=self,
7448
- cells_table={(datarn, datacn): pre_edit_value},
7723
+ cells_table={(datarn, datacn): self.get_cell_data(datarn, datacn)},
7449
7724
  key="??",
7450
7725
  value=selection,
7451
7726
  loc=Loc(r, c),
@@ -7454,30 +7729,12 @@ class MainTable(tk.Canvas):
7454
7729
  boxes=self.get_boxes(),
7455
7730
  selected=self.selected,
7456
7731
  )
7457
- if kwargs["select_function"] is not None:
7458
- kwargs["select_function"](event_data)
7459
- if self.edit_validation_func:
7460
- selection, edited = self.edit_validation_func(event_data), False
7461
- if selection is not None:
7462
- edited = self.set_cell_data_undo(
7463
- r,
7464
- c,
7465
- datarn=datarn,
7466
- datacn=datacn,
7467
- value=selection,
7468
- redraw=not redraw,
7469
- )
7470
- else:
7471
- edited = self.set_cell_data_undo(
7472
- r,
7473
- c,
7474
- datarn=datarn,
7475
- datacn=datacn,
7476
- value=selection,
7477
- redraw=not redraw,
7478
- )
7479
- if edited:
7480
- try_binding(self.extra_end_edit_cell_func, event_data)
7732
+ try_binding(kwargs["select_function"], event_data)
7733
+ selection = selection if not self.edit_validation_func else self.edit_validation_func(event_data)
7734
+ if selection is not None:
7735
+ edited = self.set_cell_data_undo(r, c, datarn=datarn, datacn=datacn, value=selection, redraw=not redraw)
7736
+ if edited:
7737
+ try_binding(self.extra_end_edit_cell_func, event_data)
7481
7738
  self.recreate_all_selection_boxes()
7482
7739
  self.focus_set()
7483
7740
  self.hide_text_editor_and_dropdown(redraw=redraw)
@@ -7770,7 +8027,7 @@ class MainTable(tk.Canvas):
7770
8027
  else:
7771
8028
  # assumed given formatter class has get_data_with_valid_check()
7772
8029
  return f"{value.get_data_with_valid_check()}"
7773
- return "" if value is None else f"{value}"
8030
+ return "" if value is None else value if isinstance(value, str) else f"{value}"
7774
8031
 
7775
8032
  def get_cell_data(
7776
8033
  self,
@@ -7783,7 +8040,11 @@ class MainTable(tk.Canvas):
7783
8040
  ) -> object:
7784
8041
  if get_displayed:
7785
8042
  return self.get_valid_cell_data_as_str(datarn, datacn, get_displayed=True)
7786
- value = self.data[datarn][datacn] if len(self.data) > datarn and len(self.data[datarn]) > datacn else ""
8043
+ value = (
8044
+ self.data[datarn][datacn]
8045
+ if len(self.data) > datarn and len(self.data[datarn]) > datacn
8046
+ else self.get_value_for_empty_cell(datarn, datacn)
8047
+ )
7787
8048
  kwargs = self.get_cell_kwargs(datarn, datacn, key="format")
7788
8049
  if kwargs and kwargs["formatter"] is not None:
7789
8050
  value = value.value # assumed given formatter class has value attribute