tksheet 7.3.4__py3-none-any.whl → 7.4.1__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/row_index.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import tkinter as tk
4
4
  from collections import defaultdict
5
- from collections.abc import Generator, Hashable, Sequence
5
+ from collections.abc import Callable, Generator, Hashable, Sequence
6
6
  from functools import partial
7
7
  from itertools import chain, cycle, islice, repeat
8
8
  from math import ceil, floor
@@ -10,39 +10,53 @@ from operator import itemgetter
10
10
  from typing import Literal
11
11
 
12
12
  from .colors import color_map
13
- from .constants import rc_binding, text_editor_close_bindings, text_editor_newline_bindings, text_editor_to_unbind
13
+ from .constants import (
14
+ _test_str,
15
+ rc_binding,
16
+ text_editor_close_bindings,
17
+ text_editor_newline_bindings,
18
+ text_editor_to_unbind,
19
+ )
14
20
  from .formatters import is_bool_like, try_to_bool
15
21
  from .functions import (
16
22
  consecutive_chunks,
17
23
  consecutive_ranges,
24
+ del_placeholder_dict_key,
18
25
  event_dict,
19
26
  event_has_char_key,
20
27
  event_opens_dropdown_or_checkbox,
21
28
  get_n2a,
29
+ get_new_indexes,
22
30
  int_x_tuple,
23
31
  is_contiguous,
32
+ mod_event_val,
24
33
  new_tk_event,
25
34
  num2alpha,
26
35
  rounded_box_coords,
27
36
  stored_event_dict,
37
+ try_b_index,
28
38
  try_binding,
39
+ wrap_text,
29
40
  )
30
- from .other_classes import DotDict, DraggedRowColumn, DropdownStorage, Node, TextEditorStorage
41
+ from .other_classes import DotDict, DraggedRowColumn, DropdownStorage, EventDataDict, Node, TextEditorStorage
42
+ from .sorting import sort_columns_by_row, sort_row
31
43
  from .text_editor import TextEditor
32
- from .types import AnyIter
44
+ from .tksheet_types import AnyIter
33
45
 
34
46
 
35
47
  class RowIndex(tk.Canvas):
36
- def __init__(self, *args, **kwargs):
48
+ def __init__(self, parent, **kwargs):
37
49
  super().__init__(
38
- kwargs["parent"],
39
- background=kwargs["parent"].ops.index_bg,
50
+ parent,
51
+ background=parent.ops.index_bg,
40
52
  highlightthickness=0,
41
53
  )
42
- self.PAR = kwargs["parent"]
54
+ self.PAR = parent
55
+ self.ops = self.PAR.ops
43
56
  self.MT = None # is set from within MainTable() __init__
44
57
  self.CH = None # is set from within MainTable() __init__
45
58
  self.TL = None # is set from within TopLeftRectangle() __init__
59
+ self.new_iid_ctr = -1
46
60
  self.current_width = None
47
61
  self.popup_menu_loc = None
48
62
  self.extra_begin_edit_cell_func = None
@@ -63,6 +77,8 @@ class RowIndex(tk.Canvas):
63
77
  self.drag_selection_binding_func = None
64
78
  self.ri_extra_begin_drag_drop_func = None
65
79
  self.ri_extra_end_drag_drop_func = None
80
+ self.ri_extra_begin_sort_cols_func = None
81
+ self.ri_extra_end_sort_cols_func = None
66
82
  self.extra_double_b1_func = None
67
83
  self.row_height_resize_func = None
68
84
  self.cell_options = {}
@@ -134,18 +150,6 @@ class RowIndex(tk.Canvas):
134
150
  self.unbind("<Double-Button-1>")
135
151
  self.unbind(rc_binding)
136
152
 
137
- def tree_reset(self) -> None:
138
- # treeview mode
139
- self.tree = {}
140
- self.tree_open_ids = set()
141
- self.tree_rns = {}
142
- if self.MT:
143
- self.MT.displayed_rows = []
144
- self.MT._row_index = []
145
- self.MT.data = []
146
- self.MT.row_positions = [0]
147
- self.MT.saved_row_heights = {}
148
-
149
153
  def set_width(self, new_width: int, set_TL: bool = False, recreate_selection_boxes: bool = True) -> None:
150
154
  try:
151
155
  self.config(width=new_width)
@@ -328,9 +332,9 @@ class RowIndex(tk.Canvas):
328
332
  if (
329
333
  self.width_resizing_enabled
330
334
  and not mouse_over_resize
331
- and self.PAR.ops.auto_resize_row_index is not True
335
+ and self.ops.auto_resize_row_index is not True
332
336
  and not (
333
- self.PAR.ops.auto_resize_row_index == "empty"
337
+ self.ops.auto_resize_row_index == "empty"
334
338
  and not isinstance(self.MT._row_index, int)
335
339
  and not self.MT._row_index
336
340
  )
@@ -385,7 +389,7 @@ class RowIndex(tk.Canvas):
385
389
  )
386
390
  elif self.width_resizing_enabled and self.rsz_h is None and self.rsz_w is True:
387
391
  self.set_width_of_index_to_text()
388
- elif (self.row_selection_enabled or self.PAR.ops.treeview) and self.rsz_h is None and self.rsz_w is None:
392
+ elif (self.row_selection_enabled or self.ops.treeview) and self.rsz_h is None and self.rsz_w is None:
389
393
  r = self.MT.identify_row(y=event.y)
390
394
  if r < len(self.MT.row_positions) - 1:
391
395
  iid = self.event_over_tree_arrow(r, self.canvasy(event.y), event.x)
@@ -435,21 +439,21 @@ class RowIndex(tk.Canvas):
435
439
  self.current_width,
436
440
  y,
437
441
  width=1,
438
- fill=self.PAR.ops.resizing_line_fg,
442
+ fill=self.ops.resizing_line_fg,
439
443
  tag=("rh", "rhl"),
440
444
  )
441
- self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.PAR.ops.resizing_line_fg, tag=("rh", "rhl"))
445
+ self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.ops.resizing_line_fg, tag=("rh", "rhl"))
442
446
  self.create_resize_line(
443
447
  0,
444
448
  line2y,
445
449
  self.current_width,
446
450
  line2y,
447
451
  width=1,
448
- fill=self.PAR.ops.resizing_line_fg,
452
+ fill=self.ops.resizing_line_fg,
449
453
  tag=("rh", "rhl2"),
450
454
  )
451
455
  self.MT.create_resize_line(
452
- x1, line2y, x2, line2y, width=1, fill=self.PAR.ops.resizing_line_fg, tag=("rh", "rhl2")
456
+ x1, line2y, x2, line2y, width=1, fill=self.ops.resizing_line_fg, tag=("rh", "rhl2")
453
457
  )
454
458
  elif self.width_resizing_enabled and self.rsz_h is None and self.rsz_w is True:
455
459
  self.currently_resizing_width = True
@@ -481,7 +485,7 @@ class RowIndex(tk.Canvas):
481
485
  if self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height:
482
486
  y = self.canvasy(event.y)
483
487
  size = y - self.MT.row_positions[self.rsz_h - 1]
484
- if size >= self.MT.min_row_height and size < self.PAR.ops.max_row_height:
488
+ if size >= self.MT.min_row_height and size < self.ops.max_row_height:
485
489
  self.hide_resize_and_ctrl_lines(ctrl_lines=False)
486
490
  line2y = self.MT.row_positions[self.rsz_h - 1]
487
491
  self.create_resize_line(
@@ -490,17 +494,17 @@ class RowIndex(tk.Canvas):
490
494
  self.current_width,
491
495
  y,
492
496
  width=1,
493
- fill=self.PAR.ops.resizing_line_fg,
497
+ fill=self.ops.resizing_line_fg,
494
498
  tag=("rh", "rhl"),
495
499
  )
496
- self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.PAR.ops.resizing_line_fg, tag=("rh", "rhl"))
500
+ self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.ops.resizing_line_fg, tag=("rh", "rhl"))
497
501
  self.create_resize_line(
498
502
  0,
499
503
  line2y,
500
504
  self.current_width,
501
505
  line2y,
502
506
  width=1,
503
- fill=self.PAR.ops.resizing_line_fg,
507
+ fill=self.ops.resizing_line_fg,
504
508
  tag=("rh", "rhl2"),
505
509
  )
506
510
  self.MT.create_resize_line(
@@ -509,19 +513,19 @@ class RowIndex(tk.Canvas):
509
513
  x2,
510
514
  line2y,
511
515
  width=1,
512
- fill=self.PAR.ops.resizing_line_fg,
516
+ fill=self.ops.resizing_line_fg,
513
517
  tag=("rh", "rhl2"),
514
518
  )
515
519
  self.drag_height_resize()
516
520
  elif self.width_resizing_enabled and self.rsz_w is not None and self.currently_resizing_width:
517
521
  evx = event.x
518
522
  if evx > self.current_width:
519
- if evx > self.PAR.ops.max_index_width:
520
- evx = int(self.PAR.ops.max_index_width)
523
+ if evx > self.ops.max_index_width:
524
+ evx = int(self.ops.max_index_width)
521
525
  self.drag_width_resize(evx)
522
526
  else:
523
- if evx < self.PAR.ops.min_column_width:
524
- evx = self.PAR.ops.min_column_width
527
+ if evx < self.ops.min_column_width:
528
+ evx = self.ops.min_column_width
525
529
  self.drag_width_resize(evx)
526
530
  elif (
527
531
  self.drag_and_drop_enabled
@@ -678,16 +682,16 @@ class RowIndex(tk.Canvas):
678
682
  self.current_width,
679
683
  ypos,
680
684
  width=3,
681
- fill=self.PAR.ops.drag_and_drop_bg,
685
+ fill=self.ops.drag_and_drop_bg,
682
686
  tag="move_rows",
683
687
  )
684
- self.MT.create_resize_line(x1, ypos, x2, ypos, width=3, fill=self.PAR.ops.drag_and_drop_bg, tag="move_rows")
688
+ self.MT.create_resize_line(x1, ypos, x2, ypos, width=3, fill=self.ops.drag_and_drop_bg, tag="move_rows")
685
689
  for chunk in consecutive_chunks(rows):
686
690
  self.MT.show_ctrl_outline(
687
691
  start_cell=(0, chunk[0]),
688
692
  end_cell=(len(self.MT.col_positions) - 1, chunk[-1] + 1),
689
693
  dash=(),
690
- outline=self.PAR.ops.drag_and_drop_bg,
694
+ outline=self.ops.drag_and_drop_bg,
691
695
  delete_on_timer=False,
692
696
  )
693
697
 
@@ -751,8 +755,8 @@ class RowIndex(tk.Canvas):
751
755
  size = new_row_pos - self.MT.row_positions[self.rsz_h - 1]
752
756
  if size < self.MT.min_row_height:
753
757
  new_row_pos = ceil(self.MT.row_positions[self.rsz_h - 1] + self.MT.min_row_height)
754
- elif size > self.PAR.ops.max_row_height:
755
- new_row_pos = floor(self.MT.row_positions[self.rsz_h - 1] + self.PAR.ops.max_row_height)
758
+ elif size > self.ops.max_row_height:
759
+ new_row_pos = floor(self.MT.row_positions[self.rsz_h - 1] + self.ops.max_row_height)
756
760
  increment = new_row_pos - self.MT.row_positions[self.rsz_h]
757
761
  self.MT.row_positions[self.rsz_h + 1 :] = [
758
762
  e + increment for e in islice(self.MT.row_positions, self.rsz_h + 1, None)
@@ -761,7 +765,7 @@ class RowIndex(tk.Canvas):
761
765
  new_height = self.MT.row_positions[self.rsz_h] - self.MT.row_positions[self.rsz_h - 1]
762
766
  self.MT.allow_auto_resize_rows = False
763
767
  self.MT.recreate_all_selection_boxes()
764
- self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
768
+ self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True, set_scrollregion=False)
765
769
  if self.row_height_resize_func is not None and old_height != new_height:
766
770
  self.row_height_resize_func(
767
771
  event_dict(
@@ -787,6 +791,7 @@ class RowIndex(tk.Canvas):
787
791
  if self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height:
788
792
  self.drag_height_resize()
789
793
  self.hide_resize_and_ctrl_lines(ctrl_lines=False)
794
+ self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
790
795
  elif (
791
796
  self.drag_and_drop_enabled
792
797
  and self.MT.anything_selected(exclude_cells=True, exclude_columns=True)
@@ -812,33 +817,28 @@ class RowIndex(tk.Canvas):
812
817
  r += 1
813
818
  if r > len(self.MT.row_positions) - 1:
814
819
  r = len(self.MT.row_positions) - 1
815
- event_data = event_dict(
816
- name="move_rows",
817
- sheet=self.PAR.name,
818
- widget=self,
819
- boxes=self.MT.get_boxes(),
820
- selected=self.MT.selected,
821
- value=r,
822
- )
820
+ event_data = self.MT.new_event_dict("move_rows", state=True)
821
+ event_data["value"] = r
823
822
  if try_binding(self.ri_extra_begin_drag_drop_func, event_data, "begin_move_rows"):
824
823
  data_new_idxs, disp_new_idxs, event_data = self.MT.move_rows_adjust_options_dict(
825
824
  *self.MT.get_args_for_move_rows(
826
825
  move_to=r,
827
826
  to_move=self.dragged_row.to_move,
828
827
  ),
829
- move_data=self.PAR.ops.row_drag_and_drop_perform,
830
- move_heights=self.PAR.ops.row_drag_and_drop_perform,
828
+ move_data=self.ops.row_drag_and_drop_perform,
829
+ move_heights=self.ops.row_drag_and_drop_perform,
831
830
  event_data=event_data,
832
831
  )
833
- event_data["moved"]["rows"] = {
834
- "data": data_new_idxs,
835
- "displayed": disp_new_idxs,
836
- }
837
- if self.MT.undo_enabled:
838
- self.MT.undo_stack.append(stored_event_dict(event_data))
839
- self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
840
- try_binding(self.ri_extra_end_drag_drop_func, event_data, "end_move_rows")
841
- self.MT.sheet_modified(event_data)
832
+ if data_new_idxs and disp_new_idxs:
833
+ event_data["moved"]["rows"] = {
834
+ "data": data_new_idxs,
835
+ "displayed": disp_new_idxs,
836
+ }
837
+ if self.MT.undo_enabled:
838
+ self.MT.undo_stack.append(stored_event_dict(event_data))
839
+ self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
840
+ try_binding(self.ri_extra_end_drag_drop_func, event_data, "end_move_rows")
841
+ self.MT.sheet_modified(event_data)
842
842
  elif self.b1_pressed_loc is not None and self.rsz_w is None and self.rsz_h is None:
843
843
  r = self.MT.identify_row(y=event.y)
844
844
  if (
@@ -875,7 +875,7 @@ class RowIndex(tk.Canvas):
875
875
  canvasy: float,
876
876
  eventx: int,
877
877
  ) -> bool:
878
- if self.PAR.ops.treeview and (
878
+ if self.ops.treeview and (
879
879
  canvasy < self.MT.row_positions[r] + self.MT.index_txt_height + 5
880
880
  and isinstance(self.MT._row_index, list)
881
881
  and (datarn := self.MT.datarn(r)) < len(self.MT._row_index)
@@ -886,6 +886,96 @@ class RowIndex(tk.Canvas):
886
886
  return iid
887
887
  return None
888
888
 
889
+ def _sort_rows(
890
+ self,
891
+ event: tk.Event | None = None,
892
+ rows: AnyIter[int] | None = None,
893
+ reverse: bool = False,
894
+ validation: bool = True,
895
+ key: Callable | None = None,
896
+ undo: bool = True,
897
+ ) -> EventDataDict:
898
+ if rows is None:
899
+ rows = self.MT.get_selected_rows()
900
+ if not rows:
901
+ rows = list(range(0, len(self.MT.row_positions) - 1))
902
+ event_data = self.MT.new_event_dict("edit_table")
903
+ try_binding(self.MT.extra_begin_sort_cells_func, event_data)
904
+ for r in rows:
905
+ datarn = self.MT.datarn(r)
906
+ for c, val in enumerate(sort_row(self.MT.data[datarn], reverse=reverse, key=key)):
907
+ if (
908
+ not self.MT.edit_validation_func
909
+ or not validation
910
+ or (
911
+ self.MT.edit_validation_func
912
+ and (val := self.MT.edit_validation_func(mod_event_val(event_data, val, (datarn, c))))
913
+ is not None
914
+ )
915
+ ):
916
+ event_data = self.MT.event_data_set_cell(
917
+ datarn=datarn,
918
+ datacn=c,
919
+ value=val,
920
+ event_data=event_data,
921
+ )
922
+ if event_data["cells"]["table"]:
923
+ if undo and self.MT.undo_enabled:
924
+ self.MT.undo_stack.append(stored_event_dict(event_data))
925
+ try_binding(self.MT.extra_end_sort_cells_func, event_data, "end_edit_table")
926
+ self.MT.sheet_modified(event_data)
927
+ self.PAR.emit_event("<<SheetModified>>", event_data)
928
+ self.MT.refresh()
929
+ return event_data
930
+
931
+ def _sort_columns_by_row(
932
+ self,
933
+ event: tk.Event | None = None,
934
+ row: int | None = None,
935
+ reverse: bool = False,
936
+ key: Callable | None = None,
937
+ undo: bool = True,
938
+ ) -> EventDataDict:
939
+ event_data = self.MT.new_event_dict("sort_columns", state=True)
940
+ if not self.MT.data:
941
+ return event_data
942
+ if row is None:
943
+ if not self.MT.selected:
944
+ return event_data
945
+ row = self.MT.selected.row
946
+ if try_binding(self.ri_extra_begin_sort_cols_func, event_data, "begin_sort_columns"):
947
+ sorted_indices, data_new_idxs = sort_columns_by_row(self.MT.data, row=row, reverse=reverse, key=key)
948
+ disp_new_idxs = {}
949
+ if self.MT.all_columns_displayed:
950
+ disp_new_idxs = data_new_idxs
951
+ else:
952
+ col_ctr = 0
953
+ # idx is the displayed index, can just do range
954
+ for old_idx in sorted_indices:
955
+ if (idx := try_b_index(self.MT.displayed_columns, old_idx)) is not None:
956
+ disp_new_idxs[idx] = col_ctr
957
+ col_ctr += 1
958
+ data_new_idxs, disp_new_idxs, _ = self.PAR.mapping_move_columns(
959
+ data_new_idxs=data_new_idxs,
960
+ disp_new_idxs=disp_new_idxs,
961
+ move_data=True,
962
+ create_selections=False,
963
+ undo=False,
964
+ emit_event=False,
965
+ redraw=True,
966
+ )
967
+ event_data["moved"]["columns"] = {
968
+ "data": data_new_idxs,
969
+ "displayed": disp_new_idxs,
970
+ }
971
+ if undo and self.MT.undo_enabled:
972
+ self.MT.undo_stack.append(stored_event_dict(event_data))
973
+ try_binding(self.ri_extra_end_sort_cols_func, event_data, "end_sort_columns")
974
+ self.MT.sheet_modified(event_data)
975
+ self.PAR.emit_event("<<SheetModified>>", event_data)
976
+ self.MT.refresh()
977
+ return event_data
978
+
889
979
  def toggle_select_row(
890
980
  self,
891
981
  row: int,
@@ -974,7 +1064,7 @@ class RowIndex(tk.Canvas):
974
1064
  y1,
975
1065
  x2,
976
1066
  y2,
977
- radius=5 if self.PAR.ops.rounded_boxes else 0,
1067
+ radius=5 if self.ops.rounded_boxes else 0,
978
1068
  )
979
1069
  if isinstance(iid, int):
980
1070
  self.coords(iid, coords)
@@ -1005,27 +1095,42 @@ class RowIndex(tk.Canvas):
1005
1095
  def get_cell_dimensions(self, datarn: int) -> tuple[int, int]:
1006
1096
  txt = self.get_valid_cell_data_as_str(datarn, fix=False)
1007
1097
  if txt:
1008
- self.MT.txt_measure_canvas.itemconfig(
1009
- self.MT.txt_measure_canvas_text, text=txt, font=self.PAR.ops.index_font
1010
- )
1098
+ self.MT.txt_measure_canvas.itemconfig(self.MT.txt_measure_canvas_text, text=txt, font=self.ops.index_font)
1011
1099
  b = self.MT.txt_measure_canvas.bbox(self.MT.txt_measure_canvas_text)
1012
1100
  w = b[2] - b[0] + 7
1013
1101
  h = b[3] - b[1] + 5
1014
1102
  else:
1015
- w = self.PAR.ops.default_row_index_width
1103
+ w = self.ops.default_row_index_width
1016
1104
  h = self.MT.min_row_height
1017
1105
  if self.get_cell_kwargs(datarn, key="dropdown") or self.get_cell_kwargs(datarn, key="checkbox"):
1018
1106
  w += self.MT.index_txt_height + 2
1019
- if self.PAR.ops.treeview:
1107
+ if self.ops.treeview:
1020
1108
  if datarn in self.cell_options and "align" in self.cell_options[datarn]:
1021
1109
  align = self.cell_options[datarn]["align"]
1022
1110
  else:
1023
1111
  align = self.align
1024
- if align == "w":
1112
+ if align.endswith("w"):
1025
1113
  w += self.MT.index_txt_height
1026
1114
  w += self.get_iid_indent(self.MT._row_index[datarn].iid) + 10
1027
1115
  return w, h
1028
1116
 
1117
+ def get_wrapped_cell_height(self, datarn: int) -> int:
1118
+ n_lines = max(
1119
+ 1,
1120
+ sum(
1121
+ 1
1122
+ for _ in wrap_text(
1123
+ text=self.get_valid_cell_data_as_str(datarn, fix=False),
1124
+ max_width=self.current_width,
1125
+ max_lines=float("inf"),
1126
+ char_width_fn=self.wrap_get_char_w,
1127
+ widths=self.MT.char_widths[self.index_font],
1128
+ wrap=self.ops.index_wrap,
1129
+ )
1130
+ ),
1131
+ )
1132
+ return 3 + (n_lines * self.MT.index_txt_height)
1133
+
1029
1134
  def get_row_text_height(
1030
1135
  self,
1031
1136
  row: int,
@@ -1035,7 +1140,7 @@ class RowIndex(tk.Canvas):
1035
1140
  h = self.MT.min_row_height
1036
1141
  datarn = row if self.MT.all_rows_displayed else self.MT.displayed_rows[row]
1037
1142
  # index
1038
- _w, ih = self.get_cell_dimensions(datarn)
1143
+ ih = self.get_wrapped_cell_height(datarn)
1039
1144
  # table
1040
1145
  if self.MT.data:
1041
1146
  if self.MT.all_columns_displayed:
@@ -1052,20 +1157,22 @@ class RowIndex(tk.Canvas):
1052
1157
  else:
1053
1158
  start_col, end_col = 0, len(self.MT.displayed_columns)
1054
1159
  iterable = self.MT.displayed_columns[start_col:end_col]
1055
- for datacn in iterable:
1056
- if (txt := self.MT.get_valid_cell_data_as_str(datarn, datacn, get_displayed=True)) and (
1057
- th := self.MT.get_txt_h(txt) + 5
1058
- ) > h:
1059
- h = th
1060
- if ih > h:
1061
- h = ih
1160
+ h = max(
1161
+ h,
1162
+ max(
1163
+ self.MT.get_wrapped_cell_height(
1164
+ datarn,
1165
+ datacn,
1166
+ )
1167
+ for datacn in iterable
1168
+ ),
1169
+ )
1170
+ self.MT.cells_cache = None
1171
+ h = max(h, ih)
1062
1172
  if only_if_too_small and h < self.MT.row_positions[row + 1] - self.MT.row_positions[row]:
1063
1173
  return self.MT.row_positions[row + 1] - self.MT.row_positions[row]
1064
- if h < self.MT.min_row_height:
1065
- h = int(self.MT.min_row_height)
1066
- elif h > self.PAR.ops.max_row_height:
1067
- h = int(self.PAR.ops.max_row_height)
1068
- return h
1174
+ else:
1175
+ return max(int(min(h, self.ops.max_row_height)), self.MT.min_row_height)
1069
1176
 
1070
1177
  def set_row_height(
1071
1178
  self,
@@ -1079,8 +1186,8 @@ class RowIndex(tk.Canvas):
1079
1186
  height = self.get_row_text_height(row=row, visible_only=visible_only)
1080
1187
  if height < self.MT.min_row_height:
1081
1188
  height = int(self.MT.min_row_height)
1082
- elif height > self.PAR.ops.max_row_height:
1083
- height = int(self.PAR.ops.max_row_height)
1189
+ elif height > self.ops.max_row_height:
1190
+ height = int(self.ops.max_row_height)
1084
1191
  if only_if_too_small and height <= self.MT.row_positions[row + 1] - self.MT.row_positions[row]:
1085
1192
  return self.MT.row_positions[row + 1] - self.MT.row_positions[row]
1086
1193
  new_row_pos = self.MT.row_positions[row] + height
@@ -1098,7 +1205,7 @@ class RowIndex(tk.Canvas):
1098
1205
  only_rows: AnyIter[int] | None = None,
1099
1206
  ) -> int:
1100
1207
  self.fix_index()
1101
- w = self.PAR.ops.default_row_index_width
1208
+ w = self.ops.default_row_index_width
1102
1209
  if (not self.MT._row_index and isinstance(self.MT._row_index, list)) or (
1103
1210
  isinstance(self.MT._row_index, int) and self.MT._row_index >= len(self.MT.data)
1104
1211
  ):
@@ -1114,8 +1221,8 @@ class RowIndex(tk.Canvas):
1114
1221
  iterable = self.MT.displayed_rows
1115
1222
  if (new_w := max(map(itemgetter(0), map(self.get_cell_dimensions, iterable)), default=w)) > w:
1116
1223
  w = new_w
1117
- if w > self.PAR.ops.max_index_width:
1118
- w = int(self.PAR.ops.max_index_width)
1224
+ if w > self.ops.max_index_width:
1225
+ w = int(self.ops.max_index_width)
1119
1226
  return w
1120
1227
 
1121
1228
  def set_width_of_index_to_text(
@@ -1124,7 +1231,7 @@ class RowIndex(tk.Canvas):
1124
1231
  only_rows: list = [],
1125
1232
  ) -> int:
1126
1233
  self.fix_index()
1127
- w = self.PAR.ops.default_row_index_width
1234
+ w = self.ops.default_row_index_width
1128
1235
  if (text is None and isinstance(self.MT._row_index, list) and not self.MT._row_index) or (
1129
1236
  isinstance(self.MT._row_index, int) and self.MT._row_index >= len(self.MT.data)
1130
1237
  ):
@@ -1136,8 +1243,8 @@ class RowIndex(tk.Canvas):
1136
1243
  w = tw
1137
1244
  elif text is None:
1138
1245
  w = self.get_index_text_width(only_rows=only_rows)
1139
- if w > self.PAR.ops.max_index_width:
1140
- w = int(self.PAR.ops.max_index_width)
1246
+ if w > self.ops.max_index_width:
1247
+ w = int(self.ops.max_index_width)
1141
1248
  self.set_width(w, set_TL=True)
1142
1249
  self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
1143
1250
  return w
@@ -1166,15 +1273,15 @@ class RowIndex(tk.Canvas):
1166
1273
 
1167
1274
  def auto_set_index_width(self, end_row: int, only_rows: list) -> bool:
1168
1275
  if not isinstance(self.MT._row_index, int) and not self.MT._row_index:
1169
- if self.PAR.ops.default_row_index == "letters":
1170
- new_w = self.MT.get_txt_w(f"{num2alpha(end_row)}") + 20
1171
- elif self.PAR.ops.default_row_index == "numbers":
1172
- new_w = self.MT.get_txt_w(f"{end_row}") + 20
1173
- elif self.PAR.ops.default_row_index == "both":
1174
- new_w = self.MT.get_txt_w(f"{end_row + 1} {num2alpha(end_row)}") + 20
1175
- elif self.PAR.ops.default_row_index is None:
1276
+ if self.ops.default_row_index == "letters":
1277
+ new_w = self.MT.get_txt_w(f"{num2alpha(end_row)}", self.index_font) + 20
1278
+ elif self.ops.default_row_index == "numbers":
1279
+ new_w = self.MT.get_txt_w(f"{end_row}", self.index_font) + 20
1280
+ elif self.ops.default_row_index == "both":
1281
+ new_w = self.MT.get_txt_w(f"{end_row + 1} {num2alpha(end_row)}", self.index_font) + 20
1282
+ elif self.ops.default_row_index is None:
1176
1283
  new_w = 20
1177
- elif self.PAR.ops.auto_resize_row_index is True:
1284
+ elif self.ops.auto_resize_row_index is True:
1178
1285
  new_w = self.get_index_text_width(only_rows=only_rows)
1179
1286
  else:
1180
1287
  new_w = None
@@ -1205,8 +1312,8 @@ class RowIndex(tk.Canvas):
1205
1312
  fill = color_map[fill]
1206
1313
  if "rows" in selections and r in selections["rows"]:
1207
1314
  txtfg = (
1208
- self.PAR.ops.index_selected_rows_fg
1209
- if kwargs[1] is None or self.PAR.ops.display_selected_fg_over_highlights
1315
+ self.ops.index_selected_rows_fg
1316
+ if kwargs[1] is None or self.ops.display_selected_fg_over_highlights
1210
1317
  else kwargs[1]
1211
1318
  )
1212
1319
  if fill:
@@ -1217,8 +1324,8 @@ class RowIndex(tk.Canvas):
1217
1324
  )
1218
1325
  elif "cells" in selections and r in selections["cells"]:
1219
1326
  txtfg = (
1220
- self.PAR.ops.index_selected_cells_fg
1221
- if kwargs[1] is None or self.PAR.ops.display_selected_fg_over_highlights
1327
+ self.ops.index_selected_cells_fg
1328
+ if kwargs[1] is None or self.ops.display_selected_fg_over_highlights
1222
1329
  else kwargs[1]
1223
1330
  )
1224
1331
  if fill:
@@ -1228,7 +1335,7 @@ class RowIndex(tk.Canvas):
1228
1335
  + f"{int((int(fill[5:], 16) + int(sel_cells_bg[5:], 16)) / 2):02X}"
1229
1336
  )
1230
1337
  else:
1231
- txtfg = self.PAR.ops.index_fg if kwargs[1] is None else kwargs[1]
1338
+ txtfg = self.ops.index_fg if kwargs[1] is None else kwargs[1]
1232
1339
  if fill:
1233
1340
  redrawn = self.redraw_highlight(
1234
1341
  0,
@@ -1237,8 +1344,8 @@ class RowIndex(tk.Canvas):
1237
1344
  sr,
1238
1345
  fill=fill,
1239
1346
  outline=(
1240
- self.PAR.ops.index_fg
1241
- if self.get_cell_kwargs(datarn, key="dropdown") and self.PAR.ops.show_dropdown_borders
1347
+ self.ops.index_fg
1348
+ if self.get_cell_kwargs(datarn, key="dropdown") and self.ops.show_dropdown_borders
1242
1349
  else ""
1243
1350
  ),
1244
1351
  tag="s",
@@ -1246,14 +1353,14 @@ class RowIndex(tk.Canvas):
1246
1353
  tree_arrow_fg = txtfg
1247
1354
  elif not kwargs:
1248
1355
  if "rows" in selections and r in selections["rows"]:
1249
- txtfg = self.PAR.ops.index_selected_rows_fg
1250
- tree_arrow_fg = self.PAR.ops.selected_rows_tree_arrow_fg
1356
+ txtfg = self.ops.index_selected_rows_fg
1357
+ tree_arrow_fg = self.ops.selected_rows_tree_arrow_fg
1251
1358
  elif "cells" in selections and r in selections["cells"]:
1252
- txtfg = self.PAR.ops.index_selected_cells_fg
1253
- tree_arrow_fg = self.PAR.ops.selected_cells_tree_arrow_fg
1359
+ txtfg = self.ops.index_selected_cells_fg
1360
+ tree_arrow_fg = self.ops.selected_cells_tree_arrow_fg
1254
1361
  else:
1255
- txtfg = self.PAR.ops.index_fg
1256
- tree_arrow_fg = self.PAR.ops.tree_arrow_fg
1362
+ txtfg = self.ops.index_fg
1363
+ tree_arrow_fg = self.ops.tree_arrow_fg
1257
1364
  return txtfg, tree_arrow_fg, redrawn
1258
1365
 
1259
1366
  def redraw_highlight(
@@ -1375,14 +1482,14 @@ class RowIndex(tk.Canvas):
1375
1482
  t, sh = self.hidd_tree_arrow.popitem()
1376
1483
  self.coords(t, points)
1377
1484
  if sh:
1378
- self.itemconfig(t, fill=fill if has_children else self.PAR.ops.index_grid_fg)
1485
+ self.itemconfig(t, fill=fill if has_children else self.ops.index_grid_fg)
1379
1486
  else:
1380
- self.itemconfig(t, fill=fill if has_children else self.PAR.ops.index_grid_fg, tag=tag, state="normal")
1487
+ self.itemconfig(t, fill=fill if has_children else self.ops.index_grid_fg, tag=tag, state="normal")
1381
1488
  self.lift(t)
1382
1489
  else:
1383
1490
  t = self.create_line(
1384
1491
  points,
1385
- fill=fill if has_children else self.PAR.ops.index_grid_fg,
1492
+ fill=fill if has_children else self.ops.index_grid_fg,
1386
1493
  tag=tag,
1387
1494
  width=2,
1388
1495
  capstyle=tk.ROUND,
@@ -1403,8 +1510,8 @@ class RowIndex(tk.Canvas):
1403
1510
  draw_arrow: bool = True,
1404
1511
  open_: bool = False,
1405
1512
  ) -> None:
1406
- if draw_outline and self.PAR.ops.show_dropdown_borders:
1407
- self.redraw_highlight(x1 + 1, y1 + 1, x2, y2, fill="", outline=self.PAR.ops.index_fg, tag=tag)
1513
+ if draw_outline and self.ops.show_dropdown_borders:
1514
+ self.redraw_highlight(x1 + 1, y1 + 1, x2, y2, fill="", outline=self.ops.index_fg, tag=tag)
1408
1515
  if draw_arrow:
1409
1516
  mod = (self.MT.index_txt_height - 1) if self.MT.index_txt_height % 2 else self.MT.index_txt_height
1410
1517
  small_mod = int(mod / 5)
@@ -1490,15 +1597,33 @@ class RowIndex(tk.Canvas):
1490
1597
  t = self.create_polygon(points, fill=fill, outline=outline, tag=tag, smooth=True)
1491
1598
  self.disp_checkbox[t] = True
1492
1599
 
1493
- def configure_scrollregion(self, last_row_line_pos: float) -> None:
1494
- self.configure(
1495
- scrollregion=(
1496
- 0,
1497
- 0,
1498
- self.current_width,
1499
- last_row_line_pos + self.PAR.ops.empty_vertical + 2,
1600
+ def configure_scrollregion(self, last_row_line_pos: float) -> bool:
1601
+ try:
1602
+ self.configure(
1603
+ scrollregion=(
1604
+ 0,
1605
+ 0,
1606
+ self.current_width,
1607
+ last_row_line_pos + self.ops.empty_vertical + 2,
1608
+ )
1500
1609
  )
1610
+ return True
1611
+ except Exception:
1612
+ return False
1613
+
1614
+ def wrap_get_char_w(self, c: str) -> int:
1615
+ self.MT.txt_measure_canvas.itemconfig(
1616
+ self.MT.txt_measure_canvas_text,
1617
+ text=_test_str + c,
1618
+ font=self.index_font,
1501
1619
  )
1620
+ b = self.MT.txt_measure_canvas.bbox(self.MT.txt_measure_canvas_text)
1621
+ if c in self.MT.char_widths[self.index_font]:
1622
+ return self.MT.char_widths[self.index_font][c]
1623
+ else:
1624
+ wd = b[2] - b[0] - self.index_test_str_w
1625
+ self.MT.char_widths[self.index_font][c] = wd
1626
+ return wd
1502
1627
 
1503
1628
  def redraw_grid_and_text(
1504
1629
  self,
@@ -1511,11 +1636,11 @@ class RowIndex(tk.Canvas):
1511
1636
  text_end_row: int,
1512
1637
  scrollpos_bot: int,
1513
1638
  row_pos_exists: bool,
1514
- ) -> None:
1515
- try:
1516
- self.configure_scrollregion(last_row_line_pos=last_row_line_pos)
1517
- except Exception:
1518
- return
1639
+ set_scrollregion: bool,
1640
+ ) -> bool:
1641
+ if set_scrollregion:
1642
+ if not self.configure_scrollregion(last_row_line_pos=last_row_line_pos):
1643
+ return False
1519
1644
  self.hidd_text.update(self.disp_text)
1520
1645
  self.disp_text = {}
1521
1646
  self.hidd_high.update(self.disp_high)
@@ -1536,20 +1661,20 @@ class RowIndex(tk.Canvas):
1536
1661
  scrollpos_bot,
1537
1662
  )
1538
1663
  sel_cells_bg = (
1539
- self.PAR.ops.index_selected_cells_bg
1540
- if self.PAR.ops.index_selected_cells_bg.startswith("#")
1541
- else color_map[self.PAR.ops.index_selected_cells_bg]
1664
+ self.ops.index_selected_cells_bg
1665
+ if self.ops.index_selected_cells_bg.startswith("#")
1666
+ else color_map[self.ops.index_selected_cells_bg]
1542
1667
  )
1543
1668
  sel_rows_bg = (
1544
- self.PAR.ops.index_selected_rows_bg
1545
- if self.PAR.ops.index_selected_rows_bg.startswith("#")
1546
- else color_map[self.PAR.ops.index_selected_rows_bg]
1669
+ self.ops.index_selected_rows_bg
1670
+ if self.ops.index_selected_rows_bg.startswith("#")
1671
+ else color_map[self.ops.index_selected_rows_bg]
1547
1672
  )
1548
- font = self.PAR.ops.index_font
1673
+ font = self.ops.index_font
1549
1674
  selections = self.get_redraw_selections(text_start_row, grid_end_row)
1550
1675
  dd_coords = self.dropdown.get_coords()
1551
- treeview = self.PAR.ops.treeview
1552
-
1676
+ treeview = self.ops.treeview
1677
+ wrap = self.ops.index_wrap
1553
1678
  for r in range(text_start_row, text_end_row):
1554
1679
  rtopgridln = self.MT.row_positions[r]
1555
1680
  rbotgridln = self.MT.row_positions[r + 1]
@@ -1571,19 +1696,19 @@ class RowIndex(tk.Canvas):
1571
1696
  else:
1572
1697
  align = self.align
1573
1698
  if dropdown_kwargs := self.get_cell_kwargs(datarn, key="dropdown"):
1574
- mw = self.current_width - self.MT.index_txt_height - 2
1575
- if align == "w":
1699
+ max_width = self.current_width - self.MT.index_txt_height - 2
1700
+ if align.endswith("w"):
1576
1701
  draw_x = 3
1577
- elif align == "e":
1702
+ elif align.endswith("e"):
1578
1703
  draw_x = self.current_width - 5 - self.MT.index_txt_height
1579
- elif align == "center":
1704
+ elif align.endswith("n"):
1580
1705
  draw_x = ceil((self.current_width - self.MT.index_txt_height) / 2)
1581
1706
  self.redraw_dropdown(
1582
1707
  0,
1583
1708
  rtopgridln,
1584
1709
  self.current_width - 1,
1585
1710
  rbotgridln - 1,
1586
- fill=fill if dropdown_kwargs["state"] != "disabled" else self.PAR.ops.index_grid_fg,
1711
+ fill=fill if dropdown_kwargs["state"] != "disabled" else self.ops.index_grid_fg,
1587
1712
  outline=fill,
1588
1713
  tag="dd",
1589
1714
  draw_outline=not dd_drawn,
@@ -1591,27 +1716,24 @@ class RowIndex(tk.Canvas):
1591
1716
  open_=dd_coords == r,
1592
1717
  )
1593
1718
  else:
1594
- mw = self.current_width - 2
1595
- if align == "w":
1719
+ max_width = self.current_width - 2
1720
+ if align.endswith("w"):
1596
1721
  draw_x = 3
1597
- elif align == "e":
1722
+ elif align.endswith("e"):
1598
1723
  draw_x = self.current_width - 3
1599
- elif align == "center":
1724
+ elif align.endswith("n"):
1600
1725
  draw_x = floor(self.current_width / 2)
1601
1726
  if (
1602
1727
  (checkbox_kwargs := self.get_cell_kwargs(datarn, key="checkbox"))
1603
1728
  and not dropdown_kwargs
1604
- and mw > self.MT.index_txt_height + 1
1729
+ and max_width > self.MT.index_txt_height + 1
1605
1730
  ):
1606
1731
  box_w = self.MT.index_txt_height + 1
1607
- if align == "w":
1732
+ if align.endswith("w"):
1608
1733
  draw_x += box_w + 3
1609
- mw -= box_w + 3
1610
- elif align == "center":
1734
+ elif align.endswith("n"):
1611
1735
  draw_x += ceil(box_w / 2) + 1
1612
- mw -= box_w + 2
1613
- else:
1614
- mw -= box_w + 1
1736
+ max_width -= box_w + 4
1615
1737
  try:
1616
1738
  draw_check = (
1617
1739
  self.MT._row_index[datarn]
@@ -1625,15 +1747,15 @@ class RowIndex(tk.Canvas):
1625
1747
  rtopgridln + 2,
1626
1748
  self.MT.index_txt_height + 3,
1627
1749
  rtopgridln + self.MT.index_txt_height + 3,
1628
- fill=fill if checkbox_kwargs["state"] == "normal" else self.PAR.ops.index_grid_fg,
1750
+ fill=fill if checkbox_kwargs["state"] == "normal" else self.ops.index_grid_fg,
1629
1751
  outline="",
1630
1752
  tag="cb",
1631
1753
  draw_check=draw_check,
1632
1754
  )
1633
1755
  if treeview and isinstance(self.MT._row_index, list) and len(self.MT._row_index) > datarn:
1634
1756
  iid = self.MT._row_index[datarn].iid
1635
- mw -= self.MT.index_txt_height
1636
- if align == "w":
1757
+ max_width -= self.MT.index_txt_height
1758
+ if align.endswith("w"):
1637
1759
  draw_x += self.MT.index_txt_height + 3
1638
1760
  level, indent = self.get_iid_level_indent(iid)
1639
1761
  draw_x += indent + 5
@@ -1648,24 +1770,30 @@ class RowIndex(tk.Canvas):
1648
1770
  open_=self.MT._row_index[datarn].iid in self.tree_open_ids,
1649
1771
  level=level,
1650
1772
  )
1651
- if mw < 5:
1652
- continue
1653
- lines = self.get_valid_cell_data_as_str(datarn, fix=False)
1654
- if not lines:
1773
+ if max_width <= 1:
1655
1774
  continue
1656
- start_ln = max(0, int((scrollpos_top - rtopgridln) / self.MT.index_xtra_lines_increment))
1657
- draw_y = rtopgridln + self.MT.index_first_ln_ins + (start_ln * self.MT.index_xtra_lines_increment)
1658
- lines = lines.split("\n")
1659
- if len(lines) <= start_ln or draw_y + self.MT.index_half_txt_height - 1 > rbotgridln:
1775
+ text = self.get_valid_cell_data_as_str(datarn, fix=False)
1776
+ if not text:
1660
1777
  continue
1661
- for txt in islice(lines, start_ln, None):
1778
+ start_line = max(0, int((scrollpos_top - rtopgridln) / self.MT.index_txt_height))
1779
+ draw_y = rtopgridln + 3 + (start_line * self.MT.index_txt_height)
1780
+ gen_lines = wrap_text(
1781
+ text=text,
1782
+ max_width=max_width,
1783
+ max_lines=int((rbotgridln - rtopgridln - 2) / self.MT.index_txt_height),
1784
+ char_width_fn=self.wrap_get_char_w,
1785
+ widths=self.MT.char_widths[font],
1786
+ wrap=wrap,
1787
+ start_line=start_line,
1788
+ )
1789
+ if align.endswith(("w", "e")):
1662
1790
  if self.hidd_text:
1663
1791
  iid, showing = self.hidd_text.popitem()
1664
1792
  self.coords(iid, draw_x, draw_y)
1665
1793
  if showing:
1666
1794
  self.itemconfig(
1667
1795
  iid,
1668
- text=txt,
1796
+ text="\n".join(gen_lines),
1669
1797
  fill=fill,
1670
1798
  font=font,
1671
1799
  anchor=align,
@@ -1673,7 +1801,7 @@ class RowIndex(tk.Canvas):
1673
1801
  else:
1674
1802
  self.itemconfig(
1675
1803
  iid,
1676
- text=txt,
1804
+ text="\n".join(gen_lines),
1677
1805
  fill=fill,
1678
1806
  font=font,
1679
1807
  anchor=align,
@@ -1684,47 +1812,51 @@ class RowIndex(tk.Canvas):
1684
1812
  iid = self.create_text(
1685
1813
  draw_x,
1686
1814
  draw_y,
1687
- text=txt,
1815
+ text="\n".join(gen_lines),
1688
1816
  fill=fill,
1689
1817
  font=font,
1690
1818
  anchor=align,
1691
- tag="t",
1819
+ tags="t",
1692
1820
  )
1693
1821
  self.disp_text[iid] = True
1694
- wd = self.bbox(iid)
1695
- if (wd := wd[2] - wd[0]) > mw:
1696
- if align == "w" and dropdown_kwargs:
1697
- txt = txt[: int(len(txt) * (mw / wd))]
1698
- self.itemconfig(iid, text=txt)
1699
- wd = self.bbox(iid)
1700
- while wd[2] - wd[0] > mw:
1701
- txt = txt[:-1]
1702
- self.itemconfig(iid, text=txt)
1703
- wd = self.bbox(iid)
1704
- elif align == "e" and checkbox_kwargs:
1705
- txt = txt[len(txt) - int(len(txt) * (mw / wd)) :]
1706
- self.itemconfig(iid, text=txt)
1707
- wd = self.bbox(iid)
1708
- while wd[2] - wd[0] > mw:
1709
- txt = txt[1:]
1710
- self.itemconfig(iid, text=txt)
1711
- wd = self.bbox(iid)
1712
- elif align == "center" and (dropdown_kwargs or checkbox_kwargs):
1713
- tmod = ceil((len(txt) - int(len(txt) * (mw / wd))) / 2)
1714
- txt = txt[tmod - 1 : -tmod]
1715
- self.itemconfig(iid, text=txt)
1716
- wd = self.bbox(iid)
1717
- self.c_align_cyc = cycle(self.centre_alignment_text_mod_indexes)
1718
- while wd[2] - wd[0] > mw:
1719
- txt = txt[next(self.c_align_cyc)]
1720
- self.itemconfig(iid, text=txt)
1721
- wd = self.bbox(iid)
1822
+ else:
1823
+ for text in gen_lines:
1824
+ if self.hidd_text:
1825
+ iid, showing = self.hidd_text.popitem()
1722
1826
  self.coords(iid, draw_x, draw_y)
1723
- draw_y += self.MT.index_xtra_lines_increment
1724
- if draw_y + self.MT.index_half_txt_height - 1 > rbotgridln:
1725
- break
1827
+ if showing:
1828
+ self.itemconfig(
1829
+ iid,
1830
+ text=text,
1831
+ fill=fill,
1832
+ font=font,
1833
+ anchor=align,
1834
+ )
1835
+ else:
1836
+ self.itemconfig(
1837
+ iid,
1838
+ text=text,
1839
+ fill=fill,
1840
+ font=font,
1841
+ anchor=align,
1842
+ state="normal",
1843
+ )
1844
+ self.tag_raise(iid)
1845
+ else:
1846
+ iid = self.create_text(
1847
+ draw_x,
1848
+ draw_y,
1849
+ text=text,
1850
+ fill=fill,
1851
+ font=font,
1852
+ anchor=align,
1853
+ tags="t",
1854
+ )
1855
+ self.disp_text[iid] = True
1856
+ draw_y += self.MT.header_txt_height
1857
+
1726
1858
  xend = self.current_width - 6
1727
- if (self.PAR.ops.show_horizontal_grid or self.height_resizing_enabled) and row_pos_exists:
1859
+ if (self.ops.show_horizontal_grid or self.height_resizing_enabled) and row_pos_exists:
1728
1860
  points = [
1729
1861
  self.current_width - 1,
1730
1862
  y_stop - 1,
@@ -1749,7 +1881,7 @@ class RowIndex(tk.Canvas):
1749
1881
  self.MT.row_positions[r + 1] if len(self.MT.row_positions) - 1 > r else draw_y,
1750
1882
  )
1751
1883
  )
1752
- self.redraw_gridline(points=points, fill=self.PAR.ops.index_grid_fg, width=1, tag="h")
1884
+ self.redraw_gridline(points=points, fill=self.ops.index_grid_fg, width=1, tag="h")
1753
1885
  for dct in (
1754
1886
  self.hidd_text,
1755
1887
  self.hidd_high,
@@ -1842,14 +1974,14 @@ class RowIndex(tk.Canvas):
1842
1974
  return False
1843
1975
  else:
1844
1976
  text = text if isinstance(text, str) else f"{text}"
1845
- if self.PAR.ops.cell_auto_resize_enabled:
1977
+ if self.ops.cell_auto_resize_enabled:
1846
1978
  self.set_row_height_run_binding(r)
1847
1979
  if self.text_editor.open and r == self.text_editor.row:
1848
1980
  self.text_editor.set_text(self.text_editor.get() + "" if not isinstance(text, str) else text)
1849
1981
  return False
1850
1982
  self.hide_text_editor()
1851
1983
  if not self.MT.see(r=r, c=0, keep_yscroll=True, check_cell_visibility=True):
1852
- self.MT.refresh()
1984
+ self.MT.main_table_redraw_grid_and_text(True, True)
1853
1985
  x = 0
1854
1986
  y = self.MT.row_positions[r]
1855
1987
  w = self.current_width + 1
@@ -1857,24 +1989,24 @@ class RowIndex(tk.Canvas):
1857
1989
  kwargs = {
1858
1990
  "menu_kwargs": DotDict(
1859
1991
  {
1860
- "font": self.PAR.ops.table_font,
1861
- "foreground": self.PAR.ops.popup_menu_fg,
1862
- "background": self.PAR.ops.popup_menu_bg,
1863
- "activebackground": self.PAR.ops.popup_menu_highlight_bg,
1864
- "activeforeground": self.PAR.ops.popup_menu_highlight_fg,
1992
+ "font": self.ops.index_font,
1993
+ "foreground": self.ops.popup_menu_fg,
1994
+ "background": self.ops.popup_menu_bg,
1995
+ "activebackground": self.ops.popup_menu_highlight_bg,
1996
+ "activeforeground": self.ops.popup_menu_highlight_fg,
1865
1997
  }
1866
1998
  ),
1867
- "sheet_ops": self.PAR.ops,
1868
- "border_color": self.PAR.ops.index_selected_rows_bg,
1999
+ "sheet_ops": self.ops,
2000
+ "border_color": self.ops.index_selected_rows_bg,
1869
2001
  "text": text,
1870
2002
  "state": state,
1871
2003
  "width": w,
1872
2004
  "height": h,
1873
2005
  "show_border": True,
1874
- "bg": self.PAR.ops.index_editor_bg,
1875
- "fg": self.PAR.ops.index_editor_fg,
1876
- "select_bg": self.PAR.ops.index_editor_select_bg,
1877
- "select_fg": self.PAR.ops.index_editor_select_fg,
2006
+ "bg": self.ops.index_editor_bg,
2007
+ "fg": self.ops.index_editor_fg,
2008
+ "select_bg": self.ops.index_editor_select_bg,
2009
+ "select_fg": self.ops.index_editor_select_fg,
1878
2010
  "align": self.get_cell_align(r),
1879
2011
  "r": r,
1880
2012
  }
@@ -1913,13 +2045,13 @@ class RowIndex(tk.Canvas):
1913
2045
  > curr_height
1914
2046
  ):
1915
2047
  r = self.text_editor.row
1916
- new_height = curr_height + self.MT.index_xtra_lines_increment
2048
+ new_height = curr_height + self.MT.index_txt_height
1917
2049
  space_bot = self.MT.get_space_bot(r)
1918
2050
  if new_height > space_bot:
1919
2051
  new_height = space_bot
1920
2052
  if new_height != curr_height:
1921
2053
  self.set_row_height(r, new_height)
1922
- self.MT.refresh()
2054
+ self.MT.main_table_redraw_grid_and_text(True, True)
1923
2055
  self.text_editor.window.config(height=new_height)
1924
2056
  self.coords(self.text_editor.canvas_id, 0, self.MT.row_positions[r] + 1)
1925
2057
  if self.dropdown.open and self.dropdown.get_coords() == r:
@@ -1944,7 +2076,7 @@ class RowIndex(tk.Canvas):
1944
2076
  if self.text_editor.open:
1945
2077
  r = self.text_editor.row
1946
2078
  self.text_editor.window.config(height=self.MT.row_positions[r + 1] - self.MT.row_positions[r])
1947
- self.text_editor.tktext.config(font=self.PAR.ops.index_font)
2079
+ self.text_editor.tktext.config(font=self.ops.index_font)
1948
2080
  self.coords(
1949
2081
  self.text_editor.canvas_id,
1950
2082
  0,
@@ -2047,9 +2179,7 @@ class RowIndex(tk.Canvas):
2047
2179
  for i, v in enumerate(self.get_cell_kwargs(datarn, key="dropdown")["values"]):
2048
2180
  v_numlines = len(v.split("\n") if isinstance(v, str) else f"{v}".split("\n"))
2049
2181
  if v_numlines > 1:
2050
- win_h += (
2051
- self.MT.index_first_ln_ins + (v_numlines * self.MT.index_xtra_lines_increment) + 5
2052
- ) # end of cell
2182
+ win_h += self.MT.index_txt_height + (v_numlines * self.MT.index_txt_height) + 5 # end of cell
2053
2183
  else:
2054
2184
  win_h += self.MT.min_row_height
2055
2185
  if i == 5:
@@ -2117,15 +2247,15 @@ class RowIndex(tk.Canvas):
2117
2247
  reset_kwargs = {
2118
2248
  "r": r,
2119
2249
  "c": 0,
2120
- "bg": self.PAR.ops.index_editor_bg,
2121
- "fg": self.PAR.ops.index_editor_fg,
2122
- "select_bg": self.PAR.ops.index_editor_select_bg,
2123
- "select_fg": self.PAR.ops.index_editor_select_fg,
2250
+ "bg": self.ops.index_editor_bg,
2251
+ "fg": self.ops.index_editor_fg,
2252
+ "select_bg": self.ops.index_editor_select_bg,
2253
+ "select_fg": self.ops.index_editor_select_fg,
2124
2254
  "width": win_w,
2125
2255
  "height": win_h,
2126
- "font": self.PAR.ops.index_font,
2127
- "ops": self.PAR.ops,
2128
- "outline_color": self.PAR.ops.index_selected_rows_bg,
2256
+ "font": self.ops.index_font,
2257
+ "ops": self.ops,
2258
+ "outline_color": self.ops.index_selected_rows_bg,
2129
2259
  "align": self.get_cell_align(r),
2130
2260
  "values": kwargs["values"],
2131
2261
  "search_function": kwargs["search_function"],
@@ -2274,7 +2404,7 @@ class RowIndex(tk.Canvas):
2274
2404
  self.MT.undo_stack.append(stored_event_dict(event_data))
2275
2405
  self.set_cell_data(datarn=datarn, value=value)
2276
2406
  edited = True
2277
- if edited and cell_resize and self.PAR.ops.cell_auto_resize_enabled:
2407
+ if edited and cell_resize and self.ops.cell_auto_resize_enabled:
2278
2408
  self.set_row_height_run_binding(r, only_if_too_small=False)
2279
2409
  if redraw:
2280
2410
  self.MT.refresh()
@@ -2289,7 +2419,7 @@ class RowIndex(tk.Canvas):
2289
2419
  self.fix_index(datarn)
2290
2420
  if self.get_cell_kwargs(datarn, key="checkbox"):
2291
2421
  self.MT._row_index[datarn] = try_to_bool(value)
2292
- elif self.PAR.ops.treeview:
2422
+ elif self.ops.treeview:
2293
2423
  self.MT._row_index[datarn].text = value
2294
2424
  else:
2295
2425
  self.MT._row_index[datarn] = value
@@ -2331,7 +2461,7 @@ class RowIndex(tk.Canvas):
2331
2461
  or (self.MT._row_index[datarn] is None and none_to_empty_str)
2332
2462
  ):
2333
2463
  return ""
2334
- if self.PAR.ops.treeview:
2464
+ if self.ops.treeview:
2335
2465
  return self.MT._row_index[datarn].text
2336
2466
  return self.MT._row_index[datarn]
2337
2467
 
@@ -2349,14 +2479,23 @@ class RowIndex(tk.Canvas):
2349
2479
  if fix:
2350
2480
  self.fix_index(datarn)
2351
2481
  try:
2352
- value = "" if self.MT._row_index[datarn] is None else f"{self.MT._row_index[datarn]}"
2482
+ value = self.MT._row_index[datarn]
2483
+ if value is None:
2484
+ value = ""
2485
+ elif isinstance(value, Node):
2486
+ value = value.text
2487
+ elif not isinstance(value, str):
2488
+ value = f"{value}"
2353
2489
  except Exception:
2354
2490
  value = ""
2355
- if not value and self.PAR.ops.show_default_index_for_empty:
2356
- value = get_n2a(datarn, self.PAR.ops.default_row_index)
2491
+ if not value and self.ops.show_default_index_for_empty:
2492
+ value = get_n2a(datarn, self.ops.default_row_index)
2357
2493
  return value
2358
2494
 
2359
2495
  def get_value_for_empty_cell(self, datarn: int, r_ops: bool = True) -> object:
2496
+ if self.ops.treeview:
2497
+ iid = self.new_iid()
2498
+ return Node(text=iid, iid=iid, parent=self.get_row_parent(datarn))
2360
2499
  if self.get_cell_kwargs(datarn, key="checkbox", cell=r_ops):
2361
2500
  return False
2362
2501
  kwargs = self.get_cell_kwargs(datarn, key="dropdown", cell=r_ops)
@@ -2436,63 +2575,378 @@ class RowIndex(tk.Canvas):
2436
2575
 
2437
2576
  # Treeview Mode
2438
2577
 
2578
+ def tree_reset(self) -> None:
2579
+ self.tree: dict[str, Node] = {}
2580
+ self.tree_open_ids = set()
2581
+ self.tree_rns = {}
2582
+ if self.MT:
2583
+ self.MT.displayed_rows = []
2584
+ self.MT._row_index = []
2585
+ self.MT.data = []
2586
+ self.MT.row_positions = [0]
2587
+ self.MT.saved_row_heights = {}
2588
+
2589
+ def new_iid(self) -> str:
2590
+ self.new_iid_ctr += 1
2591
+ while (iid := f"{num2alpha(self.new_iid_ctr)}") in self.tree:
2592
+ self.new_iid_ctr += 1
2593
+ return iid
2594
+
2595
+ def get_row_parent(self, r: int) -> str:
2596
+ if r >= len(self.MT._row_index):
2597
+ return ""
2598
+ else:
2599
+ return self.MT._row_index[r].parent
2600
+
2601
+ def tree_del_rows(self, event_data: EventDataDict) -> EventDataDict:
2602
+ event_data["treeview"]["nodes"] = {}
2603
+ for node in reversed(event_data["deleted"]["index"].values()):
2604
+ iid = node.iid
2605
+ if parent_node := self.parent_node(iid):
2606
+ if parent_node.iid not in event_data["treeview"]["nodes"]:
2607
+ event_data["treeview"]["nodes"][parent_node.iid] = Node(
2608
+ text=parent_node.text,
2609
+ iid=parent_node.iid,
2610
+ parent=parent_node.parent,
2611
+ children=parent_node.children.copy(),
2612
+ )
2613
+ self.remove_iid_from_parents_children(iid)
2614
+ for node in reversed(event_data["deleted"]["index"].values()):
2615
+ iid = node.iid
2616
+ for did in self.get_iid_descendants(iid):
2617
+ self.tree_open_ids.discard(did)
2618
+ del self.tree[did]
2619
+ self.tree_open_ids.discard(iid)
2620
+ del self.tree[iid]
2621
+ return event_data
2622
+
2623
+ def tree_add_rows(self, event_data: EventDataDict) -> EventDataDict:
2624
+ for rn, node in event_data["added"]["rows"]["index"].items():
2625
+ self.tree[node.iid] = node
2626
+ self.tree_rns[node.iid] = rn
2627
+ if event_data["treeview"]["nodes"]:
2628
+ self.restore_nodes(event_data=event_data)
2629
+ else:
2630
+ row, a_node = next(reversed(event_data["added"]["rows"]["index"].items()))
2631
+ if parent := a_node.parent:
2632
+ if self.tree[parent].children:
2633
+ index = next(
2634
+ (i for i, cid in enumerate(self.tree[parent].children) if self.tree_rns[cid] >= row),
2635
+ len(self.tree[parent].children),
2636
+ )
2637
+ self.tree[parent].children[index:index] = [
2638
+ n.iid for n in reversed(event_data["added"]["rows"]["index"].values())
2639
+ ]
2640
+ else:
2641
+ self.tree[parent].children.extend(
2642
+ n.iid for n in reversed(event_data["added"]["rows"]["index"].values())
2643
+ )
2644
+ if not self.PAR.item_displayed(parent) or parent not in self.tree_open_ids:
2645
+ self.PAR.hide_rows(event_data["added"]["rows"]["index"], data_indexes=True)
2646
+ return event_data
2647
+
2648
+ def move_rows_mod_nodes(
2649
+ self,
2650
+ data_new_idxs: dict[int, int],
2651
+ data_old_idxs: dict[int, int],
2652
+ disp_new_idxs: dict[int, int],
2653
+ maxidx: int,
2654
+ event_data: EventDataDict,
2655
+ undo_modification: EventDataDict | None = None,
2656
+ node_change: tuple[str, str, int] | None = None,
2657
+ ) -> Generator[tuple[dict[int, int], dict[int, int], dict[str, int], EventDataDict]] | None:
2658
+ # data_new_idxs is {old: new, old: new}
2659
+ # data_old_idxs is {new: old, new: old}
2660
+ if not event_data["treeview"]["nodes"]:
2661
+ if undo_modification:
2662
+ """
2663
+ Used by undo/redo
2664
+ """
2665
+ event_data = self.copy_nodes(undo_modification["treeview"]["nodes"], event_data)
2666
+ self.restore_nodes(undo_modification)
2667
+
2668
+ elif event_data["moved"]["rows"]:
2669
+ if node_change:
2670
+ item = node_change[0]
2671
+ moved_rows = [self.tree_rns[item]]
2672
+ new_parent = node_change[1]
2673
+ move_to_index = node_change[2]
2674
+ if new_parent:
2675
+ move_to_index = (
2676
+ move_to_index if isinstance(move_to_index, int) else len(self.tree[new_parent].children)
2677
+ )
2678
+ move_to_row = self.tree_rns[new_parent] + max(
2679
+ 0, min(move_to_index, len(self.tree[new_parent].children))
2680
+ )
2681
+ else:
2682
+ num_top_nodes = sum(1 for _ in self.gen_top_nodes())
2683
+ if move_to_index is None:
2684
+ move_to_row = self.PAR.top_index_row(num_top_nodes - 1)
2685
+ move_to_index = num_top_nodes
2686
+ else:
2687
+ move_to_row = self.PAR.top_index_row(move_to_index)
2688
+ if move_to_row is None:
2689
+ move_to_row = self.PAR.top_index_row(num_top_nodes - 1)
2690
+ move_to_index = num_top_nodes
2691
+
2692
+ move_to_iid = self.MT._row_index[move_to_row].iid
2693
+ insert_row = move_to_row + 1
2694
+ disp_insert_row = None
2695
+
2696
+ else:
2697
+ iids = set(self.MT._row_index[r].iid for r in event_data["moved"]["rows"]["data"])
2698
+ iids_descendants = {iid: set(self.get_iid_descendants(iid)) for iid in iids}
2699
+
2700
+ # remove descendants in iids to move
2701
+ iids -= set.union(*iids_descendants.values()) & iids
2702
+ moved_rows = sorted(map(self.tree_rns.__getitem__, iids))
2703
+ item = self.MT._row_index[moved_rows[0]].iid
2704
+
2705
+ if isinstance(event_data.value, int):
2706
+ disp_insert_row = event_data.value
2707
+ if disp_insert_row >= len(self.MT.displayed_rows):
2708
+ insert_row = len(self.MT._row_index)
2709
+ else:
2710
+ insert_row = self.MT.datarn(disp_insert_row)
2711
+ move_to_iid = self.MT._row_index[min(insert_row, len(self.MT._row_index) - 1)].iid
2712
+
2713
+ else:
2714
+ disp_insert_row = None
2715
+ min_from = min(event_data["moved"]["rows"]["data"])
2716
+ # max_from = max(event_data.moved.rows)
2717
+ min_to = min(event_data["moved"]["rows"]["data"].values())
2718
+ max_to = max(event_data["moved"]["rows"]["data"].values())
2719
+ if min_from <= min_to:
2720
+ insert_row = max_to
2721
+ else:
2722
+ insert_row = min_to
2723
+ move_to_iid = self.MT._row_index[insert_row].iid
2724
+
2725
+ move_to_index = self.PAR.index(move_to_iid)
2726
+ new_parent = self.items_parent(move_to_iid)
2727
+
2728
+ event_data["moved"]["rows"]["data"] = {moved_rows[0]: insert_row}
2729
+
2730
+ new_loc_is_displayed = not new_parent or (
2731
+ new_parent and new_parent in self.tree_open_ids and self.PAR.item_displayed(new_parent)
2732
+ )
2733
+ # deal with displayed mapping
2734
+ event_data["moved"]["rows"]["displayed"] = {}
2735
+ if new_loc_is_displayed:
2736
+ if disp_insert_row is None:
2737
+ if new_parent == self.tree[item].parent:
2738
+ disp_insert_row = self.MT.disprn(self.tree_rns[move_to_iid]) + 1
2739
+ else:
2740
+ disp_insert_row = self.MT.disprn(self.tree_rns[move_to_iid]) + 1
2741
+ if (disp_from_row := self.MT.try_disprn(self.tree_rns[item])) is not None:
2742
+ event_data["moved"]["rows"]["displayed"] = {disp_from_row: disp_insert_row}
2743
+ else:
2744
+ event_data["moved"]["rows"]["displayed"] = {tuple(): disp_insert_row}
2745
+
2746
+ if any(self.move_pid_causes_recursive_loop(self.MT._row_index[r].iid, new_parent) for r in moved_rows):
2747
+ event_data["moved"]["rows"] = {}
2748
+ data_new_idxs, data_old_idxs, disp_new_idxs = {}, {}, {}
2749
+
2750
+ else:
2751
+ for r in moved_rows:
2752
+ iid = self.MT._row_index[r].iid
2753
+ event_data = self.move_node(
2754
+ event_data=event_data,
2755
+ item=iid,
2756
+ parent=new_parent,
2757
+ index=move_to_index,
2758
+ )
2759
+ move_to_index += 1
2760
+
2761
+ event_data["moved"]["rows"]["data"] = get_new_indexes(
2762
+ insert_row,
2763
+ event_data["moved"]["rows"]["data"],
2764
+ )
2765
+ data_new_idxs = event_data["moved"]["rows"]["data"]
2766
+ data_old_idxs = dict(zip(data_new_idxs.values(), data_new_idxs))
2767
+
2768
+ if tuple() in event_data["moved"]["rows"]["displayed"]:
2769
+ del event_data["moved"]["rows"]["displayed"][tuple()]
2770
+
2771
+ if event_data["moved"]["rows"]["displayed"]:
2772
+ event_data["moved"]["rows"]["displayed"] = get_new_indexes(
2773
+ disp_insert_row,
2774
+ event_data["moved"]["rows"]["displayed"],
2775
+ )
2776
+ disp_new_idxs = event_data["moved"]["rows"]["displayed"]
2777
+
2778
+ if data_new_idxs:
2779
+ self.MT.move_rows_data(data_new_idxs, data_old_idxs, maxidx)
2780
+
2781
+ yield data_new_idxs, data_old_idxs, disp_new_idxs, event_data
2782
+
2783
+ if not undo_modification and data_new_idxs:
2784
+ if new_parent and (not self.PAR.item_displayed(new_parent) or new_parent not in self.tree_open_ids):
2785
+ self.PAR.hide_rows(set(data_new_idxs.values()), data_indexes=True)
2786
+
2787
+ if new_loc_is_displayed:
2788
+ self.PAR.show_rows(
2789
+ (r for r in data_new_idxs.values() if self.ancestors_all_open(self.MT._row_index[r].iid))
2790
+ )
2791
+
2792
+ yield None
2793
+
2794
+ def move_node(
2795
+ self,
2796
+ event_data: EventDataDict,
2797
+ item: str,
2798
+ parent: str | None = None,
2799
+ index: int = 0,
2800
+ ) -> EventDataDict:
2801
+ # also backs up nodes
2802
+ if parent is None:
2803
+ parent = self.items_parent(item)
2804
+
2805
+ item_node = self.tree[item]
2806
+
2807
+ # new parent is an item
2808
+ if parent:
2809
+ parent_node = self.tree[parent]
2810
+ # its the same parent, we're just moving index
2811
+ if parent == item_node.parent:
2812
+ event_data = self.copy_nodes((item, parent), event_data)
2813
+ pop_index = parent_node.children.index(item)
2814
+ parent_node.children.insert(index, parent_node.children.pop(pop_index))
2815
+
2816
+ else:
2817
+ if item_node.parent:
2818
+ event_data = self.copy_nodes((item, item_node.parent, parent), event_data)
2819
+ else:
2820
+ event_data = self.copy_nodes((item, parent), event_data)
2821
+ self.remove_iid_from_parents_children(item)
2822
+ item_node.parent = parent_node.iid
2823
+ parent_node.children.insert(index, item)
2824
+
2825
+ # no new parent
2826
+ else:
2827
+ if item_node.parent:
2828
+ event_data = self.copy_nodes((item, item_node.parent), event_data)
2829
+ else:
2830
+ event_data = self.copy_nodes((item,), event_data)
2831
+ self.remove_iid_from_parents_children(item)
2832
+ self.tree[item].parent = ""
2833
+
2834
+ # last row in mapping is where to start from +1
2835
+ mapping = event_data["moved"]["rows"]["data"]
2836
+ row_ctr = next(reversed(mapping.values())) + 1
2837
+
2838
+ if disp_mapping := event_data["moved"]["rows"]["displayed"]:
2839
+ if tuple() in disp_mapping:
2840
+ disp_row_ctr = next(reversed(disp_mapping.values()))
2841
+ else:
2842
+ disp_row_ctr = next(reversed(disp_mapping.values())) + 1
2843
+
2844
+ rn = self.tree_rns[item]
2845
+ if rn not in mapping:
2846
+ mapping[rn] = row_ctr
2847
+ row_ctr += 1
2848
+ if disp_mapping and (disp_from := self.MT.try_disprn(rn)) is not None:
2849
+ disp_mapping = del_placeholder_dict_key(disp_mapping, disp_from, disp_row_ctr)
2850
+ disp_row_ctr += 1
2851
+
2852
+ for did in self.get_iid_descendants(item):
2853
+ mapping[self.tree_rns[did]] = row_ctr
2854
+ row_ctr += 1
2855
+ if disp_mapping and (disp_from := self.MT.try_disprn(self.tree_rns[did])) is not None:
2856
+ disp_mapping = del_placeholder_dict_key(disp_mapping, disp_from, disp_row_ctr)
2857
+ disp_row_ctr += 1
2858
+
2859
+ event_data["moved"]["rows"]["data"] = mapping
2860
+ event_data["moved"]["rows"]["displayed"] = disp_mapping
2861
+
2862
+ return event_data
2863
+
2864
+ def restore_nodes(self, event_data: EventDataDict) -> None:
2865
+ for iid, node in event_data["treeview"]["nodes"].items():
2866
+ self.MT._row_index[self.tree_rns[iid]] = node
2867
+ self.tree[iid] = node
2868
+
2869
+ def copy_node(self, item: str) -> Node:
2870
+ n = self.tree[item]
2871
+ return Node(
2872
+ text=n.text,
2873
+ iid=n.iid,
2874
+ parent=n.parent,
2875
+ children=n.children.copy(),
2876
+ )
2877
+
2878
+ def copy_nodes(self, items: AnyIter[str], event_data: EventDataDict) -> EventDataDict:
2879
+ nodes = event_data["treeview"]["nodes"]
2880
+ for iid in items:
2881
+ if iid not in nodes:
2882
+ n = self.tree[iid]
2883
+ nodes[iid] = Node(
2884
+ text=n.text,
2885
+ iid=n.iid,
2886
+ parent=n.parent,
2887
+ children=n.children.copy(),
2888
+ )
2889
+ return event_data
2890
+
2439
2891
  def get_node_level(self, node: Node, level: int = 0) -> Generator[int]:
2440
2892
  yield level
2441
2893
  if node.parent:
2442
- yield from self.get_node_level(node.parent, level + 1)
2894
+ yield from self.get_node_level(self.tree[node.parent], level + 1)
2443
2895
 
2444
- def ancestors_all_open(self, iid: str, stop_at: str | Node = "") -> bool:
2896
+ def ancestors_all_open(self, iid: str, stop_at: str = "") -> bool:
2445
2897
  if stop_at:
2446
- stop_at = stop_at.iid
2447
2898
  for iid in self.get_iid_ancestors(iid):
2448
2899
  if iid == stop_at:
2449
2900
  return True
2450
- if iid not in self.tree_open_ids:
2901
+ elif iid not in self.tree_open_ids:
2451
2902
  return False
2452
2903
  return True
2453
2904
  return all(map(self.tree_open_ids.__contains__, self.get_iid_ancestors(iid)))
2454
2905
 
2455
2906
  def get_iid_ancestors(self, iid: str) -> Generator[str]:
2456
2907
  if self.tree[iid].parent:
2457
- yield self.tree[iid].parent.iid
2458
- yield from self.get_iid_ancestors(self.tree[iid].parent.iid)
2908
+ yield self.tree[iid].parent
2909
+ yield from self.get_iid_ancestors(self.tree[iid].parent)
2459
2910
 
2460
2911
  def get_iid_descendants(self, iid: str, check_open: bool = False) -> Generator[str]:
2461
- for cnode in self.tree[iid].children:
2462
- yield cnode.iid
2463
- if (check_open and cnode.children and cnode.iid in self.tree_open_ids) or (
2464
- not check_open and cnode.children
2465
- ):
2466
- yield from self.get_iid_descendants(cnode.iid, check_open)
2912
+ for ciid in self.tree[iid].children:
2913
+ yield ciid
2914
+ if self.tree[ciid].children and (not check_open or ciid in self.tree_open_ids):
2915
+ yield from self.get_iid_descendants(ciid, check_open)
2467
2916
 
2468
2917
  def items_parent(self, iid: str) -> str:
2469
2918
  if self.tree[iid].parent:
2470
- return self.tree[iid].parent.iid
2919
+ return self.tree[iid].parent
2920
+ return ""
2921
+
2922
+ def parent_node(self, iid: str) -> Node:
2923
+ if self.tree[iid].parent:
2924
+ return self.tree[self.tree[iid].parent]
2471
2925
  return ""
2472
2926
 
2473
2927
  def gen_top_nodes(self) -> Generator[Node]:
2474
2928
  yield from (node for node in self.MT._row_index if node.parent == "")
2475
2929
 
2476
2930
  def get_iid_indent(self, iid: str) -> int:
2477
- if isinstance(self.PAR.ops.treeview_indent, str):
2478
- indent = self.MT.index_txt_width * int(self.PAR.ops.treeview_indent)
2931
+ if isinstance(self.ops.treeview_indent, str):
2932
+ indent = self.MT.index_txt_width * int(self.ops.treeview_indent)
2479
2933
  else:
2480
- indent = self.PAR.ops.treeview_indent
2934
+ indent = self.ops.treeview_indent
2481
2935
  return indent * max(self.get_node_level(self.tree[iid]))
2482
2936
 
2483
2937
  def get_iid_level_indent(self, iid: str) -> tuple[int, int]:
2484
- if isinstance(self.PAR.ops.treeview_indent, str):
2485
- indent = self.MT.index_txt_width * int(self.PAR.ops.treeview_indent)
2938
+ if isinstance(self.ops.treeview_indent, str):
2939
+ indent = self.MT.index_txt_width * int(self.ops.treeview_indent)
2486
2940
  else:
2487
- indent = self.PAR.ops.treeview_indent
2941
+ indent = self.ops.treeview_indent
2488
2942
  level = max(self.get_node_level(self.tree[iid]))
2489
2943
  return level, indent * level
2490
2944
 
2491
- def remove_node_from_parents_children(self, node: Node) -> None:
2492
- if node.parent:
2493
- node.parent.children.remove(node)
2494
- if not node.parent.children:
2495
- self.tree_open_ids.discard(node.parent)
2945
+ def remove_iid_from_parents_children(self, iid: str) -> None:
2946
+ if parent_node := self.parent_node(iid):
2947
+ parent_node.children.remove(iid)
2948
+ if not parent_node.children:
2949
+ self.tree_open_ids.discard(parent_node.iid)
2496
2950
 
2497
2951
  def build_pid_causes_recursive_loop(self, iid: str, pid: str) -> bool:
2498
2952
  return any(
@@ -2506,4 +2960,121 @@ class RowIndex(tk.Canvas):
2506
2960
  def move_pid_causes_recursive_loop(self, to_move_iid: str, move_to_parent: str) -> bool:
2507
2961
  # if the parent the item is being moved under is one of the item's descendants
2508
2962
  # then it is a recursive loop
2509
- return any(move_to_parent == diid for diid in self.get_iid_descendants(to_move_iid))
2963
+ return to_move_iid == move_to_parent or any(
2964
+ move_to_parent == diid for diid in self.get_iid_descendants(to_move_iid)
2965
+ )
2966
+
2967
+ def tree_build(
2968
+ self,
2969
+ data: list[list[object]],
2970
+ iid_column: int,
2971
+ parent_column: int,
2972
+ text_column: None | int | list[str] = None,
2973
+ push_ops: bool = False,
2974
+ row_heights: Sequence[int] | None | False = None,
2975
+ open_ids: AnyIter[str] | None = None,
2976
+ safety: bool = True,
2977
+ ncols: int | None = None,
2978
+ lower: bool = False,
2979
+ include_iid_column: bool = True,
2980
+ include_parent_column: bool = True,
2981
+ include_text_column: bool = True,
2982
+ ) -> None:
2983
+ self.PAR.reset(cell_options=False, column_widths=False, header=False, redraw=False)
2984
+ if text_column is None:
2985
+ text_column = iid_column
2986
+ tally_of_ids = defaultdict(lambda: -1)
2987
+ if not isinstance(ncols, int):
2988
+ ncols = max(map(len, data), default=0)
2989
+ for rn, row in enumerate(data):
2990
+ if safety and ncols > (lnr := len(row)):
2991
+ row += self.MT.get_empty_row_seq(rn, end=ncols, start=lnr)
2992
+ if lower:
2993
+ iid = row[iid_column].lower()
2994
+ pid = row[parent_column].lower()
2995
+ else:
2996
+ iid = row[iid_column]
2997
+ pid = row[parent_column]
2998
+ if safety:
2999
+ if not iid:
3000
+ continue
3001
+ tally_of_ids[iid] += 1
3002
+ if tally_of_ids[iid]:
3003
+ x = 1
3004
+ while iid in tally_of_ids:
3005
+ new = f"{row[iid_column]}_DUPLICATED_{x}"
3006
+ iid = new.lower() if lower else new
3007
+ x += 1
3008
+ tally_of_ids[iid] += 1
3009
+ row[iid_column] = new
3010
+ if iid in self.tree:
3011
+ self.tree[iid].text = row[text_column] if isinstance(text_column, int) else text_column[rn]
3012
+ else:
3013
+ self.tree[iid] = Node(row[text_column] if isinstance(text_column, int) else text_column[rn], iid, "")
3014
+ if safety and (iid == pid or self.build_pid_causes_recursive_loop(iid, pid)):
3015
+ row[parent_column] = ""
3016
+ pid = ""
3017
+ if pid:
3018
+ if pid in self.tree:
3019
+ self.tree[pid].children.append(iid)
3020
+ else:
3021
+ self.tree[pid] = Node(
3022
+ text=row[text_column] if isinstance(text_column, int) else text_column[rn],
3023
+ iid=pid,
3024
+ children=[iid],
3025
+ )
3026
+ self.tree[iid].parent = pid
3027
+ else:
3028
+ self.tree[iid].parent = ""
3029
+ self.tree_rns[iid] = rn
3030
+ if safety:
3031
+ for n in self.tree.values():
3032
+ if n.parent is None:
3033
+ n.parent = ""
3034
+ newrow = self.MT.get_empty_row_seq(len(data), ncols)
3035
+ newrow[iid_column] = n.iid
3036
+ self.tree_rns[n.iid] = len(data)
3037
+ data.append(newrow)
3038
+ insert_rows = partial(
3039
+ self.PAR.insert_rows,
3040
+ idx=0,
3041
+ heights={} if row_heights is False else row_heights,
3042
+ row_index=True,
3043
+ create_selections=False,
3044
+ fill=False,
3045
+ undo=False,
3046
+ push_ops=push_ops,
3047
+ redraw=False,
3048
+ )
3049
+ exclude = set()
3050
+ if not include_iid_column:
3051
+ exclude.add(iid_column)
3052
+ if not include_parent_column:
3053
+ exclude.add(parent_column)
3054
+ if isinstance(text_column, int) and not include_text_column:
3055
+ exclude.add(text_column)
3056
+ if exclude:
3057
+ insert_rows(
3058
+ rows=[
3059
+ [self.tree[iid]] + [e for i, e in enumerate(data[self.tree_rns[iid]]) if i not in exclude]
3060
+ for iid in self.PAR.get_iids()
3061
+ ],
3062
+ tree=False,
3063
+ )
3064
+ else:
3065
+ insert_rows(
3066
+ rows=[[self.tree[iid]] + data[self.tree_rns[iid]] for iid in self.PAR.get_iids()],
3067
+ tree=False,
3068
+ )
3069
+ self.MT.all_rows_displayed = False
3070
+ self.MT.displayed_rows = list(range(len(self.MT._row_index)))
3071
+ self.tree_rns = {n.iid: i for i, n in enumerate(self.MT._row_index)}
3072
+ if open_ids:
3073
+ self.PAR.tree_set_open(open_ids=open_ids)
3074
+ else:
3075
+ self.PAR.hide_rows(
3076
+ {self.tree_rns[iid] for iid in self.PAR.get_children() if self.tree[iid].parent},
3077
+ deselect_all=False,
3078
+ data_indexes=True,
3079
+ row_heights=False if row_heights is False else True,
3080
+ )