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/row_index.py CHANGED
@@ -2,83 +2,61 @@ from __future__ import annotations
2
2
 
3
3
  import tkinter as tk
4
4
  from collections import defaultdict
5
- from collections.abc import (
6
- Generator,
7
- Hashable,
8
- Sequence,
9
- )
10
- from functools import (
11
- partial,
12
- )
13
- from itertools import (
14
- chain,
15
- cycle,
16
- islice,
17
- repeat,
18
- )
19
- from math import (
20
- ceil,
21
- floor,
22
- )
23
- from operator import (
24
- itemgetter,
25
- )
5
+ from collections.abc import Callable, Generator, Hashable, Sequence
6
+ from functools import partial
7
+ from itertools import chain, cycle, islice, repeat
8
+ from math import ceil, floor
9
+ from operator import itemgetter
26
10
  from typing import Literal
27
11
 
28
- from .colors import (
29
- color_map,
30
- )
12
+ from .colors import color_map
31
13
  from .constants import (
14
+ _test_str,
32
15
  rc_binding,
33
16
  text_editor_close_bindings,
34
17
  text_editor_newline_bindings,
35
18
  text_editor_to_unbind,
36
19
  )
37
- from .formatters import (
38
- is_bool_like,
39
- try_to_bool,
40
- )
20
+ from .formatters import is_bool_like, try_to_bool
41
21
  from .functions import (
42
22
  consecutive_chunks,
43
23
  consecutive_ranges,
24
+ del_placeholder_dict_key,
44
25
  event_dict,
45
26
  event_has_char_key,
46
27
  event_opens_dropdown_or_checkbox,
47
28
  get_n2a,
29
+ get_new_indexes,
48
30
  int_x_tuple,
49
31
  is_contiguous,
32
+ mod_event_val,
50
33
  new_tk_event,
51
34
  num2alpha,
52
35
  rounded_box_coords,
53
36
  stored_event_dict,
37
+ try_b_index,
54
38
  try_binding,
39
+ wrap_text,
55
40
  )
56
- from .other_classes import (
57
- DotDict,
58
- DraggedRowColumn,
59
- DropdownStorage,
60
- Node,
61
- TextEditorStorage,
62
- )
63
- from .text_editor import (
64
- TextEditor,
65
- )
66
- from .types import (
67
- AnyIter,
68
- )
41
+ from .other_classes import DotDict, DraggedRowColumn, DropdownStorage, EventDataDict, Node, TextEditorStorage
42
+ from .sorting import sort_columns_by_row, sort_row
43
+ from .text_editor import TextEditor
44
+ from .tksheet_types import AnyIter
69
45
 
70
46
 
71
47
  class RowIndex(tk.Canvas):
72
- def __init__(self, *args, **kwargs):
48
+ def __init__(self, parent, **kwargs):
73
49
  super().__init__(
74
- kwargs["parent"],
75
- background=kwargs["parent"].ops.index_bg,
50
+ parent,
51
+ background=parent.ops.index_bg,
76
52
  highlightthickness=0,
77
53
  )
78
- self.PAR = kwargs["parent"]
54
+ self.PAR = parent
55
+ self.ops = self.PAR.ops
79
56
  self.MT = None # is set from within MainTable() __init__
80
57
  self.CH = None # is set from within MainTable() __init__
81
58
  self.TL = None # is set from within TopLeftRectangle() __init__
59
+ self.new_iid_ctr = -1
82
60
  self.current_width = None
83
61
  self.popup_menu_loc = None
84
62
  self.extra_begin_edit_cell_func = None
@@ -99,6 +77,8 @@ class RowIndex(tk.Canvas):
99
77
  self.drag_selection_binding_func = None
100
78
  self.ri_extra_begin_drag_drop_func = None
101
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
102
82
  self.extra_double_b1_func = None
103
83
  self.row_height_resize_func = None
104
84
  self.cell_options = {}
@@ -170,18 +150,6 @@ class RowIndex(tk.Canvas):
170
150
  self.unbind("<Double-Button-1>")
171
151
  self.unbind(rc_binding)
172
152
 
173
- def tree_reset(self) -> None:
174
- # treeview mode
175
- self.tree = {}
176
- self.tree_open_ids = set()
177
- self.tree_rns = {}
178
- if self.MT:
179
- self.MT.displayed_rows = []
180
- self.MT._row_index = []
181
- self.MT.data = []
182
- self.MT.row_positions = [0]
183
- self.MT.saved_row_heights = {}
184
-
185
153
  def set_width(self, new_width: int, set_TL: bool = False, recreate_selection_boxes: bool = True) -> None:
186
154
  try:
187
155
  self.config(width=new_width)
@@ -364,9 +332,9 @@ class RowIndex(tk.Canvas):
364
332
  if (
365
333
  self.width_resizing_enabled
366
334
  and not mouse_over_resize
367
- and self.PAR.ops.auto_resize_row_index is not True
335
+ and self.ops.auto_resize_row_index is not True
368
336
  and not (
369
- self.PAR.ops.auto_resize_row_index == "empty"
337
+ self.ops.auto_resize_row_index == "empty"
370
338
  and not isinstance(self.MT._row_index, int)
371
339
  and not self.MT._row_index
372
340
  )
@@ -421,7 +389,7 @@ class RowIndex(tk.Canvas):
421
389
  )
422
390
  elif self.width_resizing_enabled and self.rsz_h is None and self.rsz_w is True:
423
391
  self.set_width_of_index_to_text()
424
- 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:
425
393
  r = self.MT.identify_row(y=event.y)
426
394
  if r < len(self.MT.row_positions) - 1:
427
395
  iid = self.event_over_tree_arrow(r, self.canvasy(event.y), event.x)
@@ -471,20 +439,22 @@ class RowIndex(tk.Canvas):
471
439
  self.current_width,
472
440
  y,
473
441
  width=1,
474
- fill=self.PAR.ops.resizing_line_fg,
475
- tag="rhl",
442
+ fill=self.ops.resizing_line_fg,
443
+ tag=("rh", "rhl"),
476
444
  )
477
- self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.PAR.ops.resizing_line_fg, tag="rhl")
445
+ self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.ops.resizing_line_fg, tag=("rh", "rhl"))
478
446
  self.create_resize_line(
479
447
  0,
480
448
  line2y,
481
449
  self.current_width,
482
450
  line2y,
483
451
  width=1,
484
- fill=self.PAR.ops.resizing_line_fg,
485
- tag="rhl2",
452
+ fill=self.ops.resizing_line_fg,
453
+ tag=("rh", "rhl2"),
454
+ )
455
+ self.MT.create_resize_line(
456
+ x1, line2y, x2, line2y, width=1, fill=self.ops.resizing_line_fg, tag=("rh", "rhl2")
486
457
  )
487
- self.MT.create_resize_line(x1, line2y, x2, line2y, width=1, fill=self.PAR.ops.resizing_line_fg, tag="rhl2")
488
458
  elif self.width_resizing_enabled and self.rsz_h is None and self.rsz_w is True:
489
459
  self.currently_resizing_width = True
490
460
  elif self.MT.identify_row(y=event.y, allow_end=False) is None:
@@ -515,7 +485,7 @@ class RowIndex(tk.Canvas):
515
485
  if self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height:
516
486
  y = self.canvasy(event.y)
517
487
  size = y - self.MT.row_positions[self.rsz_h - 1]
518
- 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:
519
489
  self.hide_resize_and_ctrl_lines(ctrl_lines=False)
520
490
  line2y = self.MT.row_positions[self.rsz_h - 1]
521
491
  self.create_resize_line(
@@ -524,18 +494,18 @@ class RowIndex(tk.Canvas):
524
494
  self.current_width,
525
495
  y,
526
496
  width=1,
527
- fill=self.PAR.ops.resizing_line_fg,
528
- tag="rhl",
497
+ fill=self.ops.resizing_line_fg,
498
+ tag=("rh", "rhl"),
529
499
  )
530
- self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.PAR.ops.resizing_line_fg, tag="rhl")
500
+ self.MT.create_resize_line(x1, y, x2, y, width=1, fill=self.ops.resizing_line_fg, tag=("rh", "rhl"))
531
501
  self.create_resize_line(
532
502
  0,
533
503
  line2y,
534
504
  self.current_width,
535
505
  line2y,
536
506
  width=1,
537
- fill=self.PAR.ops.resizing_line_fg,
538
- tag="rhl2",
507
+ fill=self.ops.resizing_line_fg,
508
+ tag=("rh", "rhl2"),
539
509
  )
540
510
  self.MT.create_resize_line(
541
511
  x1,
@@ -543,19 +513,19 @@ class RowIndex(tk.Canvas):
543
513
  x2,
544
514
  line2y,
545
515
  width=1,
546
- fill=self.PAR.ops.resizing_line_fg,
547
- tag="rhl2",
516
+ fill=self.ops.resizing_line_fg,
517
+ tag=("rh", "rhl2"),
548
518
  )
549
519
  self.drag_height_resize()
550
520
  elif self.width_resizing_enabled and self.rsz_w is not None and self.currently_resizing_width:
551
521
  evx = event.x
552
522
  if evx > self.current_width:
553
- if evx > self.PAR.ops.max_index_width:
554
- evx = int(self.PAR.ops.max_index_width)
523
+ if evx > self.ops.max_index_width:
524
+ evx = int(self.ops.max_index_width)
555
525
  self.drag_width_resize(evx)
556
526
  else:
557
- if evx < self.PAR.ops.min_column_width:
558
- evx = self.PAR.ops.min_column_width
527
+ if evx < self.ops.min_column_width:
528
+ evx = self.ops.min_column_width
559
529
  self.drag_width_resize(evx)
560
530
  elif (
561
531
  self.drag_and_drop_enabled
@@ -712,16 +682,16 @@ class RowIndex(tk.Canvas):
712
682
  self.current_width,
713
683
  ypos,
714
684
  width=3,
715
- fill=self.PAR.ops.drag_and_drop_bg,
685
+ fill=self.ops.drag_and_drop_bg,
716
686
  tag="move_rows",
717
687
  )
718
- 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")
719
689
  for chunk in consecutive_chunks(rows):
720
690
  self.MT.show_ctrl_outline(
721
691
  start_cell=(0, chunk[0]),
722
692
  end_cell=(len(self.MT.col_positions) - 1, chunk[-1] + 1),
723
693
  dash=(),
724
- outline=self.PAR.ops.drag_and_drop_bg,
694
+ outline=self.ops.drag_and_drop_bg,
725
695
  delete_on_timer=False,
726
696
  )
727
697
 
@@ -785,8 +755,8 @@ class RowIndex(tk.Canvas):
785
755
  size = new_row_pos - self.MT.row_positions[self.rsz_h - 1]
786
756
  if size < self.MT.min_row_height:
787
757
  new_row_pos = ceil(self.MT.row_positions[self.rsz_h - 1] + self.MT.min_row_height)
788
- elif size > self.PAR.ops.max_row_height:
789
- 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)
790
760
  increment = new_row_pos - self.MT.row_positions[self.rsz_h]
791
761
  self.MT.row_positions[self.rsz_h + 1 :] = [
792
762
  e + increment for e in islice(self.MT.row_positions, self.rsz_h + 1, None)
@@ -795,7 +765,7 @@ class RowIndex(tk.Canvas):
795
765
  new_height = self.MT.row_positions[self.rsz_h] - self.MT.row_positions[self.rsz_h - 1]
796
766
  self.MT.allow_auto_resize_rows = False
797
767
  self.MT.recreate_all_selection_boxes()
798
- 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)
799
769
  if self.row_height_resize_func is not None and old_height != new_height:
800
770
  self.row_height_resize_func(
801
771
  event_dict(
@@ -821,6 +791,7 @@ class RowIndex(tk.Canvas):
821
791
  if self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height:
822
792
  self.drag_height_resize()
823
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)
824
795
  elif (
825
796
  self.drag_and_drop_enabled
826
797
  and self.MT.anything_selected(exclude_cells=True, exclude_columns=True)
@@ -846,33 +817,28 @@ class RowIndex(tk.Canvas):
846
817
  r += 1
847
818
  if r > len(self.MT.row_positions) - 1:
848
819
  r = len(self.MT.row_positions) - 1
849
- event_data = event_dict(
850
- name="move_rows",
851
- sheet=self.PAR.name,
852
- widget=self,
853
- boxes=self.MT.get_boxes(),
854
- selected=self.MT.selected,
855
- value=r,
856
- )
820
+ event_data = self.MT.new_event_dict("move_rows", state=True)
821
+ event_data["value"] = r
857
822
  if try_binding(self.ri_extra_begin_drag_drop_func, event_data, "begin_move_rows"):
858
823
  data_new_idxs, disp_new_idxs, event_data = self.MT.move_rows_adjust_options_dict(
859
824
  *self.MT.get_args_for_move_rows(
860
825
  move_to=r,
861
826
  to_move=self.dragged_row.to_move,
862
827
  ),
863
- move_data=self.PAR.ops.row_drag_and_drop_perform,
864
- 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,
865
830
  event_data=event_data,
866
831
  )
867
- event_data["moved"]["rows"] = {
868
- "data": data_new_idxs,
869
- "displayed": disp_new_idxs,
870
- }
871
- if self.MT.undo_enabled:
872
- self.MT.undo_stack.append(stored_event_dict(event_data))
873
- self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
874
- try_binding(self.ri_extra_end_drag_drop_func, event_data, "end_move_rows")
875
- 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)
876
842
  elif self.b1_pressed_loc is not None and self.rsz_w is None and self.rsz_h is None:
877
843
  r = self.MT.identify_row(y=event.y)
878
844
  if (
@@ -909,7 +875,7 @@ class RowIndex(tk.Canvas):
909
875
  canvasy: float,
910
876
  eventx: int,
911
877
  ) -> bool:
912
- if self.PAR.ops.treeview and (
878
+ if self.ops.treeview and (
913
879
  canvasy < self.MT.row_positions[r] + self.MT.index_txt_height + 5
914
880
  and isinstance(self.MT._row_index, list)
915
881
  and (datarn := self.MT.datarn(r)) < len(self.MT._row_index)
@@ -920,6 +886,96 @@ class RowIndex(tk.Canvas):
920
886
  return iid
921
887
  return None
922
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
+
923
979
  def toggle_select_row(
924
980
  self,
925
981
  row: int,
@@ -1008,7 +1064,7 @@ class RowIndex(tk.Canvas):
1008
1064
  y1,
1009
1065
  x2,
1010
1066
  y2,
1011
- radius=5 if self.PAR.ops.rounded_boxes else 0,
1067
+ radius=5 if self.ops.rounded_boxes else 0,
1012
1068
  )
1013
1069
  if isinstance(iid, int):
1014
1070
  self.coords(iid, coords)
@@ -1039,27 +1095,42 @@ class RowIndex(tk.Canvas):
1039
1095
  def get_cell_dimensions(self, datarn: int) -> tuple[int, int]:
1040
1096
  txt = self.get_valid_cell_data_as_str(datarn, fix=False)
1041
1097
  if txt:
1042
- self.MT.txt_measure_canvas.itemconfig(
1043
- self.MT.txt_measure_canvas_text, text=txt, font=self.PAR.ops.index_font
1044
- )
1098
+ self.MT.txt_measure_canvas.itemconfig(self.MT.txt_measure_canvas_text, text=txt, font=self.ops.index_font)
1045
1099
  b = self.MT.txt_measure_canvas.bbox(self.MT.txt_measure_canvas_text)
1046
1100
  w = b[2] - b[0] + 7
1047
1101
  h = b[3] - b[1] + 5
1048
1102
  else:
1049
- w = self.PAR.ops.default_row_index_width
1103
+ w = self.ops.default_row_index_width
1050
1104
  h = self.MT.min_row_height
1051
1105
  if self.get_cell_kwargs(datarn, key="dropdown") or self.get_cell_kwargs(datarn, key="checkbox"):
1052
1106
  w += self.MT.index_txt_height + 2
1053
- if self.PAR.ops.treeview:
1107
+ if self.ops.treeview:
1054
1108
  if datarn in self.cell_options and "align" in self.cell_options[datarn]:
1055
1109
  align = self.cell_options[datarn]["align"]
1056
1110
  else:
1057
1111
  align = self.align
1058
- if align == "w":
1112
+ if align.endswith("w"):
1059
1113
  w += self.MT.index_txt_height
1060
1114
  w += self.get_iid_indent(self.MT._row_index[datarn].iid) + 10
1061
1115
  return w, h
1062
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
+
1063
1134
  def get_row_text_height(
1064
1135
  self,
1065
1136
  row: int,
@@ -1069,7 +1140,7 @@ class RowIndex(tk.Canvas):
1069
1140
  h = self.MT.min_row_height
1070
1141
  datarn = row if self.MT.all_rows_displayed else self.MT.displayed_rows[row]
1071
1142
  # index
1072
- _w, ih = self.get_cell_dimensions(datarn)
1143
+ ih = self.get_wrapped_cell_height(datarn)
1073
1144
  # table
1074
1145
  if self.MT.data:
1075
1146
  if self.MT.all_columns_displayed:
@@ -1086,20 +1157,22 @@ class RowIndex(tk.Canvas):
1086
1157
  else:
1087
1158
  start_col, end_col = 0, len(self.MT.displayed_columns)
1088
1159
  iterable = self.MT.displayed_columns[start_col:end_col]
1089
- for datacn in iterable:
1090
- if (txt := self.MT.get_valid_cell_data_as_str(datarn, datacn, get_displayed=True)) and (
1091
- th := self.MT.get_txt_h(txt) + 5
1092
- ) > h:
1093
- h = th
1094
- if ih > h:
1095
- 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)
1096
1172
  if only_if_too_small and h < self.MT.row_positions[row + 1] - self.MT.row_positions[row]:
1097
1173
  return self.MT.row_positions[row + 1] - self.MT.row_positions[row]
1098
- if h < self.MT.min_row_height:
1099
- h = int(self.MT.min_row_height)
1100
- elif h > self.PAR.ops.max_row_height:
1101
- h = int(self.PAR.ops.max_row_height)
1102
- return h
1174
+ else:
1175
+ return max(int(min(h, self.ops.max_row_height)), self.MT.min_row_height)
1103
1176
 
1104
1177
  def set_row_height(
1105
1178
  self,
@@ -1113,8 +1186,8 @@ class RowIndex(tk.Canvas):
1113
1186
  height = self.get_row_text_height(row=row, visible_only=visible_only)
1114
1187
  if height < self.MT.min_row_height:
1115
1188
  height = int(self.MT.min_row_height)
1116
- elif height > self.PAR.ops.max_row_height:
1117
- height = int(self.PAR.ops.max_row_height)
1189
+ elif height > self.ops.max_row_height:
1190
+ height = int(self.ops.max_row_height)
1118
1191
  if only_if_too_small and height <= self.MT.row_positions[row + 1] - self.MT.row_positions[row]:
1119
1192
  return self.MT.row_positions[row + 1] - self.MT.row_positions[row]
1120
1193
  new_row_pos = self.MT.row_positions[row] + height
@@ -1132,7 +1205,7 @@ class RowIndex(tk.Canvas):
1132
1205
  only_rows: AnyIter[int] | None = None,
1133
1206
  ) -> int:
1134
1207
  self.fix_index()
1135
- w = self.PAR.ops.default_row_index_width
1208
+ w = self.ops.default_row_index_width
1136
1209
  if (not self.MT._row_index and isinstance(self.MT._row_index, list)) or (
1137
1210
  isinstance(self.MT._row_index, int) and self.MT._row_index >= len(self.MT.data)
1138
1211
  ):
@@ -1148,8 +1221,8 @@ class RowIndex(tk.Canvas):
1148
1221
  iterable = self.MT.displayed_rows
1149
1222
  if (new_w := max(map(itemgetter(0), map(self.get_cell_dimensions, iterable)), default=w)) > w:
1150
1223
  w = new_w
1151
- if w > self.PAR.ops.max_index_width:
1152
- w = int(self.PAR.ops.max_index_width)
1224
+ if w > self.ops.max_index_width:
1225
+ w = int(self.ops.max_index_width)
1153
1226
  return w
1154
1227
 
1155
1228
  def set_width_of_index_to_text(
@@ -1158,7 +1231,7 @@ class RowIndex(tk.Canvas):
1158
1231
  only_rows: list = [],
1159
1232
  ) -> int:
1160
1233
  self.fix_index()
1161
- w = self.PAR.ops.default_row_index_width
1234
+ w = self.ops.default_row_index_width
1162
1235
  if (text is None and isinstance(self.MT._row_index, list) and not self.MT._row_index) or (
1163
1236
  isinstance(self.MT._row_index, int) and self.MT._row_index >= len(self.MT.data)
1164
1237
  ):
@@ -1170,8 +1243,8 @@ class RowIndex(tk.Canvas):
1170
1243
  w = tw
1171
1244
  elif text is None:
1172
1245
  w = self.get_index_text_width(only_rows=only_rows)
1173
- if w > self.PAR.ops.max_index_width:
1174
- w = int(self.PAR.ops.max_index_width)
1246
+ if w > self.ops.max_index_width:
1247
+ w = int(self.ops.max_index_width)
1175
1248
  self.set_width(w, set_TL=True)
1176
1249
  self.MT.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
1177
1250
  return w
@@ -1200,15 +1273,15 @@ class RowIndex(tk.Canvas):
1200
1273
 
1201
1274
  def auto_set_index_width(self, end_row: int, only_rows: list) -> bool:
1202
1275
  if not isinstance(self.MT._row_index, int) and not self.MT._row_index:
1203
- if self.PAR.ops.default_row_index == "letters":
1204
- new_w = self.MT.get_txt_w(f"{num2alpha(end_row)}") + 20
1205
- elif self.PAR.ops.default_row_index == "numbers":
1206
- new_w = self.MT.get_txt_w(f"{end_row}") + 20
1207
- elif self.PAR.ops.default_row_index == "both":
1208
- new_w = self.MT.get_txt_w(f"{end_row + 1} {num2alpha(end_row)}") + 20
1209
- 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:
1210
1283
  new_w = 20
1211
- elif self.PAR.ops.auto_resize_row_index is True:
1284
+ elif self.ops.auto_resize_row_index is True:
1212
1285
  new_w = self.get_index_text_width(only_rows=only_rows)
1213
1286
  else:
1214
1287
  new_w = None
@@ -1239,8 +1312,8 @@ class RowIndex(tk.Canvas):
1239
1312
  fill = color_map[fill]
1240
1313
  if "rows" in selections and r in selections["rows"]:
1241
1314
  txtfg = (
1242
- self.PAR.ops.index_selected_rows_fg
1243
- 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
1244
1317
  else kwargs[1]
1245
1318
  )
1246
1319
  if fill:
@@ -1251,8 +1324,8 @@ class RowIndex(tk.Canvas):
1251
1324
  )
1252
1325
  elif "cells" in selections and r in selections["cells"]:
1253
1326
  txtfg = (
1254
- self.PAR.ops.index_selected_cells_fg
1255
- 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
1256
1329
  else kwargs[1]
1257
1330
  )
1258
1331
  if fill:
@@ -1262,7 +1335,7 @@ class RowIndex(tk.Canvas):
1262
1335
  + f"{int((int(fill[5:], 16) + int(sel_cells_bg[5:], 16)) / 2):02X}"
1263
1336
  )
1264
1337
  else:
1265
- 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]
1266
1339
  if fill:
1267
1340
  redrawn = self.redraw_highlight(
1268
1341
  0,
@@ -1271,8 +1344,8 @@ class RowIndex(tk.Canvas):
1271
1344
  sr,
1272
1345
  fill=fill,
1273
1346
  outline=(
1274
- self.PAR.ops.index_fg
1275
- 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
1276
1349
  else ""
1277
1350
  ),
1278
1351
  tag="s",
@@ -1280,14 +1353,14 @@ class RowIndex(tk.Canvas):
1280
1353
  tree_arrow_fg = txtfg
1281
1354
  elif not kwargs:
1282
1355
  if "rows" in selections and r in selections["rows"]:
1283
- txtfg = self.PAR.ops.index_selected_rows_fg
1284
- 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
1285
1358
  elif "cells" in selections and r in selections["cells"]:
1286
- txtfg = self.PAR.ops.index_selected_cells_fg
1287
- 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
1288
1361
  else:
1289
- txtfg = self.PAR.ops.index_fg
1290
- tree_arrow_fg = self.PAR.ops.tree_arrow_fg
1362
+ txtfg = self.ops.index_fg
1363
+ tree_arrow_fg = self.ops.tree_arrow_fg
1291
1364
  return txtfg, tree_arrow_fg, redrawn
1292
1365
 
1293
1366
  def redraw_highlight(
@@ -1308,6 +1381,7 @@ class RowIndex(tk.Canvas):
1308
1381
  self.itemconfig(iid, fill=fill, outline=outline)
1309
1382
  else:
1310
1383
  self.itemconfig(iid, fill=fill, outline=outline, tag=tag, state="normal")
1384
+ self.tag_raise(iid)
1311
1385
  else:
1312
1386
  iid = self.create_rectangle(coords, fill=fill, outline=outline, tag=tag)
1313
1387
  self.disp_high[iid] = True
@@ -1408,14 +1482,14 @@ class RowIndex(tk.Canvas):
1408
1482
  t, sh = self.hidd_tree_arrow.popitem()
1409
1483
  self.coords(t, points)
1410
1484
  if sh:
1411
- 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)
1412
1486
  else:
1413
- 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")
1414
1488
  self.lift(t)
1415
1489
  else:
1416
1490
  t = self.create_line(
1417
1491
  points,
1418
- fill=fill if has_children else self.PAR.ops.index_grid_fg,
1492
+ fill=fill if has_children else self.ops.index_grid_fg,
1419
1493
  tag=tag,
1420
1494
  width=2,
1421
1495
  capstyle=tk.ROUND,
@@ -1436,8 +1510,8 @@ class RowIndex(tk.Canvas):
1436
1510
  draw_arrow: bool = True,
1437
1511
  open_: bool = False,
1438
1512
  ) -> None:
1439
- if draw_outline and self.PAR.ops.show_dropdown_borders:
1440
- 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)
1441
1515
  if draw_arrow:
1442
1516
  mod = (self.MT.index_txt_height - 1) if self.MT.index_txt_height % 2 else self.MT.index_txt_height
1443
1517
  small_mod = int(mod / 5)
@@ -1523,15 +1597,33 @@ class RowIndex(tk.Canvas):
1523
1597
  t = self.create_polygon(points, fill=fill, outline=outline, tag=tag, smooth=True)
1524
1598
  self.disp_checkbox[t] = True
1525
1599
 
1526
- def configure_scrollregion(self, last_row_line_pos: float) -> None:
1527
- self.configure(
1528
- scrollregion=(
1529
- 0,
1530
- 0,
1531
- self.current_width,
1532
- 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
+ )
1533
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,
1534
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
1535
1627
 
1536
1628
  def redraw_grid_and_text(
1537
1629
  self,
@@ -1544,11 +1636,11 @@ class RowIndex(tk.Canvas):
1544
1636
  text_end_row: int,
1545
1637
  scrollpos_bot: int,
1546
1638
  row_pos_exists: bool,
1547
- ) -> None:
1548
- try:
1549
- self.configure_scrollregion(last_row_line_pos=last_row_line_pos)
1550
- except Exception:
1551
- 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
1552
1644
  self.hidd_text.update(self.disp_text)
1553
1645
  self.disp_text = {}
1554
1646
  self.hidd_high.update(self.disp_high)
@@ -1562,59 +1654,33 @@ class RowIndex(tk.Canvas):
1562
1654
  self.hidd_tree_arrow.update(self.disp_tree_arrow)
1563
1655
  self.disp_tree_arrow = {}
1564
1656
  self.visible_row_dividers = {}
1565
- xend = self.current_width - 6
1566
1657
  self.row_width_resize_bbox = (
1567
1658
  self.current_width - 2,
1568
1659
  scrollpos_top,
1569
1660
  self.current_width,
1570
1661
  scrollpos_bot,
1571
1662
  )
1572
- if (self.PAR.ops.show_horizontal_grid or self.height_resizing_enabled) and row_pos_exists:
1573
- points = [
1574
- self.current_width - 1,
1575
- y_stop - 1,
1576
- self.current_width - 1,
1577
- scrollpos_top - 1,
1578
- -1,
1579
- scrollpos_top - 1,
1580
- ]
1581
- for r in range(grid_start_row, grid_end_row):
1582
- draw_y = self.MT.row_positions[r]
1583
- if r and self.height_resizing_enabled:
1584
- self.visible_row_dividers[r] = (1, draw_y - 2, xend, draw_y + 2)
1585
- points.extend(
1586
- (
1587
- -1,
1588
- draw_y,
1589
- self.current_width,
1590
- draw_y,
1591
- -1,
1592
- draw_y,
1593
- -1,
1594
- self.MT.row_positions[r + 1] if len(self.MT.row_positions) - 1 > r else draw_y,
1595
- )
1596
- )
1597
- self.redraw_gridline(points=points, fill=self.PAR.ops.index_grid_fg, width=1, tag="h")
1598
1663
  sel_cells_bg = (
1599
- self.PAR.ops.index_selected_cells_bg
1600
- if self.PAR.ops.index_selected_cells_bg.startswith("#")
1601
- 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]
1602
1667
  )
1603
1668
  sel_rows_bg = (
1604
- self.PAR.ops.index_selected_rows_bg
1605
- if self.PAR.ops.index_selected_rows_bg.startswith("#")
1606
- 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]
1607
1672
  )
1608
- font = self.PAR.ops.index_font
1673
+ font = self.ops.index_font
1609
1674
  selections = self.get_redraw_selections(text_start_row, grid_end_row)
1610
1675
  dd_coords = self.dropdown.get_coords()
1611
- treeview = self.PAR.ops.treeview
1612
-
1676
+ treeview = self.ops.treeview
1677
+ wrap = self.ops.index_wrap
1613
1678
  for r in range(text_start_row, text_end_row):
1614
1679
  rtopgridln = self.MT.row_positions[r]
1615
1680
  rbotgridln = self.MT.row_positions[r + 1]
1616
1681
  if rbotgridln - rtopgridln < self.MT.index_txt_height:
1617
1682
  continue
1683
+ checkbox_kwargs = {}
1618
1684
  datarn = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
1619
1685
  fill, tree_arrow_fg, dd_drawn = self.redraw_highlight_get_text_fg(
1620
1686
  fr=rtopgridln,
@@ -1625,103 +1691,71 @@ class RowIndex(tk.Canvas):
1625
1691
  selections=selections,
1626
1692
  datarn=datarn,
1627
1693
  )
1628
-
1629
1694
  if datarn in self.cell_options and "align" in self.cell_options[datarn]:
1630
1695
  align = self.cell_options[datarn]["align"]
1631
1696
  else:
1632
1697
  align = self.align
1633
- dropdown_kwargs = self.get_cell_kwargs(datarn, key="dropdown")
1634
- if align == "w":
1635
- draw_x = 3
1636
- if dropdown_kwargs:
1637
- mw = self.current_width - self.MT.index_txt_height - 2
1638
- self.redraw_dropdown(
1639
- 0,
1640
- rtopgridln,
1641
- self.current_width - 1,
1642
- rbotgridln - 1,
1643
- fill=fill if dropdown_kwargs["state"] != "disabled" else self.PAR.ops.index_grid_fg,
1644
- outline=fill,
1645
- tag="dd",
1646
- draw_outline=not dd_drawn,
1647
- draw_arrow=True,
1648
- open_=dd_coords == r,
1649
- )
1650
- else:
1651
- mw = self.current_width - 2
1652
-
1653
- elif align == "e":
1654
- if dropdown_kwargs:
1655
- mw = self.current_width - self.MT.index_txt_height - 2
1698
+ if dropdown_kwargs := self.get_cell_kwargs(datarn, key="dropdown"):
1699
+ max_width = self.current_width - self.MT.index_txt_height - 2
1700
+ if align.endswith("w"):
1701
+ draw_x = 3
1702
+ elif align.endswith("e"):
1656
1703
  draw_x = self.current_width - 5 - self.MT.index_txt_height
1657
- self.redraw_dropdown(
1658
- 0,
1659
- rtopgridln,
1660
- self.current_width - 1,
1661
- rbotgridln - 1,
1662
- fill=fill if dropdown_kwargs["state"] != "disabled" else self.PAR.ops.index_grid_fg,
1663
- outline=fill,
1664
- tag="dd",
1665
- draw_outline=not dd_drawn,
1666
- draw_arrow=True,
1667
- open_=dd_coords == r,
1668
- )
1669
- else:
1670
- mw = self.current_width - 2
1671
- draw_x = self.current_width - 3
1672
-
1673
- elif align == "center":
1674
- if dropdown_kwargs:
1675
- mw = self.current_width - self.MT.index_txt_height - 2
1704
+ elif align.endswith("n"):
1676
1705
  draw_x = ceil((self.current_width - self.MT.index_txt_height) / 2)
1677
- self.redraw_dropdown(
1678
- 0,
1679
- rtopgridln,
1680
- self.current_width - 1,
1681
- rbotgridln - 1,
1682
- fill=fill if dropdown_kwargs["state"] != "disabled" else self.PAR.ops.index_grid_fg,
1683
- outline=fill,
1684
- tag="dd",
1685
- draw_outline=not dd_drawn,
1686
- draw_arrow=True,
1687
- open_=dd_coords == r,
1688
- )
1689
- else:
1690
- mw = self.current_width - 1
1706
+ self.redraw_dropdown(
1707
+ 0,
1708
+ rtopgridln,
1709
+ self.current_width - 1,
1710
+ rbotgridln - 1,
1711
+ fill=fill if dropdown_kwargs["state"] != "disabled" else self.ops.index_grid_fg,
1712
+ outline=fill,
1713
+ tag="dd",
1714
+ draw_outline=not dd_drawn,
1715
+ draw_arrow=True,
1716
+ open_=dd_coords == r,
1717
+ )
1718
+ else:
1719
+ max_width = self.current_width - 2
1720
+ if align.endswith("w"):
1721
+ draw_x = 3
1722
+ elif align.endswith("e"):
1723
+ draw_x = self.current_width - 3
1724
+ elif align.endswith("n"):
1691
1725
  draw_x = floor(self.current_width / 2)
1692
- checkbox_kwargs = self.get_cell_kwargs(datarn, key="checkbox")
1693
- if checkbox_kwargs and not dropdown_kwargs and mw > self.MT.index_txt_height + 1:
1694
- box_w = self.MT.index_txt_height + 1
1695
- if align == "w":
1696
- draw_x += box_w + 3
1697
- mw -= box_w + 3
1698
- elif align == "center":
1699
- draw_x += ceil(box_w / 2) + 1
1700
- mw -= box_w + 2
1701
- else:
1702
- mw -= box_w + 1
1703
- try:
1704
- draw_check = (
1705
- self.MT._row_index[datarn]
1706
- if isinstance(self.MT._row_index, (list, tuple))
1707
- else self.MT.data[datarn][self.MT._row_index]
1726
+ if (
1727
+ (checkbox_kwargs := self.get_cell_kwargs(datarn, key="checkbox"))
1728
+ and not dropdown_kwargs
1729
+ and max_width > self.MT.index_txt_height + 1
1730
+ ):
1731
+ box_w = self.MT.index_txt_height + 1
1732
+ if align.endswith("w"):
1733
+ draw_x += box_w + 3
1734
+ elif align.endswith("n"):
1735
+ draw_x += ceil(box_w / 2) + 1
1736
+ max_width -= box_w + 4
1737
+ try:
1738
+ draw_check = (
1739
+ self.MT._row_index[datarn]
1740
+ if isinstance(self.MT._row_index, (list, tuple))
1741
+ else self.MT.data[datarn][self.MT._row_index]
1742
+ )
1743
+ except Exception:
1744
+ draw_check = False
1745
+ self.redraw_checkbox(
1746
+ 2,
1747
+ rtopgridln + 2,
1748
+ self.MT.index_txt_height + 3,
1749
+ rtopgridln + self.MT.index_txt_height + 3,
1750
+ fill=fill if checkbox_kwargs["state"] == "normal" else self.ops.index_grid_fg,
1751
+ outline="",
1752
+ tag="cb",
1753
+ draw_check=draw_check,
1708
1754
  )
1709
- except Exception:
1710
- draw_check = False
1711
- self.redraw_checkbox(
1712
- 2,
1713
- rtopgridln + 2,
1714
- self.MT.index_txt_height + 3,
1715
- rtopgridln + self.MT.index_txt_height + 3,
1716
- fill=fill if checkbox_kwargs["state"] == "normal" else self.PAR.ops.index_grid_fg,
1717
- outline="",
1718
- tag="cb",
1719
- draw_check=draw_check,
1720
- )
1721
1755
  if treeview and isinstance(self.MT._row_index, list) and len(self.MT._row_index) > datarn:
1722
1756
  iid = self.MT._row_index[datarn].iid
1723
- mw -= self.MT.index_txt_height
1724
- if align == "w":
1757
+ max_width -= self.MT.index_txt_height
1758
+ if align.endswith("w"):
1725
1759
  draw_x += self.MT.index_txt_height + 3
1726
1760
  level, indent = self.get_iid_level_indent(iid)
1727
1761
  draw_x += indent + 5
@@ -1736,84 +1770,118 @@ class RowIndex(tk.Canvas):
1736
1770
  open_=self.MT._row_index[datarn].iid in self.tree_open_ids,
1737
1771
  level=level,
1738
1772
  )
1739
- lns = self.get_valid_cell_data_as_str(datarn, fix=False)
1740
- if not lns:
1773
+ if max_width <= 1:
1741
1774
  continue
1742
- draw_y = rtopgridln + self.MT.index_first_ln_ins
1743
- if mw > 5:
1744
- draw_y = rtopgridln + self.MT.index_first_ln_ins
1745
- start_ln = int((scrollpos_top - rtopgridln) / self.MT.index_xtra_lines_increment)
1746
- if start_ln < 0:
1747
- start_ln = 0
1748
- draw_y += start_ln * self.MT.index_xtra_lines_increment
1749
- lns = lns.split("\n")
1750
- if draw_y + self.MT.index_half_txt_height - 1 <= rbotgridln and len(lns) > start_ln:
1751
- for txt in islice(lns, start_ln, None):
1752
- if self.hidd_text:
1753
- iid, showing = self.hidd_text.popitem()
1754
- self.coords(iid, draw_x, draw_y)
1755
- if showing:
1756
- self.itemconfig(
1757
- iid,
1758
- text=txt,
1759
- fill=fill,
1760
- font=font,
1761
- anchor=align,
1762
- )
1763
- else:
1764
- self.itemconfig(
1765
- iid,
1766
- text=txt,
1767
- fill=fill,
1768
- font=font,
1769
- anchor=align,
1770
- state="normal",
1771
- )
1772
- self.tag_raise(iid)
1775
+ text = self.get_valid_cell_data_as_str(datarn, fix=False)
1776
+ if not text:
1777
+ continue
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")):
1790
+ if self.hidd_text:
1791
+ iid, showing = self.hidd_text.popitem()
1792
+ self.coords(iid, draw_x, draw_y)
1793
+ if showing:
1794
+ self.itemconfig(
1795
+ iid,
1796
+ text="\n".join(gen_lines),
1797
+ fill=fill,
1798
+ font=font,
1799
+ anchor=align,
1800
+ )
1801
+ else:
1802
+ self.itemconfig(
1803
+ iid,
1804
+ text="\n".join(gen_lines),
1805
+ fill=fill,
1806
+ font=font,
1807
+ anchor=align,
1808
+ state="normal",
1809
+ )
1810
+ self.tag_raise(iid)
1811
+ else:
1812
+ iid = self.create_text(
1813
+ draw_x,
1814
+ draw_y,
1815
+ text="\n".join(gen_lines),
1816
+ fill=fill,
1817
+ font=font,
1818
+ anchor=align,
1819
+ tags="t",
1820
+ )
1821
+ self.disp_text[iid] = True
1822
+ else:
1823
+ for text in gen_lines:
1824
+ if self.hidd_text:
1825
+ iid, showing = self.hidd_text.popitem()
1826
+ self.coords(iid, draw_x, draw_y)
1827
+ if showing:
1828
+ self.itemconfig(
1829
+ iid,
1830
+ text=text,
1831
+ fill=fill,
1832
+ font=font,
1833
+ anchor=align,
1834
+ )
1773
1835
  else:
1774
- iid = self.create_text(
1775
- draw_x,
1776
- draw_y,
1777
- text=txt,
1836
+ self.itemconfig(
1837
+ iid,
1838
+ text=text,
1778
1839
  fill=fill,
1779
1840
  font=font,
1780
1841
  anchor=align,
1781
- tag="t",
1842
+ state="normal",
1782
1843
  )
1783
- self.disp_text[iid] = True
1784
- wd = self.bbox(iid)
1785
- wd = wd[2] - wd[0]
1786
- if wd > mw:
1787
- if align == "w" and dropdown_kwargs:
1788
- txt = txt[: int(len(txt) * (mw / wd))]
1789
- self.itemconfig(iid, text=txt)
1790
- wd = self.bbox(iid)
1791
- while wd[2] - wd[0] > mw:
1792
- txt = txt[:-1]
1793
- self.itemconfig(iid, text=txt)
1794
- wd = self.bbox(iid)
1795
- elif align == "e" and checkbox_kwargs:
1796
- txt = txt[len(txt) - int(len(txt) * (mw / wd)) :]
1797
- self.itemconfig(iid, text=txt)
1798
- wd = self.bbox(iid)
1799
- while wd[2] - wd[0] > mw:
1800
- txt = txt[1:]
1801
- self.itemconfig(iid, text=txt)
1802
- wd = self.bbox(iid)
1803
- elif align == "center" and (dropdown_kwargs or checkbox_kwargs):
1804
- tmod = ceil((len(txt) - int(len(txt) * (mw / wd))) / 2)
1805
- txt = txt[tmod - 1 : -tmod]
1806
- self.itemconfig(iid, text=txt)
1807
- wd = self.bbox(iid)
1808
- self.c_align_cyc = cycle(self.centre_alignment_text_mod_indexes)
1809
- while wd[2] - wd[0] > mw:
1810
- txt = txt[next(self.c_align_cyc)]
1811
- self.itemconfig(iid, text=txt)
1812
- wd = self.bbox(iid)
1813
- self.coords(iid, draw_x, draw_y)
1814
- draw_y += self.MT.index_xtra_lines_increment
1815
- if draw_y + self.MT.index_half_txt_height - 1 > rbotgridln:
1816
- break
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
+
1858
+ xend = self.current_width - 6
1859
+ if (self.ops.show_horizontal_grid or self.height_resizing_enabled) and row_pos_exists:
1860
+ points = [
1861
+ self.current_width - 1,
1862
+ y_stop - 1,
1863
+ self.current_width - 1,
1864
+ scrollpos_top - 1,
1865
+ -1,
1866
+ scrollpos_top - 1,
1867
+ ]
1868
+ for r in range(grid_start_row, grid_end_row):
1869
+ draw_y = self.MT.row_positions[r]
1870
+ if r and self.height_resizing_enabled:
1871
+ self.visible_row_dividers[r] = (1, draw_y - 2, xend, draw_y + 2)
1872
+ points.extend(
1873
+ (
1874
+ -1,
1875
+ draw_y,
1876
+ self.current_width,
1877
+ draw_y,
1878
+ -1,
1879
+ draw_y,
1880
+ -1,
1881
+ self.MT.row_positions[r + 1] if len(self.MT.row_positions) - 1 > r else draw_y,
1882
+ )
1883
+ )
1884
+ self.redraw_gridline(points=points, fill=self.ops.index_grid_fg, width=1, tag="h")
1817
1885
  for dct in (
1818
1886
  self.hidd_text,
1819
1887
  self.hidd_high,
@@ -1826,6 +1894,8 @@ class RowIndex(tk.Canvas):
1826
1894
  if showing:
1827
1895
  self.itemconfig(iid, state="hidden")
1828
1896
  dct[iid] = False
1897
+ if self.disp_resize_lines:
1898
+ self.tag_raise("rh")
1829
1899
  return True
1830
1900
 
1831
1901
  def get_redraw_selections(self, startr: int, endr: int) -> dict[str, set[int]]:
@@ -1904,14 +1974,14 @@ class RowIndex(tk.Canvas):
1904
1974
  return False
1905
1975
  else:
1906
1976
  text = text if isinstance(text, str) else f"{text}"
1907
- if self.PAR.ops.cell_auto_resize_enabled:
1977
+ if self.ops.cell_auto_resize_enabled:
1908
1978
  self.set_row_height_run_binding(r)
1909
1979
  if self.text_editor.open and r == self.text_editor.row:
1910
1980
  self.text_editor.set_text(self.text_editor.get() + "" if not isinstance(text, str) else text)
1911
1981
  return False
1912
1982
  self.hide_text_editor()
1913
1983
  if not self.MT.see(r=r, c=0, keep_yscroll=True, check_cell_visibility=True):
1914
- self.MT.refresh()
1984
+ self.MT.main_table_redraw_grid_and_text(True, True)
1915
1985
  x = 0
1916
1986
  y = self.MT.row_positions[r]
1917
1987
  w = self.current_width + 1
@@ -1919,24 +1989,24 @@ class RowIndex(tk.Canvas):
1919
1989
  kwargs = {
1920
1990
  "menu_kwargs": DotDict(
1921
1991
  {
1922
- "font": self.PAR.ops.table_font,
1923
- "foreground": self.PAR.ops.popup_menu_fg,
1924
- "background": self.PAR.ops.popup_menu_bg,
1925
- "activebackground": self.PAR.ops.popup_menu_highlight_bg,
1926
- "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,
1927
1997
  }
1928
1998
  ),
1929
- "sheet_ops": self.PAR.ops,
1930
- "border_color": self.PAR.ops.index_selected_rows_bg,
1999
+ "sheet_ops": self.ops,
2000
+ "border_color": self.ops.index_selected_rows_bg,
1931
2001
  "text": text,
1932
2002
  "state": state,
1933
2003
  "width": w,
1934
2004
  "height": h,
1935
2005
  "show_border": True,
1936
- "bg": self.PAR.ops.index_editor_bg,
1937
- "fg": self.PAR.ops.index_editor_fg,
1938
- "select_bg": self.PAR.ops.index_editor_select_bg,
1939
- "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,
1940
2010
  "align": self.get_cell_align(r),
1941
2011
  "r": r,
1942
2012
  }
@@ -1975,13 +2045,13 @@ class RowIndex(tk.Canvas):
1975
2045
  > curr_height
1976
2046
  ):
1977
2047
  r = self.text_editor.row
1978
- new_height = curr_height + self.MT.index_xtra_lines_increment
2048
+ new_height = curr_height + self.MT.index_txt_height
1979
2049
  space_bot = self.MT.get_space_bot(r)
1980
2050
  if new_height > space_bot:
1981
2051
  new_height = space_bot
1982
2052
  if new_height != curr_height:
1983
2053
  self.set_row_height(r, new_height)
1984
- self.MT.refresh()
2054
+ self.MT.main_table_redraw_grid_and_text(True, True)
1985
2055
  self.text_editor.window.config(height=new_height)
1986
2056
  self.coords(self.text_editor.canvas_id, 0, self.MT.row_positions[r] + 1)
1987
2057
  if self.dropdown.open and self.dropdown.get_coords() == r:
@@ -2006,7 +2076,7 @@ class RowIndex(tk.Canvas):
2006
2076
  if self.text_editor.open:
2007
2077
  r = self.text_editor.row
2008
2078
  self.text_editor.window.config(height=self.MT.row_positions[r + 1] - self.MT.row_positions[r])
2009
- self.text_editor.tktext.config(font=self.PAR.ops.index_font)
2079
+ self.text_editor.tktext.config(font=self.ops.index_font)
2010
2080
  self.coords(
2011
2081
  self.text_editor.canvas_id,
2012
2082
  0,
@@ -2109,15 +2179,12 @@ class RowIndex(tk.Canvas):
2109
2179
  for i, v in enumerate(self.get_cell_kwargs(datarn, key="dropdown")["values"]):
2110
2180
  v_numlines = len(v.split("\n") if isinstance(v, str) else f"{v}".split("\n"))
2111
2181
  if v_numlines > 1:
2112
- win_h += (
2113
- self.MT.index_first_ln_ins + (v_numlines * self.MT.index_xtra_lines_increment) + 5
2114
- ) # end of cell
2182
+ win_h += self.MT.index_txt_height + (v_numlines * self.MT.index_txt_height) + 5 # end of cell
2115
2183
  else:
2116
2184
  win_h += self.MT.min_row_height
2117
2185
  if i == 5:
2118
2186
  break
2119
- if win_h > 500:
2120
- win_h = 500
2187
+ win_h = min(win_h, 500)
2121
2188
  space_bot = self.MT.get_space_bot(r, text_editor_h)
2122
2189
  space_top = int(self.MT.row_positions[r])
2123
2190
  anchor = "nw"
@@ -2180,15 +2247,15 @@ class RowIndex(tk.Canvas):
2180
2247
  reset_kwargs = {
2181
2248
  "r": r,
2182
2249
  "c": 0,
2183
- "bg": self.PAR.ops.index_editor_bg,
2184
- "fg": self.PAR.ops.index_editor_fg,
2185
- "select_bg": self.PAR.ops.index_editor_select_bg,
2186
- "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,
2187
2254
  "width": win_w,
2188
2255
  "height": win_h,
2189
- "font": self.PAR.ops.index_font,
2190
- "ops": self.PAR.ops,
2191
- "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,
2192
2259
  "align": self.get_cell_align(r),
2193
2260
  "values": kwargs["values"],
2194
2261
  "search_function": kwargs["search_function"],
@@ -2200,7 +2267,7 @@ class RowIndex(tk.Canvas):
2200
2267
  self.coords(self.dropdown.canvas_id, 0, ypos)
2201
2268
  self.dropdown.window.tkraise()
2202
2269
  else:
2203
- self.dropdown.window = self.PAR.dropdown_class(
2270
+ self.dropdown.window = self.PAR._dropdown_cls(
2204
2271
  self.winfo_toplevel(),
2205
2272
  **reset_kwargs,
2206
2273
  single_index="r",
@@ -2244,13 +2311,11 @@ class RowIndex(tk.Canvas):
2244
2311
  if r is not None and selection is not None:
2245
2312
  datarn = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
2246
2313
  kwargs = self.get_cell_kwargs(datarn, key="dropdown")
2247
- pre_edit_value = self.get_cell_data(datarn)
2248
- edited = False
2249
2314
  event_data = event_dict(
2250
2315
  name="end_edit_index",
2251
2316
  sheet=self.PAR.name,
2252
2317
  widget=self,
2253
- cells_header={datarn: pre_edit_value},
2318
+ cells_header={datarn: self.get_cell_data(datarn)},
2254
2319
  key="??",
2255
2320
  value=selection,
2256
2321
  loc=r,
@@ -2258,16 +2323,12 @@ class RowIndex(tk.Canvas):
2258
2323
  boxes=self.MT.get_boxes(),
2259
2324
  selected=self.MT.selected,
2260
2325
  )
2261
- if kwargs["select_function"] is not None:
2262
- kwargs["select_function"](event_data)
2263
- if self.MT.edit_validation_func:
2264
- selection = self.MT.edit_validation_func(event_data)
2265
- if selection is not None:
2266
- edited = self.set_cell_data_undo(r, datarn=datarn, value=selection, redraw=not redraw)
2267
- else:
2326
+ try_binding(kwargs["select_function"], event_data)
2327
+ selection = selection if not self.MT.edit_validation_func else self.MT.edit_validation_func(event_data)
2328
+ if selection is not None:
2268
2329
  edited = self.set_cell_data_undo(r, datarn=datarn, value=selection, redraw=not redraw)
2269
- if edited:
2270
- try_binding(self.extra_end_edit_cell_func, event_data)
2330
+ if edited:
2331
+ try_binding(self.extra_end_edit_cell_func, event_data)
2271
2332
  self.MT.recreate_all_selection_boxes()
2272
2333
  self.focus_set()
2273
2334
  self.hide_text_editor_and_dropdown(redraw=redraw)
@@ -2343,7 +2404,7 @@ class RowIndex(tk.Canvas):
2343
2404
  self.MT.undo_stack.append(stored_event_dict(event_data))
2344
2405
  self.set_cell_data(datarn=datarn, value=value)
2345
2406
  edited = True
2346
- 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:
2347
2408
  self.set_row_height_run_binding(r, only_if_too_small=False)
2348
2409
  if redraw:
2349
2410
  self.MT.refresh()
@@ -2358,7 +2419,7 @@ class RowIndex(tk.Canvas):
2358
2419
  self.fix_index(datarn)
2359
2420
  if self.get_cell_kwargs(datarn, key="checkbox"):
2360
2421
  self.MT._row_index[datarn] = try_to_bool(value)
2361
- elif self.PAR.ops.treeview:
2422
+ elif self.ops.treeview:
2362
2423
  self.MT._row_index[datarn].text = value
2363
2424
  else:
2364
2425
  self.MT._row_index[datarn] = value
@@ -2400,7 +2461,7 @@ class RowIndex(tk.Canvas):
2400
2461
  or (self.MT._row_index[datarn] is None and none_to_empty_str)
2401
2462
  ):
2402
2463
  return ""
2403
- if self.PAR.ops.treeview:
2464
+ if self.ops.treeview:
2404
2465
  return self.MT._row_index[datarn].text
2405
2466
  return self.MT._row_index[datarn]
2406
2467
 
@@ -2418,14 +2479,23 @@ class RowIndex(tk.Canvas):
2418
2479
  if fix:
2419
2480
  self.fix_index(datarn)
2420
2481
  try:
2421
- 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}"
2422
2489
  except Exception:
2423
2490
  value = ""
2424
- if not value and self.PAR.ops.show_default_index_for_empty:
2425
- 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)
2426
2493
  return value
2427
2494
 
2428
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))
2429
2499
  if self.get_cell_kwargs(datarn, key="checkbox", cell=r_ops):
2430
2500
  return False
2431
2501
  kwargs = self.get_cell_kwargs(datarn, key="dropdown", cell=r_ops)
@@ -2505,63 +2575,378 @@ class RowIndex(tk.Canvas):
2505
2575
 
2506
2576
  # Treeview Mode
2507
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
+
2508
2891
  def get_node_level(self, node: Node, level: int = 0) -> Generator[int]:
2509
2892
  yield level
2510
2893
  if node.parent:
2511
- yield from self.get_node_level(node.parent, level + 1)
2894
+ yield from self.get_node_level(self.tree[node.parent], level + 1)
2512
2895
 
2513
- def ancestors_all_open(self, iid: str, stop_at: str | Node = "") -> bool:
2896
+ def ancestors_all_open(self, iid: str, stop_at: str = "") -> bool:
2514
2897
  if stop_at:
2515
- stop_at = stop_at.iid
2516
2898
  for iid in self.get_iid_ancestors(iid):
2517
2899
  if iid == stop_at:
2518
2900
  return True
2519
- if iid not in self.tree_open_ids:
2901
+ elif iid not in self.tree_open_ids:
2520
2902
  return False
2521
2903
  return True
2522
2904
  return all(map(self.tree_open_ids.__contains__, self.get_iid_ancestors(iid)))
2523
2905
 
2524
2906
  def get_iid_ancestors(self, iid: str) -> Generator[str]:
2525
2907
  if self.tree[iid].parent:
2526
- yield self.tree[iid].parent.iid
2527
- 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)
2528
2910
 
2529
2911
  def get_iid_descendants(self, iid: str, check_open: bool = False) -> Generator[str]:
2530
- for cnode in self.tree[iid].children:
2531
- yield cnode.iid
2532
- if (check_open and cnode.children and cnode.iid in self.tree_open_ids) or (
2533
- not check_open and cnode.children
2534
- ):
2535
- 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)
2536
2916
 
2537
2917
  def items_parent(self, iid: str) -> str:
2538
2918
  if self.tree[iid].parent:
2539
- 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]
2540
2925
  return ""
2541
2926
 
2542
2927
  def gen_top_nodes(self) -> Generator[Node]:
2543
2928
  yield from (node for node in self.MT._row_index if node.parent == "")
2544
2929
 
2545
2930
  def get_iid_indent(self, iid: str) -> int:
2546
- if isinstance(self.PAR.ops.treeview_indent, str):
2547
- 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)
2548
2933
  else:
2549
- indent = self.PAR.ops.treeview_indent
2934
+ indent = self.ops.treeview_indent
2550
2935
  return indent * max(self.get_node_level(self.tree[iid]))
2551
2936
 
2552
2937
  def get_iid_level_indent(self, iid: str) -> tuple[int, int]:
2553
- if isinstance(self.PAR.ops.treeview_indent, str):
2554
- 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)
2555
2940
  else:
2556
- indent = self.PAR.ops.treeview_indent
2941
+ indent = self.ops.treeview_indent
2557
2942
  level = max(self.get_node_level(self.tree[iid]))
2558
2943
  return level, indent * level
2559
2944
 
2560
- def remove_node_from_parents_children(self, node: Node) -> None:
2561
- if node.parent:
2562
- node.parent.children.remove(node)
2563
- if not node.parent.children:
2564
- 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)
2565
2950
 
2566
2951
  def build_pid_causes_recursive_loop(self, iid: str, pid: str) -> bool:
2567
2952
  return any(
@@ -2575,4 +2960,121 @@ class RowIndex(tk.Canvas):
2575
2960
  def move_pid_causes_recursive_loop(self, to_move_iid: str, move_to_parent: str) -> bool:
2576
2961
  # if the parent the item is being moved under is one of the item's descendants
2577
2962
  # then it is a recursive loop
2578
- 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
+ )