tksheet 7.0.5__py3-none-any.whl → 7.1.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/sheet.py CHANGED
@@ -2,9 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import tkinter as tk
4
4
  from bisect import bisect_left
5
- from collections import deque
5
+ from collections import defaultdict, deque
6
6
  from collections.abc import Callable, Generator, Iterator, Sequence
7
7
  from itertools import accumulate, chain, islice, product
8
+ from timeit import default_timer
8
9
  from tkinter import ttk
9
10
  from typing import Literal
10
11
  from warnings import warn as WARNING
@@ -13,7 +14,6 @@ from .column_headers import ColumnHeaders
13
14
  from .functions import (
14
15
  add_highlight,
15
16
  add_to_options,
16
- backwards_compatibility_x,
17
17
  data_to_displayed_idxs,
18
18
  del_from_options,
19
19
  del_named_span_options,
@@ -30,20 +30,23 @@ from .functions import (
30
30
  is_iterable,
31
31
  key_to_span,
32
32
  num2alpha,
33
+ pop_positions,
33
34
  set_align,
34
35
  set_readonly,
35
36
  span_froms,
36
37
  span_ranges,
37
38
  tksheet_type_error,
39
+ unpack,
38
40
  )
39
41
  from .main_table import MainTable
40
42
  from .other_classes import (
41
- CurrentlySelectedClass, # noqa: F401
42
43
  DotDict,
43
44
  EventDataDict,
44
45
  FontTuple,
45
46
  GeneratedMouseEvent,
47
+ Node,
46
48
  Span,
49
+ Selected,
47
50
  )
48
51
  from .row_index import RowIndex
49
52
  from .sheet_options import (
@@ -63,9 +66,11 @@ from .types import (
63
66
  )
64
67
  from .vars import (
65
68
  USER_OS,
69
+ backwards_compatibility_keys,
66
70
  emitted_events,
67
71
  named_span_types,
68
72
  rc_binding,
73
+ scrollbar_options_keys,
69
74
  )
70
75
 
71
76
 
@@ -98,13 +103,13 @@ class Sheet(tk.Frame):
98
103
  total_rows: int | None = None,
99
104
  default_column_width: int = 120,
100
105
  default_header_height: str | int = "1",
106
+ default_row_index_width: int = 70,
101
107
  default_row_height: str | int = "1",
102
108
  max_column_width: Literal["inf"] | float = "inf",
103
109
  max_row_height: Literal["inf"] | float = "inf",
104
110
  max_header_height: Literal["inf"] | float = "inf",
105
111
  max_index_width: Literal["inf"] | float = "inf",
106
112
  after_redraw_time_ms: int = 20,
107
- default_row_index_width: int | None = None,
108
113
  set_all_heights_and_widths: bool = False,
109
114
  zoom: int = 100,
110
115
  align: str = "w",
@@ -115,52 +120,6 @@ class Sheet(tk.Frame):
115
120
  all_columns_displayed: bool = True,
116
121
  displayed_rows: list[int] = [],
117
122
  all_rows_displayed: bool = True,
118
- outline_thickness: int = 0,
119
- outline_color: str = theme_light_blue["outline_color"],
120
- theme: str = "light blue",
121
- frame_bg: str = theme_light_blue["table_bg"],
122
- popup_menu_fg: str = theme_light_blue["popup_menu_fg"],
123
- popup_menu_bg: str = theme_light_blue["popup_menu_bg"],
124
- popup_menu_highlight_bg: str = theme_light_blue["popup_menu_highlight_bg"],
125
- popup_menu_highlight_fg: str = theme_light_blue["popup_menu_highlight_fg"],
126
- table_grid_fg: str = theme_light_blue["table_grid_fg"],
127
- table_bg: str = theme_light_blue["table_bg"],
128
- table_fg: str = theme_light_blue["table_fg"],
129
- table_selected_box_cells_fg: str = theme_light_blue["table_selected_box_cells_fg"],
130
- table_selected_box_rows_fg: str = theme_light_blue["table_selected_box_rows_fg"],
131
- table_selected_box_columns_fg: str = theme_light_blue["table_selected_box_columns_fg"],
132
- table_selected_cells_border_fg: str = theme_light_blue["table_selected_cells_border_fg"],
133
- table_selected_cells_bg: str = theme_light_blue["table_selected_cells_bg"],
134
- table_selected_cells_fg: str = theme_light_blue["table_selected_cells_fg"],
135
- table_selected_rows_border_fg: str = theme_light_blue["table_selected_rows_border_fg"],
136
- table_selected_rows_bg: str = theme_light_blue["table_selected_rows_bg"],
137
- table_selected_rows_fg: str = theme_light_blue["table_selected_rows_fg"],
138
- table_selected_columns_border_fg: str = theme_light_blue["table_selected_columns_border_fg"],
139
- table_selected_columns_bg: str = theme_light_blue["table_selected_columns_bg"],
140
- table_selected_columns_fg: str = theme_light_blue["table_selected_columns_fg"],
141
- resizing_line_fg: str = theme_light_blue["resizing_line_fg"],
142
- drag_and_drop_bg: str = theme_light_blue["drag_and_drop_bg"],
143
- index_bg: str = theme_light_blue["index_bg"],
144
- index_border_fg: str = theme_light_blue["index_border_fg"],
145
- index_grid_fg: str = theme_light_blue["index_grid_fg"],
146
- index_fg: str = theme_light_blue["index_fg"],
147
- index_selected_cells_bg: str = theme_light_blue["index_selected_cells_bg"],
148
- index_selected_cells_fg: str = theme_light_blue["index_selected_cells_fg"],
149
- index_selected_rows_bg: str = theme_light_blue["index_selected_rows_bg"],
150
- index_selected_rows_fg: str = theme_light_blue["index_selected_rows_fg"],
151
- index_hidden_rows_expander_bg: str = theme_light_blue["index_hidden_rows_expander_bg"],
152
- header_bg: str = theme_light_blue["header_bg"],
153
- header_border_fg: str = theme_light_blue["header_border_fg"],
154
- header_grid_fg: str = theme_light_blue["header_grid_fg"],
155
- header_fg: str = theme_light_blue["header_fg"],
156
- header_selected_cells_bg: str = theme_light_blue["header_selected_cells_bg"],
157
- header_selected_cells_fg: str = theme_light_blue["header_selected_cells_fg"],
158
- header_selected_columns_bg: str = theme_light_blue["header_selected_columns_bg"],
159
- header_selected_columns_fg: str = theme_light_blue["header_selected_columns_fg"],
160
- header_hidden_columns_expander_bg: str = theme_light_blue["header_hidden_columns_expander_bg"],
161
- top_left_bg: str = theme_light_blue["top_left_bg"],
162
- top_left_fg: str = theme_light_blue["top_left_fg"],
163
- top_left_fg_highlight: str = theme_light_blue["top_left_fg_highlight"],
164
123
  to_clipboard_delimiter: str = "\t",
165
124
  to_clipboard_quotechar: str = '"',
166
125
  to_clipboard_lineterminator: str = "\n",
@@ -174,7 +133,7 @@ class Sheet(tk.Frame):
174
133
  show_dropdown_borders: bool = False,
175
134
  arrow_key_down_right_scroll_page: bool = False,
176
135
  cell_auto_resize_enabled: bool = True,
177
- auto_resize_row_index: bool = True,
136
+ auto_resize_row_index: bool | Literal["empty"] = "empty",
178
137
  auto_resize_columns: int | None = None,
179
138
  auto_resize_rows: int | None = None,
180
139
  set_cell_sizes_on_zoom: bool = False,
@@ -210,6 +169,91 @@ class Sheet(tk.Frame):
210
169
  show_horizontal_grid: bool = True,
211
170
  display_selected_fg_over_highlights: bool = False,
212
171
  show_selected_cells_border: bool = True,
172
+ treeview: bool = False,
173
+ # colors
174
+ outline_thickness: int = 0,
175
+ outline_color: str = theme_light_blue["outline_color"],
176
+ theme: str = "light blue",
177
+ frame_bg: str = theme_light_blue["table_bg"],
178
+ popup_menu_fg: str = theme_light_blue["popup_menu_fg"],
179
+ popup_menu_bg: str = theme_light_blue["popup_menu_bg"],
180
+ popup_menu_highlight_bg: str = theme_light_blue["popup_menu_highlight_bg"],
181
+ popup_menu_highlight_fg: str = theme_light_blue["popup_menu_highlight_fg"],
182
+ table_grid_fg: str = theme_light_blue["table_grid_fg"],
183
+ table_bg: str = theme_light_blue["table_bg"],
184
+ table_fg: str = theme_light_blue["table_fg"],
185
+ table_selected_box_cells_fg: str = theme_light_blue["table_selected_box_cells_fg"],
186
+ table_selected_box_rows_fg: str = theme_light_blue["table_selected_box_rows_fg"],
187
+ table_selected_box_columns_fg: str = theme_light_blue["table_selected_box_columns_fg"],
188
+ table_selected_cells_border_fg: str = theme_light_blue["table_selected_cells_border_fg"],
189
+ table_selected_cells_bg: str = theme_light_blue["table_selected_cells_bg"],
190
+ table_selected_cells_fg: str = theme_light_blue["table_selected_cells_fg"],
191
+ table_selected_rows_border_fg: str = theme_light_blue["table_selected_rows_border_fg"],
192
+ table_selected_rows_bg: str = theme_light_blue["table_selected_rows_bg"],
193
+ table_selected_rows_fg: str = theme_light_blue["table_selected_rows_fg"],
194
+ table_selected_columns_border_fg: str = theme_light_blue["table_selected_columns_border_fg"],
195
+ table_selected_columns_bg: str = theme_light_blue["table_selected_columns_bg"],
196
+ table_selected_columns_fg: str = theme_light_blue["table_selected_columns_fg"],
197
+ resizing_line_fg: str = theme_light_blue["resizing_line_fg"],
198
+ drag_and_drop_bg: str = theme_light_blue["drag_and_drop_bg"],
199
+ index_bg: str = theme_light_blue["index_bg"],
200
+ index_border_fg: str = theme_light_blue["index_border_fg"],
201
+ index_grid_fg: str = theme_light_blue["index_grid_fg"],
202
+ index_fg: str = theme_light_blue["index_fg"],
203
+ index_selected_cells_bg: str = theme_light_blue["index_selected_cells_bg"],
204
+ index_selected_cells_fg: str = theme_light_blue["index_selected_cells_fg"],
205
+ index_selected_rows_bg: str = theme_light_blue["index_selected_rows_bg"],
206
+ index_selected_rows_fg: str = theme_light_blue["index_selected_rows_fg"],
207
+ index_hidden_rows_expander_bg: str = theme_light_blue["index_hidden_rows_expander_bg"],
208
+ header_bg: str = theme_light_blue["header_bg"],
209
+ header_border_fg: str = theme_light_blue["header_border_fg"],
210
+ header_grid_fg: str = theme_light_blue["header_grid_fg"],
211
+ header_fg: str = theme_light_blue["header_fg"],
212
+ header_selected_cells_bg: str = theme_light_blue["header_selected_cells_bg"],
213
+ header_selected_cells_fg: str = theme_light_blue["header_selected_cells_fg"],
214
+ header_selected_columns_bg: str = theme_light_blue["header_selected_columns_bg"],
215
+ header_selected_columns_fg: str = theme_light_blue["header_selected_columns_fg"],
216
+ header_hidden_columns_expander_bg: str = theme_light_blue["header_hidden_columns_expander_bg"],
217
+ top_left_bg: str = theme_light_blue["top_left_bg"],
218
+ top_left_fg: str = theme_light_blue["top_left_fg"],
219
+ top_left_fg_highlight: str = theme_light_blue["top_left_fg_highlight"],
220
+ vertical_scroll_background: str = theme_light_blue["vertical_scroll_background"],
221
+ horizontal_scroll_background: str = theme_light_blue["horizontal_scroll_background"],
222
+ vertical_scroll_troughcolor: str = theme_light_blue["vertical_scroll_troughcolor"],
223
+ horizontal_scroll_troughcolor: str = theme_light_blue["horizontal_scroll_troughcolor"],
224
+ vertical_scroll_lightcolor: str = theme_light_blue["vertical_scroll_lightcolor"],
225
+ horizontal_scroll_lightcolor: str = theme_light_blue["horizontal_scroll_lightcolor"],
226
+ vertical_scroll_darkcolor: str = theme_light_blue["vertical_scroll_darkcolor"],
227
+ horizontal_scroll_darkcolor: str = theme_light_blue["horizontal_scroll_darkcolor"],
228
+ vertical_scroll_relief: str = theme_light_blue["vertical_scroll_relief"],
229
+ horizontal_scroll_relief: str = theme_light_blue["horizontal_scroll_relief"],
230
+ vertical_scroll_troughrelief: str = theme_light_blue["vertical_scroll_troughrelief"],
231
+ horizontal_scroll_troughrelief: str = theme_light_blue["horizontal_scroll_troughrelief"],
232
+ vertical_scroll_bordercolor: str = theme_light_blue["vertical_scroll_bordercolor"],
233
+ horizontal_scroll_bordercolor: str = theme_light_blue["horizontal_scroll_bordercolor"],
234
+ vertical_scroll_borderwidth: int = 1,
235
+ horizontal_scroll_borderwidth: int = 1,
236
+ vertical_scroll_gripcount: int = 0,
237
+ horizontal_scroll_gripcount: int = 0,
238
+ vertical_scroll_active_bg: str = theme_light_blue["vertical_scroll_active_bg"],
239
+ horizontal_scroll_active_bg: str = theme_light_blue["horizontal_scroll_active_bg"],
240
+ vertical_scroll_not_active_bg: str = theme_light_blue["vertical_scroll_not_active_bg"],
241
+ horizontal_scroll_not_active_bg: str = theme_light_blue["horizontal_scroll_not_active_bg"],
242
+ vertical_scroll_pressed_bg: str = theme_light_blue["vertical_scroll_pressed_bg"],
243
+ horizontal_scroll_pressed_bg: str = theme_light_blue["horizontal_scroll_pressed_bg"],
244
+ vertical_scroll_active_fg: str = theme_light_blue["vertical_scroll_active_fg"],
245
+ horizontal_scroll_active_fg: str = theme_light_blue["horizontal_scroll_active_fg"],
246
+ vertical_scroll_not_active_fg: str = theme_light_blue["vertical_scroll_not_active_fg"],
247
+ horizontal_scroll_not_active_fg: str = theme_light_blue["horizontal_scroll_not_active_fg"],
248
+ vertical_scroll_pressed_fg: str = theme_light_blue["vertical_scroll_pressed_fg"],
249
+ horizontal_scroll_pressed_fg: str = theme_light_blue["horizontal_scroll_pressed_fg"],
250
+ scrollbar_theme_inheritance: str = "default",
251
+ scrollbar_show_arrows: bool = True,
252
+ # changing the arrowsize (width) of the scrollbars
253
+ # is not working with 'default' theme
254
+ # use 'clam' theme instead if you want to change the width
255
+ vertical_scroll_arrowsize: str | int = "",
256
+ horizontal_scroll_arrowsize: str | int = "",
213
257
  # backwards compatibility
214
258
  column_width: int | None = None,
215
259
  header_height: str | int | None = None,
@@ -228,9 +272,19 @@ class Sheet(tk.Frame):
228
272
  "There have been many changes from tksheet version 6.x.x to version 7.x.x. Please see the changelog for more information."
229
273
  )
230
274
  self.ops = new_sheet_options()
275
+ if column_width is not None:
276
+ default_column_width = column_width
277
+ if header_height is not None:
278
+ default_header_height = header_height
279
+ if row_height is not None:
280
+ default_row_height = row_height
281
+ if row_index_width is not None:
282
+ default_row_index_width = row_index_width
283
+ if treeview:
284
+ index_align = "w"
285
+ auto_resize_row_index = True
231
286
  for k, v in locals().items():
232
- xk = backwards_compatibility_x(k)
233
- if xk in self.ops and v != self.ops[xk]:
287
+ if (xk := backwards_compatibility_keys.get(k, k)) in self.ops and v != self.ops[xk]:
234
288
  self.ops[xk] = v
235
289
  self.ops.from_clipboard_delimiters = (
236
290
  from_clipboard_delimiters
@@ -275,10 +329,6 @@ class Sheet(tk.Frame):
275
329
  max_header_height=max_header_height,
276
330
  max_row_height=max_row_height,
277
331
  max_index_width=max_index_width,
278
- default_row_index_width=default_row_index_width if row_index_width is None else row_index_width,
279
- default_header_height=default_header_height if header_height is None else header_height,
280
- default_column_width=default_column_width if column_width is None else column_width,
281
- default_row_height=default_row_height if row_height is None else row_height,
282
332
  show_index=show_row_index,
283
333
  show_header=show_header,
284
334
  column_headers_canvas=self.CH,
@@ -303,8 +353,64 @@ class Sheet(tk.Frame):
303
353
  row_index_canvas=self.RI,
304
354
  header_canvas=self.CH,
305
355
  )
306
- self.yscroll = ttk.Scrollbar(self, command=self.MT.set_yviews, orient="vertical")
307
- self.xscroll = ttk.Scrollbar(self, command=self.MT.set_xviews, orient="horizontal")
356
+ self.unique_id = f"{default_timer()}{self.winfo_id()}".replace(".", "")
357
+ style = ttk.Style()
358
+ for orientation in ("Vertical", "Horizontal"):
359
+ style.element_create(
360
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar.trough",
361
+ "from",
362
+ scrollbar_theme_inheritance,
363
+ )
364
+ style.element_create(
365
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar.thumb",
366
+ "from",
367
+ scrollbar_theme_inheritance,
368
+ )
369
+ style.element_create(
370
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar.grip",
371
+ "from",
372
+ scrollbar_theme_inheritance,
373
+ )
374
+ if not scrollbar_show_arrows:
375
+ style.layout(
376
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar",
377
+ [
378
+ (
379
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar.trough",
380
+ {
381
+ "children": [
382
+ (
383
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar.thumb",
384
+ {
385
+ "unit": "1",
386
+ "children": [
387
+ (
388
+ f"Sheet{self.unique_id}.{orientation}.TScrollbar.grip",
389
+ {"sticky": ""},
390
+ )
391
+ ],
392
+ "sticky": "nswe",
393
+ },
394
+ )
395
+ ],
396
+ "sticky": "ns" if orientation == "Vertical" else "ew",
397
+ },
398
+ )
399
+ ],
400
+ )
401
+ self.set_scrollbar_options()
402
+ self.yscroll = ttk.Scrollbar(
403
+ self,
404
+ command=self.MT.set_yviews,
405
+ orient="vertical",
406
+ style=f"Sheet{self.unique_id}.Vertical.TScrollbar",
407
+ )
408
+ self.xscroll = ttk.Scrollbar(
409
+ self,
410
+ command=self.MT.set_xviews,
411
+ orient="horizontal",
412
+ style=f"Sheet{self.unique_id}.Horizontal.TScrollbar",
413
+ )
308
414
  if show_top_left:
309
415
  self.TL.grid(row=0, column=0)
310
416
  if show_table:
@@ -1340,9 +1446,11 @@ class Sheet(tk.Frame):
1340
1446
  row_heights: bool = True,
1341
1447
  column_widths: bool = True,
1342
1448
  cell_options: bool = True,
1449
+ tags: bool = True,
1343
1450
  undo_stack: bool = True,
1344
1451
  selections: bool = True,
1345
1452
  sheet_options: bool = False,
1453
+ tree: bool = True,
1346
1454
  redraw: bool = True,
1347
1455
  ) -> Sheet:
1348
1456
  if table:
@@ -1352,17 +1460,23 @@ class Sheet(tk.Frame):
1352
1460
  if index:
1353
1461
  self.MT._row_index = []
1354
1462
  if row_heights:
1463
+ self.MT.saved_row_heights = {}
1355
1464
  self.MT.set_row_positions([])
1356
1465
  if column_widths:
1466
+ self.MT.saved_column_widths = {}
1357
1467
  self.MT.set_col_positions([])
1358
1468
  if cell_options:
1359
1469
  self.reset_all_options()
1470
+ if tags:
1471
+ self.MT.reset_tags()
1360
1472
  if undo_stack:
1361
1473
  self.reset_undos()
1362
1474
  if selections:
1363
1475
  self.MT.deselect(redraw=False)
1364
1476
  if sheet_options:
1365
1477
  self.ops = new_sheet_options()
1478
+ if tree:
1479
+ self.RI.reset_tree()
1366
1480
  self.set_refresh_timer(redraw)
1367
1481
  return self
1368
1482
 
@@ -1467,7 +1581,7 @@ class Sheet(tk.Frame):
1467
1581
  event_data = event_dict(
1468
1582
  name="edit_table",
1469
1583
  sheet=self.name,
1470
- selected=self.MT.currently_selected(),
1584
+ selected=self.MT.selected,
1471
1585
  )
1472
1586
  set_t = self.event_data_set_table_cell
1473
1587
  set_i, set_h = self.event_data_set_index_cell, self.event_data_set_header_cell
@@ -1697,7 +1811,7 @@ class Sheet(tk.Frame):
1697
1811
  event_data = event_dict(
1698
1812
  name="edit_table",
1699
1813
  sheet=self.name,
1700
- selected=self.MT.currently_selected(),
1814
+ selected=self.MT.selected,
1701
1815
  )
1702
1816
  if index:
1703
1817
  for r in rows:
@@ -1808,6 +1922,7 @@ class Sheet(tk.Frame):
1808
1922
  fill: bool = True,
1809
1923
  undo: bool = False,
1810
1924
  emit_event: bool = False,
1925
+ create_selections: bool = True,
1811
1926
  redraw: bool = True,
1812
1927
  ) -> EventDataDict:
1813
1928
  total_cols = None
@@ -1870,8 +1985,9 @@ class Sheet(tk.Frame):
1870
1985
  name="add_rows",
1871
1986
  sheet=self.name,
1872
1987
  boxes=self.MT.get_boxes(),
1873
- selected=self.MT.currently_selected(),
1988
+ selected=self.MT.selected,
1874
1989
  ),
1990
+ create_selections=create_selections,
1875
1991
  )
1876
1992
  if undo:
1877
1993
  self.MT.undo_stack.append(ev_stack_dict(event_data))
@@ -1889,6 +2005,7 @@ class Sheet(tk.Frame):
1889
2005
  fill: bool = True,
1890
2006
  undo: bool = False,
1891
2007
  emit_event: bool = False,
2008
+ create_selections: bool = True,
1892
2009
  redraw: bool = True,
1893
2010
  ) -> EventDataDict:
1894
2011
  old_total = self.MT.equalize_data_row_lengths()
@@ -1959,8 +2076,9 @@ class Sheet(tk.Frame):
1959
2076
  name="add_columns",
1960
2077
  sheet=self.name,
1961
2078
  boxes=self.MT.get_boxes(),
1962
- selected=self.MT.currently_selected(),
2079
+ selected=self.MT.selected,
1963
2080
  ),
2081
+ create_selections=create_selections,
1964
2082
  )
1965
2083
  if undo:
1966
2084
  self.MT.undo_stack.append(ev_stack_dict(event_data))
@@ -2018,7 +2136,7 @@ class Sheet(tk.Frame):
2018
2136
  name="delete_rows",
2019
2137
  sheet=self.name,
2020
2138
  boxes=self.MT.get_boxes(),
2021
- selected=self.MT.currently_selected(),
2139
+ selected=self.MT.selected,
2022
2140
  )
2023
2141
  if not data_indexes:
2024
2142
  event_data = self.MT.delete_rows_displayed(rows, event_data)
@@ -2059,7 +2177,7 @@ class Sheet(tk.Frame):
2059
2177
  name="delete_columns",
2060
2178
  sheet=self.name,
2061
2179
  boxes=self.MT.get_boxes(),
2062
- selected=self.MT.currently_selected(),
2180
+ selected=self.MT.selected,
2063
2181
  )
2064
2182
  if not data_indexes:
2065
2183
  event_data = self.MT.delete_columns_displayed(columns, event_data)
@@ -2116,7 +2234,7 @@ class Sheet(tk.Frame):
2116
2234
  raise ValueError("number argument must be integer and > 0")
2117
2235
  if number > len(self.MT.data):
2118
2236
  if mod_positions:
2119
- height = self.MT.get_lines_cell_height(int(self.MT.default_row_height[0]))
2237
+ height = self.MT.get_default_row_height()
2120
2238
  for r in range(number - len(self.MT.data)):
2121
2239
  self.MT.insert_row_position("end", height)
2122
2240
  elif number < len(self.MT.data):
@@ -2140,7 +2258,7 @@ class Sheet(tk.Frame):
2140
2258
  raise ValueError("number argument must be integer and > 0")
2141
2259
  if number > total_cols:
2142
2260
  if mod_positions:
2143
- width = self.MT.default_column_width
2261
+ width = self.ops.default_column_width
2144
2262
  for c in range(number - total_cols):
2145
2263
  self.MT.insert_col_position("end", width)
2146
2264
  elif number < total_cols:
@@ -2254,7 +2372,7 @@ class Sheet(tk.Frame):
2254
2372
  data_idxs, disp_idxs, event_data = self.MT.move_rows_adjust_options_dict(
2255
2373
  data_new_idxs=data_new_idxs,
2256
2374
  data_old_idxs=dict(zip(data_new_idxs.values(), data_new_idxs)),
2257
- totalcols=None,
2375
+ totalrows=None,
2258
2376
  disp_new_idxs=disp_new_idxs,
2259
2377
  move_data=move_data,
2260
2378
  create_selections=create_selections,
@@ -2398,11 +2516,11 @@ class Sheet(tk.Frame):
2398
2516
  ) -> Span:
2399
2517
  span = self.span_from_key(*key)
2400
2518
  if span.table:
2401
- self.MT.destroy_opened_dropdown_window()
2519
+ self.MT.hide_dropdown_window()
2402
2520
  if span.index:
2403
- self.RI.destroy_opened_dropdown_window()
2521
+ self.RI.hide_dropdown_window()
2404
2522
  if span.header:
2405
- self.CH.destroy_opened_dropdown_window()
2523
+ self.CH.hide_dropdown_window()
2406
2524
  self.del_options_using_span(span, "dropdown")
2407
2525
  self.set_refresh_timer(redraw)
2408
2526
  return span
@@ -2789,8 +2907,14 @@ class Sheet(tk.Frame):
2789
2907
 
2790
2908
  # Getting Selected Cells
2791
2909
 
2792
- def get_currently_selected(self) -> tuple[()] | CurrentlySelectedClass:
2793
- return self.MT.currently_selected()
2910
+ def get_currently_selected(self) -> tuple[()] | Selected:
2911
+ # if self.MT.selected:
2912
+ # return self.MT.selected._replace(type_=self.MT.selected.type_[:-1])
2913
+ return self.MT.selected
2914
+
2915
+ @property
2916
+ def selected(self) -> tuple[()] | Selected:
2917
+ return self.MT.selected
2794
2918
 
2795
2919
  def get_selected_rows(
2796
2920
  self,
@@ -3057,31 +3181,18 @@ class Sheet(tk.Frame):
3057
3181
 
3058
3182
  def default_column_width(self, width: int | None = None) -> int:
3059
3183
  if isinstance(width, int):
3060
- if width < self.MT.min_column_width:
3061
- self.MT.default_column_width = self.MT.min_column_width + 20
3062
- else:
3063
- self.MT.default_column_width = int(width)
3064
- return self.MT.default_column_width
3184
+ self.ops.default_column_width = width
3185
+ return self.ops.default_column_width
3065
3186
 
3066
3187
  def default_row_height(self, height: int | str | None = None) -> int:
3067
- if height is not None:
3068
- self.MT.default_row_height = (
3069
- height if isinstance(height, str) else "pixels",
3070
- height if isinstance(height, int) else self.MT.get_lines_cell_height(int(height)),
3071
- )
3072
- return self.MT.default_row_height[1]
3188
+ if isinstance(height, (int, str)):
3189
+ self.ops.default_row_height = height
3190
+ return self.ops.default_row_height
3073
3191
 
3074
3192
  def default_header_height(self, height: int | str | None = None) -> int:
3075
- if height is not None:
3076
- self.MT.default_header_height = (
3077
- height if isinstance(height, str) else "pixels",
3078
- (
3079
- height
3080
- if isinstance(height, int)
3081
- else self.MT.get_lines_cell_height(int(height), font=self.ops.header_font)
3082
- ),
3083
- )
3084
- return self.MT.default_header_height[1]
3193
+ if isinstance(height, (int, str)):
3194
+ self.ops.default_header_height = height
3195
+ return self.ops.default_header_height
3085
3196
 
3086
3197
  def set_cell_size_to_text(
3087
3198
  self,
@@ -3189,7 +3300,7 @@ class Sheet(tk.Frame):
3189
3300
  canvas_positions: bool = False,
3190
3301
  reset: bool = False,
3191
3302
  ) -> Sheet:
3192
- if reset:
3303
+ if reset or column_widths is None:
3193
3304
  self.MT.reset_col_positions()
3194
3305
  elif is_iterable(column_widths):
3195
3306
  if canvas_positions and isinstance(column_widths, list):
@@ -3204,9 +3315,9 @@ class Sheet(tk.Frame):
3204
3315
  canvas_positions: bool = False,
3205
3316
  reset: bool = False,
3206
3317
  ) -> Sheet:
3207
- if reset:
3318
+ if reset or row_heights is None:
3208
3319
  self.MT.reset_row_positions()
3209
- if is_iterable(row_heights):
3320
+ elif is_iterable(row_heights):
3210
3321
  if canvas_positions and isinstance(row_heights, list):
3211
3322
  self.MT.row_positions = row_heights
3212
3323
  else:
@@ -3308,10 +3419,10 @@ class Sheet(tk.Frame):
3308
3419
  if total_rows is None and total_columns is None:
3309
3420
  return len(self.MT.row_positions) - 1, len(self.MT.col_positions) - 1
3310
3421
  if isinstance(total_rows, int):
3311
- height = self.MT.get_lines_cell_height(int(self.MT.default_row_height[0]))
3422
+ height = self.MT.get_default_row_height()
3312
3423
  self.MT.row_positions = list(accumulate(chain([0], (height for row in range(total_rows)))))
3313
3424
  if isinstance(total_columns, int):
3314
- width = self.MT.default_column_width
3425
+ width = self.ops.default_column_width
3315
3426
  self.MT.col_positions = list(accumulate(chain([0], (width for column in range(total_columns)))))
3316
3427
  return self
3317
3428
 
@@ -3324,13 +3435,13 @@ class Sheet(tk.Frame):
3324
3435
  return self
3325
3436
 
3326
3437
  def get_example_canvas_column_widths(self, total_cols: int | None = None) -> list[float]:
3327
- colpos = int(self.MT.default_column_width)
3438
+ colpos = int(self.ops.default_column_width)
3328
3439
  if isinstance(total_cols, int):
3329
3440
  return list(accumulate(chain([0], (colpos for c in range(total_cols)))))
3330
3441
  return list(accumulate(chain([0], (colpos for c in range(len(self.MT.col_positions) - 1)))))
3331
3442
 
3332
3443
  def get_example_canvas_row_heights(self, total_rows: int | None = None) -> list[float]:
3333
- rowpos = self.MT.default_row_height[1]
3444
+ rowpos = self.MT.get_default_row_height()
3334
3445
  if isinstance(total_rows, int):
3335
3446
  return list(accumulate(chain([0], (rowpos for c in range(total_rows)))))
3336
3447
  return list(accumulate(chain([0], (rowpos for c in range(len(self.MT.row_positions) - 1)))))
@@ -3457,15 +3568,25 @@ class Sheet(tk.Frame):
3457
3568
  def cell_completely_visible(self, r: int, c: int, seperate_axes: bool = False) -> bool:
3458
3569
  return self.MT.cell_completely_visible(r, c, seperate_axes)
3459
3570
 
3460
- def set_xview(self, position: float, option: str = "moveto") -> Sheet:
3461
- self.MT.set_xviews(option, position)
3462
- return self
3571
+ def set_xview(self, position: None | float = None, option: str = "moveto") -> Sheet | tuple[float, float]:
3572
+ if position is not None:
3573
+ self.MT.set_xviews(option, position)
3574
+ return self
3575
+ return self.MT.xview()
3463
3576
 
3464
- def set_yview(self, position: float, option: str = "moveto") -> Sheet:
3465
- self.MT.set_yviews(option, position)
3466
- return self
3577
+ xview = set_xview
3578
+ xview_moveto = set_xview
3579
+
3580
+ def set_yview(self, position: None | float = None, option: str = "moveto") -> Sheet | tuple[float, float]:
3581
+ if position is not None:
3582
+ self.MT.set_yviews(option, position)
3583
+ return self
3584
+ return self.MT.yview()
3585
+
3586
+ yview = set_yview
3587
+ yview_moveto = set_yview
3467
3588
 
3468
- def set_view(self, x_args: [str, float], y_args: [str, float]) -> Sheet:
3589
+ def set_view(self, x_args: list[str, float], y_args: list[str, float]) -> Sheet:
3469
3590
  self.MT.set_view(x_args, y_args)
3470
3591
  return self
3471
3592
 
@@ -3480,6 +3601,8 @@ class Sheet(tk.Frame):
3480
3601
  def displayed_column_to_data(self, c: int) -> int:
3481
3602
  return c if self.MT.all_columns_displayed else self.MT.displayed_columns[c]
3482
3603
 
3604
+ data_c = displayed_column_to_data
3605
+
3483
3606
  def display_columns(
3484
3607
  self,
3485
3608
  columns: None | Literal["all"] | Iterator[int] = None,
@@ -3504,31 +3627,70 @@ class Sheet(tk.Frame):
3504
3627
  self.set_refresh_timer(redraw if redraw else refresh)
3505
3628
  return res
3506
3629
 
3507
- # uses displayed indexes
3508
3630
  def hide_columns(
3509
3631
  self,
3510
- columns: int | set | Iterator[int] = set(),
3632
+ columns: int | set[int] | Iterator[int],
3511
3633
  redraw: bool = True,
3512
3634
  deselect_all: bool = True,
3635
+ data_indexes: bool = False,
3513
3636
  ) -> Sheet:
3514
3637
  if isinstance(columns, int):
3515
- _columns = {columns}
3516
- elif isinstance(columns, set):
3517
- _columns = columns
3518
- else:
3519
- _columns = set(columns)
3520
- if not _columns:
3521
- return
3638
+ columns = {columns}
3639
+ elif not isinstance(columns, set):
3640
+ columns = set(columns)
3641
+ if not columns:
3642
+ return
3522
3643
  if self.MT.all_columns_displayed:
3523
- _columns = [c for c in range(self.MT.total_data_cols()) if c not in _columns]
3644
+ self.MT.displayed_columns = [c for c in range(self.MT.total_data_cols()) if c not in columns]
3645
+ to_pop = {c: c for c in columns}
3524
3646
  else:
3525
- _columns = [e for c, e in enumerate(self.MT.displayed_columns) if c not in _columns]
3526
- self.display_columns(
3527
- columns=_columns,
3528
- all_columns_displayed=False,
3529
- redraw=redraw,
3530
- deselect_all=deselect_all,
3647
+ to_pop = {}
3648
+ new_disp = []
3649
+ if data_indexes:
3650
+ for i, c in enumerate(self.MT.displayed_columns):
3651
+ if c not in columns:
3652
+ new_disp.append(c)
3653
+ else:
3654
+ to_pop[i] = c
3655
+ else:
3656
+ for i, c in enumerate(self.MT.displayed_columns):
3657
+ if i not in columns:
3658
+ new_disp.append(c)
3659
+ else:
3660
+ to_pop[i] = c
3661
+ self.MT.displayed_columns = new_disp
3662
+ self.MT.all_columns_displayed = False
3663
+ self.MT.set_col_positions(
3664
+ pop_positions(
3665
+ itr=self.MT.gen_column_widths,
3666
+ to_pop=to_pop,
3667
+ save_to=self.MT.saved_column_widths,
3668
+ ),
3531
3669
  )
3670
+ if deselect_all:
3671
+ self.MT.deselect(redraw=False)
3672
+ self.set_refresh_timer(redraw)
3673
+ return self
3674
+
3675
+ # uses data indexes
3676
+ def show_columns(
3677
+ self,
3678
+ columns: int | Iterator[int],
3679
+ redraw: bool = True,
3680
+ deselect_all: bool = True,
3681
+ ) -> Sheet:
3682
+ if isinstance(columns, int):
3683
+ columns = [columns]
3684
+ cws = self.MT.get_column_widths()
3685
+ for column in columns:
3686
+ idx = bisect_left(self.MT.displayed_columns, column)
3687
+ if len(self.MT.displayed_columns) == idx or self.MT.displayed_columns[idx] != column:
3688
+ self.MT.displayed_columns.insert(idx, column)
3689
+ cws.insert(idx, self.MT.saved_column_widths.pop(column, self.PAR.ops.default_column_width))
3690
+ self.MT.set_col_positions(cws)
3691
+ if deselect_all:
3692
+ self.MT.deselect(redraw=False)
3693
+ self.set_refresh_timer(redraw)
3532
3694
  return self
3533
3695
 
3534
3696
  def all_columns_displayed(self, a: bool | None = None) -> bool:
@@ -3546,6 +3708,8 @@ class Sheet(tk.Frame):
3546
3708
  def displayed_row_to_data(self, r: int) -> int:
3547
3709
  return r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
3548
3710
 
3711
+ data_r = displayed_row_to_data
3712
+
3549
3713
  def display_rows(
3550
3714
  self,
3551
3715
  rows: None | Literal["all"] | Iterator[int] = None,
@@ -3568,31 +3732,70 @@ class Sheet(tk.Frame):
3568
3732
  self.set_refresh_timer(redraw if redraw else refresh)
3569
3733
  return res
3570
3734
 
3571
- # uses displayed indexes
3572
3735
  def hide_rows(
3573
3736
  self,
3574
- rows: int | set | Iterator[int] = set(),
3737
+ rows: int | set[int] | Iterator[int],
3575
3738
  redraw: bool = True,
3576
3739
  deselect_all: bool = True,
3740
+ data_indexes: bool = False,
3577
3741
  ) -> Sheet:
3578
3742
  if isinstance(rows, int):
3579
- _rows = {rows}
3580
- elif isinstance(rows, set):
3581
- _rows = rows
3582
- else:
3583
- _rows = set(rows)
3584
- if not _rows:
3585
- return
3743
+ rows = {rows}
3744
+ elif not isinstance(rows, set):
3745
+ rows = set(rows)
3746
+ if not rows:
3747
+ return
3586
3748
  if self.MT.all_rows_displayed:
3587
- _rows = [r for r in range(self.MT.total_data_rows()) if r not in _rows]
3749
+ self.MT.displayed_rows = [r for r in range(self.MT.total_data_rows()) if r not in rows]
3750
+ to_pop = {r: r for r in rows}
3588
3751
  else:
3589
- _rows = [e for r, e in enumerate(self.MT.displayed_rows) if r not in _rows]
3590
- self.display_rows(
3591
- rows=_rows,
3592
- all_rows_displayed=False,
3593
- redraw=redraw,
3594
- deselect_all=deselect_all,
3752
+ to_pop = {}
3753
+ new_disp = []
3754
+ if data_indexes:
3755
+ for i, r in enumerate(self.MT.displayed_rows):
3756
+ if r not in rows:
3757
+ new_disp.append(r)
3758
+ else:
3759
+ to_pop[i] = r
3760
+ else:
3761
+ for i, r in enumerate(self.MT.displayed_rows):
3762
+ if i not in rows:
3763
+ new_disp.append(r)
3764
+ else:
3765
+ to_pop[i] = r
3766
+ self.MT.displayed_rows = new_disp
3767
+ self.MT.all_rows_displayed = False
3768
+ self.MT.set_row_positions(
3769
+ pop_positions(
3770
+ itr=self.MT.gen_row_heights,
3771
+ to_pop=to_pop,
3772
+ save_to=self.MT.saved_row_heights,
3773
+ ),
3595
3774
  )
3775
+ if deselect_all:
3776
+ self.MT.deselect(redraw=False)
3777
+ self.set_refresh_timer(redraw)
3778
+ return self
3779
+
3780
+ # uses data indexes
3781
+ def show_rows(
3782
+ self,
3783
+ rows: int | Iterator[int],
3784
+ redraw: bool = True,
3785
+ deselect_all: bool = True,
3786
+ ) -> Sheet:
3787
+ if isinstance(rows, int):
3788
+ rows = [rows]
3789
+ rhs = self.MT.get_row_heights()
3790
+ for row in rows:
3791
+ idx = bisect_left(self.MT.displayed_rows, row)
3792
+ if len(self.MT.displayed_rows) == idx or self.MT.displayed_rows[idx] != row:
3793
+ self.MT.displayed_rows.insert(idx, row)
3794
+ rhs.insert(idx, self.MT.saved_row_heights.pop(row, self.MT.get_default_row_height()))
3795
+ self.MT.set_row_positions(rhs)
3796
+ if deselect_all:
3797
+ self.MT.deselect(redraw=False)
3798
+ self.set_refresh_timer(redraw)
3596
3799
  return self
3597
3800
 
3598
3801
  def all_rows_displayed(self, a: bool | None = None) -> bool:
@@ -3744,22 +3947,34 @@ class Sheet(tk.Frame):
3744
3947
  def set_text_editor_value(
3745
3948
  self,
3746
3949
  text: str = "",
3747
- r: int | None = None,
3748
- c: int | None = None,
3749
3950
  ) -> Sheet:
3750
- if self.MT.text_editor is not None and r is None and c is None:
3751
- self.MT.text_editor.set_text(text)
3752
- elif self.MT.text_editor is not None and self.MT.text_editor_loc == (r, c):
3753
- self.MT.text_editor.set_text(text)
3951
+ if self.MT.text_editor.open:
3952
+ self.MT.text_editor.window.set_text(text)
3953
+ return self
3954
+
3955
+ def set_index_text_editor_value(
3956
+ self,
3957
+ text: str = "",
3958
+ ) -> Sheet:
3959
+ if self.RI.text_editor.open:
3960
+ self.RI.text_editor.window.set_text(text)
3961
+ return self
3962
+
3963
+ def set_header_text_editor_value(
3964
+ self,
3965
+ text: str = "",
3966
+ ) -> Sheet:
3967
+ if self.CH.text_editor.open:
3968
+ self.CH.text_editor.window.set_text(text)
3754
3969
  return self
3755
3970
 
3756
3971
  def destroy_text_editor(self, event: object = None) -> Sheet:
3757
- self.MT.destroy_text_editor(event=event)
3972
+ self.MT.hide_text_editor(event=event)
3758
3973
  return self
3759
3974
 
3760
3975
  def get_text_editor_widget(self, event: object = None) -> tk.Text | None:
3761
3976
  try:
3762
- return self.MT.text_editor.textedit
3977
+ return self.MT.text_editor.tktext
3763
3978
  except Exception:
3764
3979
  return None
3765
3980
 
@@ -3771,7 +3986,7 @@ class Sheet(tk.Frame):
3771
3986
  if key == "all":
3772
3987
  for key in self.MT.text_editor_user_bound_keys:
3773
3988
  try:
3774
- self.MT.text_editor.textedit.unbind(key)
3989
+ self.MT.text_editor.tktext.unbind(key)
3775
3990
  except Exception:
3776
3991
  pass
3777
3992
  self.MT.text_editor_user_bound_keys = {}
@@ -3779,11 +3994,20 @@ class Sheet(tk.Frame):
3779
3994
  if key in self.MT.text_editor_user_bound_keys:
3780
3995
  del self.MT.text_editor_user_bound_keys[key]
3781
3996
  try:
3782
- self.MT.text_editor.textedit.unbind(key)
3997
+ self.MT.text_editor.tktext.unbind(key)
3783
3998
  except Exception:
3784
3999
  pass
3785
4000
  return self
3786
4001
 
4002
+ def get_text_editor_value(self) -> str | None:
4003
+ return self.MT.text_editor.get()
4004
+
4005
+ def close_text_editor(self, set_data: bool = True) -> Sheet:
4006
+ if self.MT.text_editor.open:
4007
+ event = ("ButtonPress-1",) if set_data else ("Escape",)
4008
+ self.MT.close_text_editor(editor_info=self.MT.text_editor.coords + event)
4009
+ return self
4010
+
3787
4011
  # Sheet Options and Other Functions
3788
4012
 
3789
4013
  def set_options(self, redraw: bool = True, **kwargs) -> Sheet:
@@ -3797,29 +4021,7 @@ class Sheet(tk.Frame):
3797
4021
  else "".join(self.ops.from_clipboard_delimiters)
3798
4022
  )
3799
4023
  if "default_row_height" in kwargs:
3800
- self.MT.default_row_height = (
3801
- kwargs["default_row_height"] if isinstance(kwargs["default_row_height"], str) else "pixels",
3802
- (
3803
- kwargs["default_row_height"]
3804
- if isinstance(kwargs["default_row_height"], int)
3805
- else self.MT.get_lines_cell_height(int(kwargs["default_row_height"]))
3806
- ),
3807
- )
3808
- if "default_column_width" in kwargs:
3809
- self.MT.default_column_width = (
3810
- self.MT.min_column_width + 20
3811
- if kwargs["column_width"] < self.MT.min_column_width
3812
- else int(kwargs["column_width"])
3813
- )
3814
- if "default_header_height" in kwargs:
3815
- self.MT.default_header_height = (
3816
- kwargs["default_header_height"] if isinstance(kwargs["default_header_height"], str) else "pixels",
3817
- (
3818
- kwargs["default_header_height"]
3819
- if isinstance(kwargs["default_header_height"], int)
3820
- else self.MT.get_lines_cell_height(int(kwargs["default_header_height"]), font=self.ops.header_font)
3821
- ),
3822
- )
4024
+ self.default_row_height(kwargs["default_row_height"])
3823
4025
  if "default_header" in kwargs:
3824
4026
  self.CH.default_header = kwargs["default_header"].lower()
3825
4027
  if "default_row_index" in kwargs:
@@ -3863,11 +4065,44 @@ class Sheet(tk.Frame):
3863
4065
  highlightbackground=kwargs["outline_color"],
3864
4066
  highlightcolor=kwargs["outline_color"],
3865
4067
  )
4068
+ if any(k in kwargs for k in scrollbar_options_keys):
4069
+ self.set_scrollbar_options()
3866
4070
  self.MT.create_rc_menus()
3867
4071
  self.MT.key_bindings()
3868
4072
  self.set_refresh_timer(redraw)
3869
4073
  return self
3870
4074
 
4075
+ def set_scrollbar_options(self) -> Sheet:
4076
+ style = ttk.Style()
4077
+ for orientation in ("vertical", "horizontal"):
4078
+ style.configure(
4079
+ f"Sheet{self.unique_id}.{orientation.capitalize()}.TScrollbar",
4080
+ background=self.ops[f"{orientation}_scroll_background"],
4081
+ troughcolor=self.ops[f"{orientation}_scroll_troughcolor"],
4082
+ lightcolor=self.ops[f"{orientation}_scroll_lightcolor"],
4083
+ darkcolor=self.ops[f"{orientation}_scroll_darkcolor"],
4084
+ relief=self.ops[f"{orientation}_scroll_relief"],
4085
+ troughrelief=self.ops[f"{orientation}_scroll_troughrelief"],
4086
+ bordercolor=self.ops[f"{orientation}_scroll_bordercolor"],
4087
+ borderwidth=self.ops[f"{orientation}_scroll_borderwidth"],
4088
+ gripcount=self.ops[f"{orientation}_scroll_gripcount"],
4089
+ arrowsize=self.ops[f"{orientation}_scroll_arrowsize"],
4090
+ )
4091
+ style.map(
4092
+ f"Sheet{self.unique_id}.{orientation.capitalize()}.TScrollbar",
4093
+ foreground=[
4094
+ ("!active", self.ops[f"{orientation}_scroll_not_active_fg"]),
4095
+ ("pressed", self.ops[f"{orientation}_scroll_pressed_fg"]),
4096
+ ("active", self.ops[f"{orientation}_scroll_active_fg"]),
4097
+ ],
4098
+ background=[
4099
+ ("!active", self.ops[f"{orientation}_scroll_not_active_bg"]),
4100
+ ("pressed", self.ops[f"{orientation}_scroll_pressed_bg"]),
4101
+ ("active", self.ops[f"{orientation}_scroll_active_bg"]),
4102
+ ],
4103
+ )
4104
+ return self
4105
+
3871
4106
  def get_cell_options(
3872
4107
  self,
3873
4108
  key: None | str = None,
@@ -3968,6 +4203,570 @@ class Sheet(tk.Frame):
3968
4203
  self.MT.main_table_redraw_grid_and_text(redraw_header=redraw_header, redraw_row_index=redraw_row_index)
3969
4204
  return self
3970
4205
 
4206
+ # Tags
4207
+
4208
+ def tag(
4209
+ self,
4210
+ *key: CreateSpanTypes,
4211
+ tags: Iterator[str] | str = "",
4212
+ ) -> Sheet:
4213
+ span = self.span_from_key(*key)
4214
+ rows, cols = self.ranges_from_span(span)
4215
+ if isinstance(tags, str):
4216
+ tags = (tags,)
4217
+ if span.kind == "cell":
4218
+ for tag in tags:
4219
+ if tag not in self.MT.tagged_cells:
4220
+ self.MT.tagged_cells[tag] = set()
4221
+ for r in rows:
4222
+ for c in cols:
4223
+ self.MT.tagged_cells[tag].add((r, c))
4224
+ elif span.kind == "row":
4225
+ for tag in tags:
4226
+ if tag not in self.MT.tagged_rows:
4227
+ self.MT.tagged_rows[tag] = set()
4228
+ self.MT.tagged_rows[tag].update(set(rows))
4229
+ elif span.kind == "column":
4230
+ for tag in tags:
4231
+ if tag not in self.MT.tagged_columns:
4232
+ self.MT.tagged_columns[tag] = set()
4233
+ self.MT.tagged_columns[tag].update(set(cols))
4234
+ return self
4235
+
4236
+ def tag_cell(
4237
+ self,
4238
+ cell: tuple[int, int],
4239
+ *tags,
4240
+ ) -> Sheet:
4241
+ if (
4242
+ not isinstance(cell, tuple)
4243
+ or not len(cell) == 2
4244
+ or not isinstance(cell[0], int)
4245
+ or not isinstance(cell[1], int)
4246
+ ):
4247
+ raise ValueError("'cell' argument must be tuple[int, int].")
4248
+ for tag in unpack(tags):
4249
+ if tag not in self.MT.tagged_cells:
4250
+ self.MT.tagged_cells[tag] = set()
4251
+ self.MT.tagged_cells[tag].add(cell)
4252
+ return self
4253
+
4254
+ def tag_rows(
4255
+ self,
4256
+ rows: int | Iterator[int],
4257
+ *tags,
4258
+ ) -> Sheet:
4259
+ if isinstance(rows, int):
4260
+ rows = [rows]
4261
+ for tag in unpack(tags):
4262
+ if tag not in self.MT.tagged_rows:
4263
+ self.MT.tagged_rows[tag] = set()
4264
+ self.MT.tagged_rows[tag].update(rows)
4265
+ return self
4266
+
4267
+ def tag_columns(
4268
+ self,
4269
+ columns: int | Iterator[int],
4270
+ *tags,
4271
+ ) -> Sheet:
4272
+ if isinstance(columns, int):
4273
+ columns = [columns]
4274
+ for tag in unpack(tags):
4275
+ if tag not in self.MT.tagged_columns:
4276
+ self.MT.tagged_columns[tag] = set()
4277
+ self.MT.tagged_columns[tag].update(columns)
4278
+ return self
4279
+
4280
+ def untag(
4281
+ self,
4282
+ cell: tuple[int, int] | None = None,
4283
+ rows: int | Iterator[int] | None = None,
4284
+ columns: int | Iterator[int] | None = None,
4285
+ ) -> Sheet:
4286
+ if isinstance(cell, tuple):
4287
+ for tagged in self.MT.tagged_cells.values():
4288
+ tagged.discard(cell)
4289
+ if isinstance(rows, int):
4290
+ rows = (rows,)
4291
+ if is_iterable(rows):
4292
+ for tagged in self.MT.tagged_rows.values():
4293
+ for row in rows:
4294
+ tagged.discard(row)
4295
+ if isinstance(columns, int):
4296
+ columns = (columns,)
4297
+ if is_iterable(columns):
4298
+ for tagged in self.MT.tagged_columns.values():
4299
+ for column in columns:
4300
+ tagged.discard(column)
4301
+ return self
4302
+
4303
+ def tag_del(
4304
+ self,
4305
+ *tags,
4306
+ cells: bool = True,
4307
+ rows: bool = True,
4308
+ columns: bool = True,
4309
+ ) -> Sheet:
4310
+ for tag in unpack(tags):
4311
+ if cells and tag in self.MT.tagged_cells:
4312
+ del self.MT.tagged_cells[tag]
4313
+ if rows and tag in self.MT.tagged_rows:
4314
+ del self.MT.tagged_rows[tag]
4315
+ if columns and tag in self.MT.tagged_columns:
4316
+ del self.MT.tagged_columns[tag]
4317
+ return self
4318
+
4319
+ def tag_has(
4320
+ self,
4321
+ *tags,
4322
+ ) -> DotDict:
4323
+ res = DotDict(
4324
+ cells=set(),
4325
+ rows=set(),
4326
+ columns=set(),
4327
+ )
4328
+ for tag in unpack(tags):
4329
+ res.cells.update(self.MT.tagged_cells[tag] if tag in self.MT.tagged_cells else set())
4330
+ res.rows.update(self.MT.tagged_rows[tag] if tag in self.MT.tagged_rows else set())
4331
+ res.columns.update(self.MT.tagged_columns[tag] if tag in self.MT.tagged_columns else set())
4332
+ return res
4333
+
4334
+ # Treeview Mode
4335
+
4336
+ def tree_build(
4337
+ self,
4338
+ data: list[list[object]],
4339
+ iid_column: int,
4340
+ parent_column: int,
4341
+ ) -> Sheet:
4342
+ tally_of_ids = defaultdict(lambda: -1)
4343
+ ncols = max(map(len, data), default=0)
4344
+ for rn, row in enumerate(data):
4345
+ if ncols > (lnr := len(row)):
4346
+ row += self.MT.get_empty_row_seq(rn, end=ncols, start=lnr)
4347
+ iid, pid = row[iid_column].lower(), row[parent_column].lower()
4348
+ if not iid:
4349
+ continue
4350
+ tally_of_ids[iid] += 1
4351
+ if tally_of_ids[iid] > 0:
4352
+ x = 1
4353
+ while iid in tally_of_ids:
4354
+ new = f"{row[iid_column]}_DUPLICATED_{x}"
4355
+ iid = new.lower()
4356
+ x += 1
4357
+ tally_of_ids[iid] += 1
4358
+ row[iid_column] = new
4359
+ if iid not in self.RI.tree:
4360
+ self.RI.tree[iid] = Node(row[iid_column], iid, "")
4361
+ if iid == pid or self.RI.pid_causes_recursive_loop(iid, pid):
4362
+ row[parent_column] = ""
4363
+ pid = ""
4364
+ if pid:
4365
+ if pid not in self.RI.tree:
4366
+ self.RI.tree[pid] = Node(row[parent_column], pid)
4367
+ self.RI.tree[iid].parent = self.RI.tree[pid]
4368
+ self.RI.tree[pid].children.append(self.RI.tree[iid])
4369
+ else:
4370
+ self.RI.tree[iid].parent = ""
4371
+ self.RI.tree_rns[iid] = rn
4372
+ for n in self.RI.tree.values():
4373
+ if n.parent is None:
4374
+ n.parent = ""
4375
+ newrow = self.MT.get_empty_row_seq(len(data), ncols)
4376
+ newrow[iid_column] = n.text
4377
+ self.RI.tree_rns[n.iid] = len(data)
4378
+ data.append(newrow)
4379
+ self.insert_rows(
4380
+ idx=0,
4381
+ rows=[[self.RI.tree[iid]] + data[self.RI.tree_rns[iid]] for iid in self.get_children()],
4382
+ row_index=True,
4383
+ create_selections=False,
4384
+ fill=False,
4385
+ )
4386
+ self.RI.tree_rns = {n.iid: i for i, n in enumerate(self.MT._row_index)}
4387
+ self.hide_rows(
4388
+ set(self.RI.tree_rns[iid] for iid in self.get_children() if self.RI.tree[iid].parent),
4389
+ deselect_all=True,
4390
+ data_indexes=True,
4391
+ )
4392
+ return self
4393
+
4394
+ def insert(
4395
+ self,
4396
+ parent: str = "",
4397
+ index: None | int | Literal["end"] = None,
4398
+ iid: None | str = None,
4399
+ text: None | str = None,
4400
+ values: None | list = None,
4401
+ create_selections: bool = False,
4402
+ ) -> str:
4403
+ if iid is None:
4404
+ i = 0
4405
+ while (iid := f"{i}") in self.RI.tree:
4406
+ i += 1
4407
+ iid, pid = iid.lower(), parent.lower()
4408
+ if not iid:
4409
+ raise ValueError("iid cannot be empty string.")
4410
+ if iid in self.RI.tree:
4411
+ raise ValueError(f"iid '{iid}' already exists.")
4412
+ if iid == pid:
4413
+ raise ValueError(f"iid '{iid}' cannot be equal to parent '{pid}'.")
4414
+ if pid and pid not in self.RI.tree:
4415
+ raise ValueError(f"parent '{parent}' does not exist.")
4416
+ if text is None:
4417
+ text = iid
4418
+ parent_node = self.RI.tree[pid] if parent else ""
4419
+ self.RI.tree[iid] = Node(text, iid, parent_node)
4420
+ if self.RI.pid_causes_recursive_loop(iid, pid):
4421
+ del self.RI.tree[iid]
4422
+ raise ValueError(f"iid '{iid}' causes a recursive loop with parent '{parent}'.")
4423
+ if parent_node:
4424
+ if isinstance(index, int):
4425
+ idx = self.RI.tree_rns[pid] + index + 1
4426
+ for count, cid in enumerate(self.get_children(pid)):
4427
+ if count >= index:
4428
+ break
4429
+ idx += sum(1 for _ in self.RI.get_iid_descendants(cid))
4430
+ self.RI.tree[pid].children.insert(index, self.RI.tree[iid])
4431
+ else:
4432
+ idx = self.RI.tree_rns[pid] + sum(1 for _ in self.RI.get_iid_descendants(pid)) + 1
4433
+ self.RI.tree[pid].children.append(self.RI.tree[iid])
4434
+ else:
4435
+ if isinstance(index, int):
4436
+ idx = index
4437
+ if index:
4438
+ for count, cid in enumerate(self.get_children("")):
4439
+ if count >= index:
4440
+ break
4441
+ idx += sum(1 for _ in self.RI.get_iid_descendants(cid)) + 1
4442
+ else:
4443
+ idx = len(self.MT._row_index)
4444
+ self.insert_rows(
4445
+ idx=idx,
4446
+ rows=[[self.RI.tree[iid]] + [] if values is None else values],
4447
+ row_index=True,
4448
+ create_selections=create_selections,
4449
+ fill=False,
4450
+ )
4451
+ self.RI.tree_rns[iid] = idx
4452
+ if pid and (pid not in self.RI.tree_open_ids or not self.item_displayed(pid)):
4453
+ self.hide_rows(idx, deselect_all=False, data_indexes=True)
4454
+ return iid
4455
+
4456
+ def item(
4457
+ self,
4458
+ item: str,
4459
+ iid: str | None = None,
4460
+ text: str | None = None,
4461
+ values: list | None = None,
4462
+ open_: bool | None = None,
4463
+ ) -> DotDict:
4464
+ if not (item := item.lower()) or item not in self.RI.tree:
4465
+ raise ValueError(f"Item '{item}' does not exist.")
4466
+ if isinstance(iid, str):
4467
+ if not (iid := iid.lower()):
4468
+ raise ValueError(f"iid '{iid}' does not exist.")
4469
+ if iid in self.RI.tree:
4470
+ raise ValueError(f"Cannot rename '{iid}', it already exists.")
4471
+ iid = iid.lower()
4472
+ self.RI.tree[item].iid = iid
4473
+ self.RI.tree[iid] = self.RI.tree.pop(item)
4474
+ self.RI.tree_rns[iid] = self.RI.tree_rns.pop(item)
4475
+ if iid in self.RI.tree_open_ids:
4476
+ self.RI.tree_open_ids[iid] = self.RI.tree_open_ids.pop(item)
4477
+ if isinstance(text, str):
4478
+ self.RI.tree[item].text = text
4479
+ if isinstance(values, list):
4480
+ self.set_data(self.RI.tree_rns[item], values)
4481
+ if isinstance(open_, bool):
4482
+ if not self.RI.tree[item].children or not open_:
4483
+ self.RI.tree_open_ids.discard(item)
4484
+ if open_:
4485
+ self.RI.tree_open_ids.add(item)
4486
+ self.show_rows(
4487
+ (self.RI.tree_rns[did] for did in self.RI.get_iid_descendants(item, check_open=True)),
4488
+ redraw=False,
4489
+ deselect_all=False,
4490
+ )
4491
+ elif self.RI.tree[item].children:
4492
+ self.hide_rows(
4493
+ (self.RI.tree_rns[did] for did in self.RI.get_iid_descendants(item)),
4494
+ redraw=False,
4495
+ deselect_all=False,
4496
+ data_indexes=True,
4497
+ )
4498
+ self.set_refresh_timer(isinstance(text, str) or isinstance(values, list) or isinstance(open_, bool))
4499
+ return DotDict(
4500
+ text=self.RI.tree[item].text,
4501
+ values=self[self.RI.tree_rns[item]].options(ndim=1).data,
4502
+ open_=item in self.RI.tree_open_ids,
4503
+ )
4504
+
4505
+ def itemrow(self, item: str) -> int:
4506
+ return self.RI.tree_rns[item.lower()]
4507
+
4508
+ def rowitem(self, row: int) -> str | None:
4509
+ if len(self.MT._row_index) > row:
4510
+ return self.MT._row_index[row].iid
4511
+ return None
4512
+
4513
+ def get_children(self, item: None | str = None) -> Generator[str]:
4514
+ if item is None:
4515
+ for n in self.RI.tree.values():
4516
+ if not n.parent:
4517
+ yield n.iid
4518
+ for iid in self.RI.get_iid_descendants(n.iid):
4519
+ yield iid
4520
+ elif item == "":
4521
+ yield from (n.iid for n in self.RI.tree.values() if not n.parent)
4522
+ else:
4523
+ yield from (n.iid for n in self.RI.tree[item].children)
4524
+
4525
+ def reset_tree(self) -> Sheet:
4526
+ self.deselect()
4527
+ self.RI.reset_tree()
4528
+ return self
4529
+
4530
+ def del_items(self, *items) -> Sheet:
4531
+ """
4532
+ Also deletes all descendants of items
4533
+ """
4534
+ rows_to_del = []
4535
+ iids_to_del = []
4536
+ for item in unpack(items):
4537
+ item = item.lower()
4538
+ if item not in self.RI.tree:
4539
+ continue
4540
+ rows_to_del.append(self.RI.tree_rns[item])
4541
+ iids_to_del.append(item)
4542
+ for did in self.RI.get_iid_descendants(item):
4543
+ rows_to_del.append(self.RI.tree_rns[did])
4544
+ iids_to_del.append(did)
4545
+ for item in unpack(items):
4546
+ self.RI.remove_node_from_parents_children(self.RI.tree[item])
4547
+ self.del_rows(rows_to_del)
4548
+ for iid in iids_to_del:
4549
+ self.RI.tree_open_ids.discard(iid)
4550
+ if self.RI.tree[iid].parent and len(self.RI.tree[iid].parent.children) == 1:
4551
+ self.RI.tree_open_ids.discard(self.RI.tree[iid].parent.iid)
4552
+ del self.RI.tree[iid]
4553
+ self.set_refresh_timer(True)
4554
+ return self
4555
+
4556
+ def set_children(self, item: str, *newchildren) -> Sheet:
4557
+ """
4558
+ Moves everything in '*newchildren' under 'item'
4559
+ """
4560
+ if (item := item.lower()) and item not in self.RI.tree:
4561
+ raise ValueError(f"Item '{item}' does not exist.")
4562
+ mapping = {}
4563
+ to_show = []
4564
+ if item:
4565
+ new_parent = self.RI.tree[item]
4566
+ if new_parent.children:
4567
+ ctr = self.RI.tree_rns[new_parent.children[-1].iid] + 1
4568
+ else:
4569
+ ctr = self.RI.tree_rns[new_parent.iid] + 1
4570
+ for cid in unpack(newchildren):
4571
+ if self.RI.pid_causes_recursive_loop(cid, item):
4572
+ raise ValueError(f"iid '{cid}' causes a recursive loop with parent '{item}'.")
4573
+ cid_node = self.RI.tree[cid]
4574
+ mapping[self.RI.tree_rns[cid]] = ctr
4575
+ if item in self.RI.tree_open_ids and self.item_displayed(item):
4576
+ to_show.append(ctr)
4577
+ ctr += 1
4578
+ for did in self.RI.get_iid_descendants(cid):
4579
+ mapping[self.RI.tree_rns[did]] = ctr
4580
+ if to_show and self.RI.ancestors_all_open(did, self.RI.tree[cid].parent):
4581
+ to_show.append(ctr)
4582
+ ctr += 1
4583
+ self.RI.remove_node_from_parents_children(cid_node)
4584
+ cid_node.parent = new_parent
4585
+ new_parent.children.append(cid_node)
4586
+ else:
4587
+ ctr = (
4588
+ len(self.MT._row_index)
4589
+ - len(newchildren)
4590
+ - sum(1 for cid in unpack(newchildren) for _ in self.RI.get_iid_descendants(cid))
4591
+ )
4592
+ for cid in unpack(newchildren):
4593
+ cid_node = self.RI.tree[cid]
4594
+ mapping[self.RI.tree_rns[cid]] = ctr
4595
+ to_show.append(ctr)
4596
+ ctr += 1
4597
+ for did in self.RI.get_iid_descendants(cid):
4598
+ mapping[self.RI.tree_rns[did]] = ctr
4599
+ if self.RI.ancestors_all_open(did, cid_node.parent):
4600
+ to_show.append(ctr)
4601
+ ctr += 1
4602
+ self.RI.remove_node_from_parents_children(cid_node)
4603
+ self.RI.tree[cid].parent = ""
4604
+ self.mapping_move_rows(
4605
+ data_new_idxs=mapping,
4606
+ data_indexes=True,
4607
+ create_selections=False,
4608
+ redraw=False,
4609
+ )
4610
+ if item and (item not in self.RI.tree_open_ids or not self.item_displayed(item)):
4611
+ self.hide_rows(set(mapping.values()), data_indexes=True)
4612
+ self.show_rows(to_show)
4613
+ self.set_refresh_timer(True)
4614
+ return self
4615
+
4616
+ def move(self, item: str, parent: str, index: int = 0) -> Sheet:
4617
+ """
4618
+ Moves item to be under parent as child at index
4619
+ 'parent' can be empty string which will make item a top node
4620
+ """
4621
+ if (item := item.lower()) and item not in self.RI.tree:
4622
+ raise ValueError(f"Item '{item}' does not exist.")
4623
+ if (parent := parent.lower()) and parent not in self.RI.tree:
4624
+ raise ValueError(f"Parent '{parent}' does not exist.")
4625
+ mapping = {}
4626
+ to_show = []
4627
+ item_node = self.RI.tree[item]
4628
+ if parent:
4629
+ if self.RI.pid_causes_recursive_loop(item, parent):
4630
+ raise ValueError(f"iid '{item}' causes a recursive loop with parent '{parent}'.")
4631
+ parent_node = self.RI.tree[parent]
4632
+ if parent_node.children:
4633
+ if len(parent_node.children) < index:
4634
+ index = len(parent_node.children)
4635
+ ctr = self.RI.tree_rns[parent_node.children[index].iid]
4636
+ else:
4637
+ ctr = self.RI.tree_rns[parent_node.iid] + 1
4638
+ mapping[self.RI.tree_rns[item]] = ctr
4639
+ if parent in self.RI.tree_open_ids and self.item_displayed(parent):
4640
+ to_show.append(ctr)
4641
+ ctr += 1
4642
+ for did in self.RI.get_iid_descendants(item):
4643
+ mapping[self.RI.tree_rns[did]] = ctr
4644
+ if to_show and self.RI.ancestors_all_open(did, item_node.parent):
4645
+ to_show.append(ctr)
4646
+ ctr += 1
4647
+ self.RI.remove_node_from_parents_children(item_node)
4648
+ item_node.parent = parent_node
4649
+ parent_node.children.append(item_node)
4650
+ else:
4651
+ if len(self.MT._row_index) < index:
4652
+ index = len(self.MT._row_index)
4653
+ ctr = index
4654
+ mapping[self.RI.tree_rns[item]] = ctr
4655
+ to_show.append(ctr)
4656
+ ctr += 1
4657
+ for did in self.RI.get_iid_descendants(item):
4658
+ mapping[self.RI.tree_rns[did]] = ctr
4659
+ if to_show and self.RI.ancestors_all_open(did, item_node.parent):
4660
+ to_show.append(ctr)
4661
+ ctr += 1
4662
+ self.RI.remove_node_from_parents_children(item_node)
4663
+ self.RI.tree[item].parent = ""
4664
+ self.mapping_move_rows(
4665
+ data_new_idxs=mapping,
4666
+ data_indexes=True,
4667
+ create_selections=False,
4668
+ redraw=False,
4669
+ )
4670
+ if parent and (parent not in self.RI.tree_open_ids or not self.item_displayed(parent)):
4671
+ self.hide_rows(set(mapping.values()), data_indexes=True)
4672
+ self.show_rows(to_show)
4673
+ self.set_refresh_timer(True)
4674
+ return self
4675
+
4676
+ reattach = move
4677
+
4678
+ def exists(self, item: str) -> bool:
4679
+ return item in self.RI.tree
4680
+
4681
+ def parent(self, item: str) -> str:
4682
+ if (item := item.lower()) not in self.RI.tree:
4683
+ raise ValueError(f"Item '{item}' does not exist.")
4684
+ return self.RI.tree[item].parent.iid if self.RI.tree[item].parent else self.RI.tree[item].parent
4685
+
4686
+ def index(self, item: str) -> int:
4687
+ if (item := item.lower()) not in self.RI.tree:
4688
+ raise ValueError(f"Item '{item}' does not exist.")
4689
+ if not self.RI.tree[item].parent:
4690
+ return sorted(self.RI.tree_rns[iid] for iid in self.get_children("")).index(self.RI.tree_rns[item])
4691
+ return self.RI.tree[item].parent.children.index(self.RI.tree[item])
4692
+
4693
+ def item_displayed(self, item: str) -> bool:
4694
+ """
4695
+ Check if an item (row) is currently displayed on the sheet
4696
+ - Does not check if the item is visible to the user
4697
+ """
4698
+ if (item := item.lower()) not in self.RI.tree:
4699
+ raise ValueError(f"Item '{item}' does not exist.")
4700
+ return self.RI.tree_rns[item] in self.MT.displayed_rows
4701
+
4702
+ def display_item(self, item: str) -> Sheet:
4703
+ """
4704
+ Ensure that item is displayed in the tree
4705
+ - Opens all of an item's ancestors
4706
+ - Unlike the ttk treeview 'see' function
4707
+ this function does **NOT** scroll to the item
4708
+ """
4709
+ if (item := item.lower()) not in self.RI.tree:
4710
+ raise ValueError(f"Item '{item}' does not exist.")
4711
+ if self.RI.tree[item].parent:
4712
+ for iid in self.RI.get_iid_ancestors(item):
4713
+ self.item(iid, open_=True)
4714
+
4715
+ def scroll_to_item(self, item: str) -> Sheet:
4716
+ """
4717
+ Scrolls to an item and ensures that it is displayed
4718
+ """
4719
+ if (item := item.lower()) not in self.RI.tree:
4720
+ raise ValueError(f"Item '{item}' does not exist.")
4721
+ self.display_item(item)
4722
+ self.see(row=bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]), keep_xscroll=True)
4723
+
4724
+ def selection(self) -> list[str]:
4725
+ """
4726
+ Get currently selected item ids
4727
+ - Only includes selected rows
4728
+ """
4729
+ return [self.MT._row_index[self.displayed_row_to_data(rn)].iid for rn in self.get_selected_rows()]
4730
+
4731
+ def selection_set(self, *items) -> Sheet:
4732
+ self.deselect()
4733
+ self.selection_add(*items)
4734
+ return self
4735
+
4736
+ def selection_add(self, *items) -> Sheet:
4737
+ for item in unpack(items):
4738
+ if (item := item.lower()) not in self.RI.tree:
4739
+ raise ValueError(f"Item '{item}' does not exist.")
4740
+ if not self.item_displayed(item):
4741
+ self.display_item(item)
4742
+ self.add_row_selection(bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]))
4743
+ return self
4744
+
4745
+ def selection_remove(self, *items) -> Sheet:
4746
+ for item in unpack(items):
4747
+ if (item := item.lower()) not in self.RI.tree:
4748
+ raise ValueError(f"Item '{item}' does not exist.")
4749
+ try:
4750
+ self.deselect(bisect_left(self.MT.displayed_rows, self.RI.tree_rns[item]))
4751
+ except Exception:
4752
+ continue
4753
+ return self
4754
+
4755
+ def selection_toggle(self, *items) -> Sheet:
4756
+ selected = set(self.MT._row_index[self.displayed_row_to_data(rn)].iid for rn in self.get_selected_rows())
4757
+ add = []
4758
+ remove = []
4759
+ for item in unpack(items):
4760
+ if (item := item.lower()) not in self.RI.tree:
4761
+ continue
4762
+ if item in selected:
4763
+ remove.append(item)
4764
+ else:
4765
+ add.append(item)
4766
+ self.selection_remove(*remove)
4767
+ self.selection_add(*add)
4768
+ return self
4769
+
3971
4770
  # Functions not in docs
3972
4771
 
3973
4772
  def emit_event(
@@ -4005,10 +4804,10 @@ class Sheet(tk.Frame):
4005
4804
  table, index, header = span.table, span.index, span.header
4006
4805
  # index header
4007
4806
  if header and span.kind in ("cell", "column"):
4008
- self.CH.destroy_opened_dropdown_window()
4807
+ self.CH.hide_dropdown_window()
4009
4808
  del_from_options(self.CH.cell_options, key, cols)
4010
4809
  if index and span.kind in ("cell", "row"):
4011
- self.RI.destroy_opened_dropdown_window()
4810
+ self.RI.hide_dropdown_window()
4012
4811
  del_from_options(self.RI.cell_options, key, rows)
4013
4812
  # table
4014
4813
  if table and span.kind == "cell":
@@ -4023,7 +4822,7 @@ class Sheet(tk.Frame):
4023
4822
  # ########## TABLE ##########
4024
4823
 
4025
4824
  def del_cell_options_dropdown(self, datarn: int, datacn: int) -> None:
4026
- self.MT.destroy_opened_dropdown_window()
4825
+ self.MT.hide_dropdown_window()
4027
4826
  if (datarn, datacn) in self.MT.cell_options and "dropdown" in self.MT.cell_options[(datarn, datacn)]:
4028
4827
  del self.MT.cell_options[(datarn, datacn)]["dropdown"]
4029
4828
 
@@ -4036,7 +4835,7 @@ class Sheet(tk.Frame):
4036
4835
  self.del_cell_options_checkbox(datarn, datacn)
4037
4836
 
4038
4837
  def del_row_options_dropdown(self, datarn: int) -> None:
4039
- self.MT.destroy_opened_dropdown_window()
4838
+ self.MT.hide_dropdown_window()
4040
4839
  if datarn in self.MT.row_options and "dropdown" in self.MT.row_options[datarn]:
4041
4840
  del self.MT.row_options[datarn]["dropdown"]
4042
4841
 
@@ -4049,7 +4848,7 @@ class Sheet(tk.Frame):
4049
4848
  self.del_row_options_checkbox(datarn)
4050
4849
 
4051
4850
  def del_column_options_dropdown(self, datacn: int) -> None:
4052
- self.MT.destroy_opened_dropdown_window()
4851
+ self.MT.hide_dropdown_window()
4053
4852
  if datacn in self.MT.col_options and "dropdown" in self.MT.col_options[datacn]:
4054
4853
  del self.MT.col_options[datacn]["dropdown"]
4055
4854
 
@@ -4064,7 +4863,7 @@ class Sheet(tk.Frame):
4064
4863
  # ########## INDEX ##########
4065
4864
 
4066
4865
  def del_index_cell_options_dropdown(self, datarn: int) -> None:
4067
- self.RI.destroy_opened_dropdown_window(datarn=datarn)
4866
+ self.RI.hide_dropdown_window()
4068
4867
  if datarn in self.RI.cell_options and "dropdown" in self.RI.cell_options[datarn]:
4069
4868
  del self.RI.cell_options[datarn]["dropdown"]
4070
4869
 
@@ -4079,7 +4878,7 @@ class Sheet(tk.Frame):
4079
4878
  # ########## HEADER ##########
4080
4879
 
4081
4880
  def del_header_cell_options_dropdown(self, datacn: int) -> None:
4082
- self.CH.destroy_opened_dropdown_window(datacn=datacn)
4881
+ self.CH.hide_dropdown_window()
4083
4882
  if datacn in self.CH.cell_options and "dropdown" in self.CH.cell_options[datacn]:
4084
4883
  del self.CH.cell_options[datacn]["dropdown"]
4085
4884
 
@@ -4300,7 +5099,7 @@ class Sheet(tk.Frame):
4300
5099
  if add_rows:
4301
5100
  maxidx = len(self.MT.data) - 1
4302
5101
  total_cols = None
4303
- height = self.MT.default_row_height[1]
5102
+ height = self.MT.get_default_row_height()
4304
5103
  for rn, v in enumerate(values):
4305
5104
  if rn > maxidx:
4306
5105
  if total_cols is None:
@@ -4412,13 +5211,11 @@ class Sheet(tk.Frame):
4412
5211
 
4413
5212
  def dehighlight_rows(
4414
5213
  self,
4415
- rows: list | str = [],
5214
+ rows: list[int] | Literal["all"] = [],
4416
5215
  redraw: bool = True,
4417
5216
  ) -> None:
4418
5217
  if isinstance(rows, int):
4419
5218
  rows = [rows]
4420
- else:
4421
- rows = rows
4422
5219
  if not rows or rows == "all":
4423
5220
  for r in self.MT.row_options:
4424
5221
  if "highlight" in self.MT.row_options[r]:
@@ -4439,11 +5236,13 @@ class Sheet(tk.Frame):
4439
5236
  pass
4440
5237
  self.set_refresh_timer(redraw)
4441
5238
 
4442
- def dehighlight_columns(self, columns: list | str = [], redraw: bool = True) -> None:
5239
+ def dehighlight_columns(
5240
+ self,
5241
+ columns: list[int] | Literal["all"] = [],
5242
+ redraw: bool = True,
5243
+ ) -> None:
4443
5244
  if isinstance(columns, int):
4444
5245
  columns = [columns]
4445
- else:
4446
- columns = columns
4447
5246
  if not columns or columns == "all":
4448
5247
  for c in self.MT.col_options:
4449
5248
  if "highlight" in self.MT.col_options[c]:
@@ -4466,7 +5265,7 @@ class Sheet(tk.Frame):
4466
5265
 
4467
5266
  def highlight_rows(
4468
5267
  self,
4469
- rows: list = [],
5268
+ rows: Iterator[int] | int,
4470
5269
  bg: None | str = None,
4471
5270
  fg: None | str = None,
4472
5271
  highlight_index: bool = True,
@@ -4484,7 +5283,7 @@ class Sheet(tk.Frame):
4484
5283
 
4485
5284
  def highlight_columns(
4486
5285
  self,
4487
- columns: list = [],
5286
+ columns: Iterator[int] | int,
4488
5287
  bg: bool | None | str = False,
4489
5288
  fg: bool | None | str = False,
4490
5289
  highlight_header: bool = True,
@@ -4946,26 +5745,17 @@ class Sheet(tk.Frame):
4946
5745
  return self
4947
5746
 
4948
5747
  def get_checkboxes(self) -> dict:
4949
- d = {
5748
+ return {
4950
5749
  **{k: v["checkbox"] for k, v in self.MT.cell_options.items() if "checkbox" in v},
4951
5750
  **{k: v["checkbox"] for k, v in self.MT.row_options.items() if "checkbox" in v},
4952
5751
  **{k: v["checkbox"] for k, v in self.MT.col_options.items() if "checkbox" in v},
4953
5752
  }
4954
- if "checkbox" in self.MT.options:
4955
- return {**d, "checkbox": self.MT.options["checkbox"]}
4956
- return d
4957
5753
 
4958
5754
  def get_header_checkboxes(self) -> dict:
4959
- d = {k: v["checkbox"] for k, v in self.CH.cell_options.items() if "checkbox" in v}
4960
- if "checkbox" in self.CH.options:
4961
- return {**d, "checkbox": self.CH.options["checkbox"]}
4962
- return d
5755
+ return {k: v["checkbox"] for k, v in self.CH.cell_options.items() if "checkbox" in v}
4963
5756
 
4964
5757
  def get_index_checkboxes(self) -> dict:
4965
- d = {k: v["checkbox"] for k, v in self.RI.cell_options.items() if "checkbox" in v}
4966
- if "checkbox" in self.RI.options:
4967
- return {**d, "checkbox": self.RI.options["checkbox"]}
4968
- return d
5758
+ return {k: v["checkbox"] for k, v in self.RI.cell_options.items() if "checkbox" in v}
4969
5759
 
4970
5760
  def create_dropdown(
4971
5761
  self,
@@ -5212,9 +6002,8 @@ class Sheet(tk.Frame):
5212
6002
  set_value: object = None,
5213
6003
  ) -> Sheet:
5214
6004
  if set_existing_dropdown:
5215
- if self.MT.existing_dropdown_window is not None:
5216
- r_ = self.MT.existing_dropdown_window.r
5217
- c_ = self.MT.existing_dropdown_window.c
6005
+ if self.MT.dropdown.open:
6006
+ r_, c_ = self.MT.dropdown.get_coords()
5218
6007
  else:
5219
6008
  raise Exception("No dropdown box is currently open")
5220
6009
  else:
@@ -5222,16 +6011,12 @@ class Sheet(tk.Frame):
5222
6011
  c_ = c
5223
6012
  kwargs = self.MT.get_cell_kwargs(r, c, key="dropdown")
5224
6013
  kwargs["values"] = values
5225
- if kwargs["window"] != "no dropdown open":
5226
- kwargs["window"].values(values)
6014
+ if self.MT.dropdown.open:
6015
+ self.MT.dropdown.window.values(values)
5227
6016
  if set_value is not None:
5228
6017
  self.set_cell_data(r_, c_, set_value)
5229
- if (
5230
- kwargs["window"] != "no dropdown open"
5231
- and self.MT.text_editor_loc is not None
5232
- and self.MT.text_editor is not None
5233
- ):
5234
- self.MT.text_editor.set_text(set_value)
6018
+ if self.MT.dropdown.open:
6019
+ self.MT.text_editor.window.set_text(set_value)
5235
6020
  return self
5236
6021
 
5237
6022
  def set_header_dropdown_values(
@@ -5242,8 +6027,8 @@ class Sheet(tk.Frame):
5242
6027
  set_value: object = None,
5243
6028
  ) -> Sheet:
5244
6029
  if set_existing_dropdown:
5245
- if self.CH.existing_dropdown_window is not None:
5246
- c_ = self.CH.existing_dropdown_window.c
6030
+ if self.CH.dropdown.open:
6031
+ c_ = self.CH.dropdown.get_coords()
5247
6032
  else:
5248
6033
  raise Exception("No dropdown box is currently open")
5249
6034
  else:
@@ -5251,10 +6036,11 @@ class Sheet(tk.Frame):
5251
6036
  kwargs = self.CH.get_cell_kwargs(c_, key="dropdown")
5252
6037
  if kwargs:
5253
6038
  kwargs["values"] = values
5254
- if kwargs["window"] != "no dropdown open":
5255
- kwargs["window"].values(values)
6039
+ if self.CH.dropdown.open:
6040
+ self.CH.dropdown.window.values(values)
5256
6041
  if set_value is not None:
5257
6042
  self.MT.headers(newheaders=set_value, index=c_)
6043
+
5258
6044
  return self
5259
6045
 
5260
6046
  def set_index_dropdown_values(
@@ -5265,8 +6051,8 @@ class Sheet(tk.Frame):
5265
6051
  set_value: object = None,
5266
6052
  ) -> Sheet:
5267
6053
  if set_existing_dropdown:
5268
- if self.RI.existing_dropdown_window is not None:
5269
- r_ = self.RI.existing_dropdown_window.r
6054
+ if self.RI.current_dropdown_window is not None:
6055
+ r_ = self.RI.current_dropdown_window.r
5270
6056
  else:
5271
6057
  raise Exception("No dropdown box is currently open")
5272
6058
  else:
@@ -5274,10 +6060,11 @@ class Sheet(tk.Frame):
5274
6060
  kwargs = self.RI.get_cell_kwargs(r_, key="dropdown")
5275
6061
  if kwargs:
5276
6062
  kwargs["values"] = values
5277
- if kwargs["window"] != "no dropdown open":
5278
- kwargs["window"].values(values)
6063
+ if self.RI.current_dropdown_window is not None:
6064
+ self.RI.current_dropdown_window.values(values)
5279
6065
  if set_value is not None:
5280
6066
  self.MT.row_index(newindex=set_value, index=r_)
6067
+ # here
5281
6068
  return self
5282
6069
 
5283
6070
  def get_dropdown_values(self, r: int = 0, c: int = 0) -> None | list:
@@ -5491,7 +6278,7 @@ class Dropdown(Sheet):
5491
6278
  parent: tk.Misc,
5492
6279
  r: int,
5493
6280
  c: int,
5494
- colors: dict[str, str],
6281
+ ops: dict,
5495
6282
  outline_color: str,
5496
6283
  width: int | None = None,
5497
6284
  height: int | None = None,
@@ -5503,7 +6290,9 @@ class Dropdown(Sheet):
5503
6290
  arrowkey_RIGHT: Callable | None = None,
5504
6291
  arrowkey_LEFT: Callable | None = None,
5505
6292
  align: str = "w",
5506
- # False for using r, c "r" for r "c" for c
6293
+ # False for using r, c
6294
+ # "r" for r
6295
+ # "c" for c
5507
6296
  single_index: str | bool = False,
5508
6297
  ) -> None:
5509
6298
  Sheet.__init__(
@@ -5511,7 +6300,7 @@ class Dropdown(Sheet):
5511
6300
  parent=parent,
5512
6301
  outline_thickness=outline_thickness,
5513
6302
  outline_color=outline_color,
5514
- table_grid_fg=colors["fg"],
6303
+ table_grid_fg=ops.popup_menu_fg,
5515
6304
  show_horizontal_grid=True,
5516
6305
  show_vertical_grid=False,
5517
6306
  show_header=False,
@@ -5525,24 +6314,23 @@ class Dropdown(Sheet):
5525
6314
  horizontal_grid_to_end_of_window=True,
5526
6315
  set_cell_sizes_on_zoom=True,
5527
6316
  show_selected_cells_border=False,
5528
- table_selected_cells_border_fg=colors["fg"],
5529
- table_selected_cells_bg=colors["highlight_bg"],
5530
- table_selected_rows_border_fg=colors["fg"],
5531
- table_selected_rows_bg=colors["highlight_bg"],
5532
- table_selected_rows_fg=colors["highlight_fg"],
6317
+ table_selected_cells_border_fg=ops.popup_menu_fg,
6318
+ table_selected_cells_bg=ops.popup_menu_highlight_bg,
6319
+ table_selected_rows_border_fg=ops.popup_menu_fg,
6320
+ table_selected_rows_bg=ops.popup_menu_highlight_bg,
6321
+ table_selected_rows_fg=ops.popup_menu_highlight_fg,
5533
6322
  width=width,
5534
6323
  height=height,
5535
6324
  font=font if font else self.ops.table_font,
5536
- table_fg=colors["fg"],
5537
- table_bg=colors["bg"],
6325
+ table_fg=ops.popup_menu_fg,
6326
+ table_bg=ops.popup_menu_bg,
6327
+ **{k: ops[k] for k in scrollbar_options_keys},
5538
6328
  )
5539
6329
  self.parent = parent
5540
6330
  self.close_dropdown_window = close_dropdown_window
5541
6331
  self.search_function = search_function
5542
6332
  self.arrowkey_RIGHT = arrowkey_RIGHT
5543
6333
  self.arrowkey_LEFT = arrowkey_LEFT
5544
- self.h_ = height
5545
- self.w_ = width
5546
6334
  self.r = r
5547
6335
  self.c = c
5548
6336
  self.row = -1
@@ -5621,6 +6409,13 @@ class Dropdown(Sheet):
5621
6409
  else:
5622
6410
  self.close_dropdown_window(self.r, self.c, self.get_cell_data(row, 0))
5623
6411
 
6412
+ def get_coords(self) -> int | tuple[int, int]:
6413
+ if self.single_index == "r":
6414
+ return self.r
6415
+ elif self.single_index == "c":
6416
+ return self.c
6417
+ return self.r, self.c
6418
+
5624
6419
  def values(self, values: list = [], redraw: bool = True) -> None:
5625
6420
  self.set_sheet_data(
5626
6421
  [[v] for v in values],