tksheet 7.4.6__py3-none-any.whl → 7.4.8__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/__init__.py +2 -1
- tksheet/column_headers.py +2 -0
- tksheet/constants.py +195 -0
- tksheet/find_window.py +324 -30
- tksheet/functions.py +257 -305
- tksheet/main_table.py +481 -295
- tksheet/row_index.py +9 -9
- tksheet/sheet.py +153 -496
- tksheet/sheet_options.py +7 -1
- tksheet/tksheet_types.py +3 -0
- {tksheet-7.4.6.dist-info → tksheet-7.4.8.dist-info}/METADATA +1 -1
- tksheet-7.4.8.dist-info/RECORD +22 -0
- {tksheet-7.4.6.dist-info → tksheet-7.4.8.dist-info}/WHEEL +1 -1
- tksheet-7.4.6.dist-info/RECORD +0 -22
- {tksheet-7.4.6.dist-info → tksheet-7.4.8.dist-info}/LICENSE.txt +0 -0
- {tksheet-7.4.6.dist-info → tksheet-7.4.8.dist-info}/top_level.txt +0 -0
tksheet/main_table.py
CHANGED
@@ -10,6 +10,7 @@ from functools import partial
|
|
10
10
|
from itertools import accumulate, chain, cycle, filterfalse, islice, repeat
|
11
11
|
from math import ceil, floor
|
12
12
|
from operator import itemgetter
|
13
|
+
from re import IGNORECASE, escape, sub
|
13
14
|
from tkinter import TclError
|
14
15
|
from typing import Any, Literal
|
15
16
|
|
@@ -30,7 +31,7 @@ from .constants import (
|
|
30
31
|
text_editor_to_unbind,
|
31
32
|
val_modifying_options,
|
32
33
|
)
|
33
|
-
from .find_window import FindWindow
|
34
|
+
from .find_window import FindWindow, replacer
|
34
35
|
from .formatters import (
|
35
36
|
data_to_str,
|
36
37
|
format_data,
|
@@ -45,19 +46,21 @@ from .functions import (
|
|
45
46
|
bisect_in,
|
46
47
|
box_gen_coords,
|
47
48
|
box_is_single_cell,
|
49
|
+
cell_down_within_box,
|
48
50
|
cell_right_within_box,
|
49
51
|
color_tup,
|
50
52
|
consecutive_ranges,
|
51
53
|
data_to_displayed_idxs,
|
52
54
|
diff_gen,
|
53
|
-
down_cell_within_box,
|
54
55
|
event_dict,
|
55
56
|
event_has_char_key,
|
56
57
|
event_opens_dropdown_or_checkbox,
|
57
58
|
float_to_int,
|
58
59
|
gen_coords,
|
59
60
|
gen_formatted,
|
61
|
+
get_bg_fg,
|
60
62
|
get_data_from_clipboard,
|
63
|
+
get_menu_kwargs,
|
61
64
|
get_new_indexes,
|
62
65
|
get_seq_without_gaps_at_index,
|
63
66
|
index_exists,
|
@@ -86,7 +89,6 @@ from .other_classes import (
|
|
86
89
|
Box_nt,
|
87
90
|
Box_st,
|
88
91
|
Box_t,
|
89
|
-
DotDict,
|
90
92
|
DropdownStorage,
|
91
93
|
EditorStorageBase,
|
92
94
|
EventDataDict,
|
@@ -136,6 +138,7 @@ class MainTable(tk.Canvas):
|
|
136
138
|
self.dropdown = DropdownStorage()
|
137
139
|
self.text_editor = TextEditorStorage()
|
138
140
|
self.find_window = EditorStorageBase()
|
141
|
+
self.find_window_left_x_pc = 1
|
139
142
|
self.event_linker = {
|
140
143
|
"<<Copy>>": self.ctrl_c,
|
141
144
|
"<<Cut>>": self.ctrl_x,
|
@@ -145,6 +148,7 @@ class MainTable(tk.Canvas):
|
|
145
148
|
"<<Redo>>": self.redo,
|
146
149
|
"<<SelectAll>>": self.select_all,
|
147
150
|
}
|
151
|
+
self.enabled_bindings = set()
|
148
152
|
|
149
153
|
self.disp_ctrl_outline = {}
|
150
154
|
self.disp_text = {}
|
@@ -190,7 +194,10 @@ class MainTable(tk.Canvas):
|
|
190
194
|
self.extra_double_b1_func = None
|
191
195
|
self.extra_rc_func = None
|
192
196
|
|
197
|
+
self.extra_end_replace_all_func = None
|
198
|
+
|
193
199
|
self.edit_validation_func = None
|
200
|
+
self.bulk_table_edit_validation_func = None
|
194
201
|
|
195
202
|
self.extra_begin_sort_cells_func = None
|
196
203
|
self.extra_end_sort_cells_func = None
|
@@ -477,10 +484,45 @@ class MainTable(tk.Canvas):
|
|
477
484
|
else:
|
478
485
|
self.deselect()
|
479
486
|
|
480
|
-
def get_find_window_dimensions_coords(self, w_width: int) -> tuple[int, int, int, int]:
|
487
|
+
def get_find_window_dimensions_coords(self, w_width: int | None) -> tuple[int, int, int, int]:
|
488
|
+
if w_width is None:
|
489
|
+
w_width = self.winfo_width()
|
481
490
|
width = min(self.get_txt_w("X" * 23), w_width - 7)
|
482
|
-
|
483
|
-
|
491
|
+
height = self.min_row_height
|
492
|
+
if self.find_window.window and self.find_window.window.replace_visible:
|
493
|
+
height *= 2
|
494
|
+
# Position from left based on percentage
|
495
|
+
xpos = w_width * self.find_window_left_x_pc
|
496
|
+
# Clamp to stay within canvas bounds
|
497
|
+
xpos = min(xpos, w_width - width - 7) # Don’t exceed right edge
|
498
|
+
xpos = max(0, xpos) # Don’t go left of 0
|
499
|
+
return width, height, self.canvasx(xpos), self.canvasy(7)
|
500
|
+
|
501
|
+
def reposition_find_window(self, w_width: int | None = None) -> None:
|
502
|
+
if w_width is None:
|
503
|
+
w_width = self.winfo_width()
|
504
|
+
w, h, x, y = self.get_find_window_dimensions_coords(w_width=w_width)
|
505
|
+
self.coords(self.find_window.canvas_id, x, y)
|
506
|
+
self.itemconfig(
|
507
|
+
self.find_window.canvas_id,
|
508
|
+
width=w,
|
509
|
+
height=h,
|
510
|
+
state="normal",
|
511
|
+
)
|
512
|
+
|
513
|
+
def drag_find_window(self, event: tk.Event) -> None:
|
514
|
+
"""Receives a tkinter b1-motion event, is bound to a label on the find window"""
|
515
|
+
# Convert screen coordinates to canvas window coordinates
|
516
|
+
window_x = event.x_root - self.winfo_rootx()
|
517
|
+
# Get the visible canvas width
|
518
|
+
visible_width = self.winfo_width()
|
519
|
+
if visible_width > 0:
|
520
|
+
# Calculate the new percentage using widget-relative coordinates
|
521
|
+
new_pc = window_x / visible_width
|
522
|
+
# Clamp the percentage between 0 and 1
|
523
|
+
self.find_window_left_x_pc = min(max(new_pc, 0), 1)
|
524
|
+
# Reposition the find window based on the updated percentage
|
525
|
+
self.reposition_find_window()
|
484
526
|
|
485
527
|
def open_find_window(
|
486
528
|
self,
|
@@ -488,7 +530,7 @@ class MainTable(tk.Canvas):
|
|
488
530
|
focus: bool = True,
|
489
531
|
) -> Literal["break"]:
|
490
532
|
if self.find_window.open:
|
491
|
-
self.
|
533
|
+
self.find_window.window.tktext.focus_set()
|
492
534
|
return "break"
|
493
535
|
width, height, x, y = self.get_find_window_dimensions_coords(w_width=self.winfo_width())
|
494
536
|
if not self.find_window.window:
|
@@ -497,14 +539,12 @@ class MainTable(tk.Canvas):
|
|
497
539
|
find_prev_func=self.find_previous,
|
498
540
|
find_next_func=self.find_next,
|
499
541
|
close_func=self.close_find_window,
|
542
|
+
replace_func=self.replace_next,
|
543
|
+
replace_all_func=self.replace_all,
|
544
|
+
toggle_replace_func=self.reposition_find_window,
|
545
|
+
drag_func=self.drag_find_window,
|
500
546
|
)
|
501
547
|
self.find_window.canvas_id = self.create_window((x, y), window=self.find_window.window, anchor="nw")
|
502
|
-
for b in chain(self.PAR.ops.escape_bindings, self.PAR.ops.find_bindings):
|
503
|
-
self.find_window.tktext.bind(b, self.close_find_window)
|
504
|
-
for b in chain(self.PAR.ops.find_next_bindings, ("<Return>", "<KP_Enter>")):
|
505
|
-
self.find_window.tktext.bind(b, self.find_next)
|
506
|
-
for b in self.PAR.ops.find_previous_bindings:
|
507
|
-
self.find_window.tktext.bind(b, self.find_previous)
|
508
548
|
else:
|
509
549
|
self.coords(self.find_window.canvas_id, x, y)
|
510
550
|
if not self.find_window.open:
|
@@ -512,21 +552,12 @@ class MainTable(tk.Canvas):
|
|
512
552
|
self.find_window.open = True
|
513
553
|
self.find_window.window.reset(
|
514
554
|
**{
|
515
|
-
"menu_kwargs":
|
516
|
-
{
|
517
|
-
"font": self.PAR.ops.table_font,
|
518
|
-
"foreground": self.PAR.ops.popup_menu_fg,
|
519
|
-
"background": self.PAR.ops.popup_menu_bg,
|
520
|
-
"activebackground": self.PAR.ops.popup_menu_highlight_bg,
|
521
|
-
"activeforeground": self.PAR.ops.popup_menu_highlight_fg,
|
522
|
-
}
|
523
|
-
),
|
555
|
+
"menu_kwargs": get_menu_kwargs(self.PAR.ops),
|
524
556
|
"sheet_ops": self.PAR.ops,
|
525
557
|
"border_color": self.PAR.ops.table_selected_box_cells_fg,
|
526
|
-
"
|
527
|
-
|
528
|
-
"
|
529
|
-
"select_fg": self.PAR.ops.table_editor_select_fg,
|
558
|
+
"grid_color": self.PAR.ops.table_grid_fg,
|
559
|
+
**get_bg_fg(self.PAR.ops),
|
560
|
+
"replace_enabled": "replace" in self.enabled_bindings or "all" in self.enabled_bindings,
|
530
561
|
}
|
531
562
|
)
|
532
563
|
self.itemconfig(self.find_window.canvas_id, width=width, height=height)
|
@@ -534,96 +565,223 @@ class MainTable(tk.Canvas):
|
|
534
565
|
self.find_window.tktext.focus_set()
|
535
566
|
return "break"
|
536
567
|
|
568
|
+
def replace_next(self, event: tk.Misc | None = None) -> None:
|
569
|
+
find = self.find_window.get().lower()
|
570
|
+
replace = self.find_window.window.get_replace()
|
571
|
+
sel = self.selected
|
572
|
+
if sel:
|
573
|
+
datarn, datacn = self.datarn(sel.row), self.datacn(sel.column)
|
574
|
+
m = self.find_match(find, datarn, datacn)
|
575
|
+
if m:
|
576
|
+
current = f"{self.get_cell_data(datarn, datacn, True)}"
|
577
|
+
new = sub(escape(find), replacer(find, replace, current), current, flags=IGNORECASE)
|
578
|
+
event_data = event_dict(
|
579
|
+
name="end_edit_table",
|
580
|
+
sheet=self.PAR.name,
|
581
|
+
widget=self,
|
582
|
+
cells_table={(datarn, datacn): self.get_cell_data(datarn, datacn)},
|
583
|
+
key="replace_next",
|
584
|
+
value=new,
|
585
|
+
loc=Loc(sel.row, sel.column),
|
586
|
+
row=sel.row,
|
587
|
+
column=sel.column,
|
588
|
+
boxes=self.get_boxes(),
|
589
|
+
selected=self.selected,
|
590
|
+
data={(datarn, datacn): new},
|
591
|
+
)
|
592
|
+
value, event_data = self.single_edit_run_validation(datarn, datacn, event_data)
|
593
|
+
if value is not None and (
|
594
|
+
self.set_cell_data_undo(
|
595
|
+
r=datarn,
|
596
|
+
c=datacn,
|
597
|
+
datarn=datarn,
|
598
|
+
datacn=datacn,
|
599
|
+
value=value,
|
600
|
+
redraw=False,
|
601
|
+
)
|
602
|
+
):
|
603
|
+
try_binding(self.extra_end_edit_cell_func, event_data)
|
604
|
+
if self.find_window.window.find_in_selection:
|
605
|
+
found_next = self.find_see_and_set(self.find_within(find))
|
606
|
+
else:
|
607
|
+
found_next = self.find_see_and_set(self.find_all_cells(find))
|
608
|
+
if not found_next and not self.find_window.window.find_in_selection:
|
609
|
+
self.deselect()
|
610
|
+
|
611
|
+
def replace_all(self, event: tk.Misc | None = None) -> None:
|
612
|
+
find = self.find_window.get().lower()
|
613
|
+
replace = self.find_window.window.get_replace()
|
614
|
+
tree = self.PAR.ops.treeview
|
615
|
+
event_data = self.new_event_dict("edit_table")
|
616
|
+
boxes = self.get_boxes()
|
617
|
+
event_data["selection_boxes"] = boxes
|
618
|
+
if self.find_window.window.find_in_selection:
|
619
|
+
iterable = chain.from_iterable(
|
620
|
+
(
|
621
|
+
box_gen_coords(
|
622
|
+
*box.coords,
|
623
|
+
start_r=box.coords.from_r,
|
624
|
+
start_c=box.coords.from_c,
|
625
|
+
reverse=False,
|
626
|
+
all_rows_displayed=self.all_rows_displayed,
|
627
|
+
all_cols_displayed=self.all_columns_displayed,
|
628
|
+
displayed_rows=self.displayed_rows,
|
629
|
+
displayed_cols=self.displayed_columns,
|
630
|
+
)
|
631
|
+
for box in self.selection_boxes.values()
|
632
|
+
)
|
633
|
+
)
|
634
|
+
else:
|
635
|
+
iterable = box_gen_coords(
|
636
|
+
from_r=0,
|
637
|
+
from_c=0,
|
638
|
+
upto_r=self.total_data_rows(include_index=False),
|
639
|
+
upto_c=self.total_data_cols(include_header=False),
|
640
|
+
start_r=0,
|
641
|
+
start_c=0,
|
642
|
+
reverse=False,
|
643
|
+
)
|
644
|
+
for r, c in iterable:
|
645
|
+
m = self.find_match(find, r, c)
|
646
|
+
if m and (
|
647
|
+
(tree or self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
648
|
+
and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
|
649
|
+
):
|
650
|
+
current = f"{self.get_cell_data(r, c, True)}"
|
651
|
+
new = sub(escape(find), replacer(find, replace, current), current, flags=IGNORECASE)
|
652
|
+
if not self.edit_validation_func or (
|
653
|
+
self.edit_validation_func
|
654
|
+
and (new := self.edit_validation_func(mod_event_val(event_data, new, (r, c)))) is not None
|
655
|
+
):
|
656
|
+
event_data = self.event_data_set_cell(
|
657
|
+
r,
|
658
|
+
c,
|
659
|
+
new,
|
660
|
+
event_data,
|
661
|
+
)
|
662
|
+
event_data = self.bulk_edit_validation(event_data)
|
663
|
+
if event_data["cells"]["table"]:
|
664
|
+
self.refresh()
|
665
|
+
if self.undo_enabled:
|
666
|
+
self.undo_stack.append(stored_event_dict(event_data))
|
667
|
+
try_binding(self.extra_end_replace_all_func, event_data, "end_edit_table")
|
668
|
+
self.sheet_modified(event_data)
|
669
|
+
self.PAR.emit_event("<<SheetModified>>", event_data)
|
670
|
+
|
537
671
|
def find_see_and_set(
|
538
|
-
self,
|
539
|
-
coords: tuple[int, int, int | None] | None,
|
540
|
-
just_see: bool = False,
|
672
|
+
self, coords: tuple[int, int, int | None] | None, within: bool | None = None
|
541
673
|
) -> tuple[int, int]:
|
542
674
|
if coords:
|
543
675
|
row, column, item = coords
|
544
|
-
if
|
545
|
-
|
546
|
-
if not self.
|
547
|
-
|
548
|
-
if
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
if not self.see(row, column):
|
676
|
+
if self.PAR.ops.treeview:
|
677
|
+
self.PAR.scroll_to_item(self.PAR.rowitem(row, data_index=True))
|
678
|
+
disp_row = self.disprn(row) if not self.all_rows_displayed else row
|
679
|
+
disp_col = self.dispcn(column) if not self.all_columns_displayed else column
|
680
|
+
if within or (self.find_window.window and self.find_window.window.find_in_selection):
|
681
|
+
self.set_currently_selected(disp_row, disp_col, item=item)
|
682
|
+
else:
|
683
|
+
self.select_cell(disp_row, disp_col, redraw=False)
|
684
|
+
if not self.see(disp_row, disp_col):
|
554
685
|
self.refresh()
|
555
686
|
return coords
|
556
687
|
|
557
688
|
def find_match(self, find: str, r: int, c: int) -> bool:
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
if not self.all_columns_displayed:
|
573
|
-
c = self.datacn(c)
|
574
|
-
return self.find_match(find, r, c)
|
689
|
+
try:
|
690
|
+
value = self.data[r][c]
|
691
|
+
except Exception:
|
692
|
+
value = ""
|
693
|
+
kwargs = self.get_cell_kwargs(r, c, key="format")
|
694
|
+
if kwargs:
|
695
|
+
# assumed given formatter class has __str__() or value attribute
|
696
|
+
value = data_to_str(value, **kwargs) if kwargs["formatter"] is None else str(value)
|
697
|
+
if value is None:
|
698
|
+
return find == ""
|
699
|
+
elif not find:
|
700
|
+
return str(value) == ""
|
701
|
+
else:
|
702
|
+
return find in str(value).lower()
|
575
703
|
|
576
704
|
def find_within_current_box(
|
577
|
-
self,
|
578
|
-
|
579
|
-
|
580
|
-
reverse: bool,
|
581
|
-
) -> None | tuple[int, int]:
|
582
|
-
start_row, start_col = next_cell(
|
705
|
+
self, current_box: SelectionBox, find: str, reverse: bool
|
706
|
+
) -> None | tuple[int, int, int]:
|
707
|
+
start_r, start_c = next_cell(
|
583
708
|
*current_box.coords,
|
584
709
|
self.selected.row,
|
585
710
|
self.selected.column,
|
586
711
|
reverse=reverse,
|
587
712
|
)
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
713
|
+
return next(
|
714
|
+
(
|
715
|
+
(r, c, current_box.fill_iid)
|
716
|
+
for r, c in box_gen_coords(
|
717
|
+
*current_box.coords,
|
718
|
+
start_r,
|
719
|
+
start_c,
|
720
|
+
reverse=reverse,
|
721
|
+
all_rows_displayed=self.all_rows_displayed,
|
722
|
+
all_cols_displayed=self.all_columns_displayed,
|
723
|
+
displayed_rows=self.displayed_rows,
|
724
|
+
displayed_cols=self.displayed_columns,
|
725
|
+
no_wrap=True,
|
726
|
+
)
|
727
|
+
if (
|
728
|
+
self.find_match(find, r, c) # will not show hidden rows
|
729
|
+
and (self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
730
|
+
and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
|
731
|
+
)
|
732
|
+
),
|
733
|
+
None,
|
734
|
+
)
|
593
735
|
|
594
|
-
def find_within_non_current_boxes(
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
736
|
+
def find_within_non_current_boxes(self, current_id: int, find: str, reverse: bool) -> None | tuple[int, int, int]:
|
737
|
+
fn = partial(
|
738
|
+
box_gen_coords,
|
739
|
+
reverse=reverse,
|
740
|
+
all_rows_displayed=self.all_rows_displayed,
|
741
|
+
all_cols_displayed=self.all_columns_displayed,
|
742
|
+
displayed_rows=self.displayed_rows,
|
743
|
+
displayed_cols=self.displayed_columns,
|
744
|
+
)
|
600
745
|
if reverse:
|
601
746
|
# iterate backwards through selection boxes from the box before current
|
602
747
|
idx = next(i for i, k in enumerate(reversed(self.selection_boxes)) if k == current_id)
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
748
|
+
return next(
|
749
|
+
(
|
750
|
+
(r, c, item)
|
751
|
+
for item, box in chain(
|
752
|
+
islice(reversed(self.selection_boxes.items()), idx + 1, None),
|
753
|
+
islice(reversed(self.selection_boxes.items()), 0, idx),
|
754
|
+
)
|
755
|
+
for r, c in fn(*box.coords, box.coords.upto_r - 1, box.coords.upto_c - 1)
|
756
|
+
if (
|
757
|
+
self.find_match(find, r, c) # will not show hidden rows
|
758
|
+
and (self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
759
|
+
and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
|
760
|
+
)
|
761
|
+
),
|
762
|
+
None,
|
763
|
+
)
|
610
764
|
else:
|
611
765
|
# iterate forwards through selection boxes from the box after current
|
612
766
|
idx = next(i for i, k in enumerate(self.selection_boxes) if k == current_id)
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
767
|
+
return next(
|
768
|
+
(
|
769
|
+
(r, c, item)
|
770
|
+
for item, box in chain(
|
771
|
+
islice(self.selection_boxes.items(), idx + 1, None),
|
772
|
+
islice(self.selection_boxes.items(), 0, idx),
|
773
|
+
)
|
774
|
+
for r, c in fn(*box.coords, box.coords.from_r, box.coords.from_c)
|
775
|
+
if (
|
776
|
+
self.find_match(find, r, c)
|
777
|
+
and (self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
778
|
+
and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
|
779
|
+
)
|
780
|
+
),
|
781
|
+
None,
|
782
|
+
)
|
621
783
|
|
622
|
-
def find_within(
|
623
|
-
self,
|
624
|
-
find: str,
|
625
|
-
reverse: bool = False,
|
626
|
-
) -> tuple[int, int, int] | None:
|
784
|
+
def find_within(self, find: str, reverse: bool = False) -> tuple[int, int, int] | None:
|
627
785
|
if not self.selected:
|
628
786
|
return None
|
629
787
|
current_box = self.selection_boxes[self.selected.fill_iid]
|
@@ -640,70 +798,73 @@ class MainTable(tk.Canvas):
|
|
640
798
|
return coord
|
641
799
|
return None
|
642
800
|
|
643
|
-
def find_all_cells(
|
644
|
-
self
|
645
|
-
|
646
|
-
|
647
|
-
) -> tuple[int, int, None] | None:
|
801
|
+
def find_all_cells(self, find: str, reverse: bool = False) -> tuple[int, int, None] | None:
|
802
|
+
tree = self.PAR.ops.treeview
|
803
|
+
totalrows = self.total_data_rows(include_index=False)
|
804
|
+
totalcols = self.total_data_cols(include_header=False)
|
648
805
|
if self.selected:
|
649
|
-
|
806
|
+
start_r, start_c = next_cell(
|
650
807
|
0,
|
651
808
|
0,
|
652
|
-
|
653
|
-
|
654
|
-
self.selected.row,
|
655
|
-
self.selected.column,
|
809
|
+
totalrows,
|
810
|
+
totalcols,
|
811
|
+
self.datarn(self.selected.row),
|
812
|
+
self.datacn(self.selected.column),
|
656
813
|
reverse=reverse,
|
657
814
|
)
|
658
815
|
else:
|
659
|
-
|
660
|
-
|
661
|
-
result = next(
|
816
|
+
start_r, start_c = 0, 0
|
817
|
+
return next(
|
662
818
|
(
|
663
|
-
(r, c)
|
819
|
+
(r, c, None)
|
664
820
|
for r, c in box_gen_coords(
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
821
|
+
from_r=0,
|
822
|
+
from_c=0,
|
823
|
+
upto_r=totalrows,
|
824
|
+
upto_c=totalcols,
|
825
|
+
start_r=start_r,
|
826
|
+
start_c=start_c,
|
669
827
|
reverse=reverse,
|
670
828
|
)
|
671
829
|
if (
|
672
|
-
|
830
|
+
self.find_match(find, r, c)
|
831
|
+
and (tree or self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
673
832
|
and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
|
674
|
-
and self.find_match(find, r, c)
|
675
833
|
)
|
676
834
|
),
|
677
835
|
None,
|
678
836
|
)
|
679
|
-
if result:
|
680
|
-
return result + (None,)
|
681
|
-
return None
|
682
837
|
|
683
|
-
def
|
684
|
-
find = self.find_window.get().lower()
|
838
|
+
def replace_toggle(self, event: tk.Event | None) -> None:
|
685
839
|
if not self.find_window.open:
|
686
840
|
self.open_find_window(focus=False)
|
687
|
-
if self.find_window.window.
|
688
|
-
self.
|
689
|
-
|
690
|
-
self.find_see_and_set(self.find_all_cells(find))
|
691
|
-
return "break"
|
841
|
+
if not self.find_window.window.replace_visible:
|
842
|
+
self.find_window.window.toggle_replace_window()
|
843
|
+
self.find_window.window.replace_tktext.focus_set()
|
692
844
|
|
693
|
-
def
|
694
|
-
|
695
|
-
|
845
|
+
def find_next(
|
846
|
+
self,
|
847
|
+
event: tk.Misc | None = None,
|
848
|
+
within: bool | None = None,
|
849
|
+
find: str | None = None,
|
850
|
+
reverse: bool = False,
|
851
|
+
) -> Literal["break"]:
|
852
|
+
if find is None:
|
853
|
+
find = self.find_window.get().lower()
|
854
|
+
if find is None and not self.find_window.open:
|
696
855
|
self.open_find_window(focus=False)
|
697
|
-
if self.find_window.window.find_in_selection:
|
698
|
-
self.find_see_and_set(self.find_within(find, reverse=
|
856
|
+
if within or (self.find_window.window and self.find_window.window.find_in_selection):
|
857
|
+
self.find_see_and_set(self.find_within(find, reverse=reverse), within=within)
|
699
858
|
else:
|
700
|
-
self.find_see_and_set(self.find_all_cells(find, reverse=
|
859
|
+
self.find_see_and_set(self.find_all_cells(find, reverse=reverse), within=within)
|
701
860
|
return "break"
|
702
861
|
|
703
|
-
def
|
704
|
-
self,
|
705
|
-
|
706
|
-
|
862
|
+
def find_previous(
|
863
|
+
self, event: tk.Misc | None = None, within: bool | None = None, find: str | None = None
|
864
|
+
) -> Literal["break"]:
|
865
|
+
return self.find_next(find=find, within=within, reverse=True)
|
866
|
+
|
867
|
+
def close_find_window(self, event: tk.Misc | None = None) -> None:
|
707
868
|
if self.find_window.open:
|
708
869
|
self.itemconfig(self.find_window.canvas_id, state="hidden")
|
709
870
|
self.find_window.open = False
|
@@ -910,11 +1071,13 @@ class MainTable(tk.Canvas):
|
|
910
1071
|
else:
|
911
1072
|
self.clipboard_append(s.getvalue())
|
912
1073
|
self.update_idletasks()
|
913
|
-
self.
|
914
|
-
for r1, c1, r2, c2 in boxes:
|
915
|
-
self.show_ctrl_outline(canvas="table", start_cell=(c1, r1), end_cell=(c2, r2))
|
1074
|
+
event_data = self.bulk_edit_validation(event_data)
|
916
1075
|
if event_data["cells"]["table"]:
|
917
|
-
self.
|
1076
|
+
self.refresh()
|
1077
|
+
for r1, c1, r2, c2 in boxes:
|
1078
|
+
self.show_ctrl_outline(canvas="table", start_cell=(c1, r1), end_cell=(c2, r2))
|
1079
|
+
if self.undo_enabled:
|
1080
|
+
self.undo_stack.append(stored_event_dict(event_data))
|
918
1081
|
try_binding(self.extra_end_ctrl_x_func, event_data, "end_ctrl_x")
|
919
1082
|
self.sheet_modified(event_data)
|
920
1083
|
self.PAR.emit_event("<<Cut>>", event_data)
|
@@ -965,6 +1128,7 @@ class MainTable(tk.Canvas):
|
|
965
1128
|
value=val,
|
966
1129
|
event_data=event_data,
|
967
1130
|
)
|
1131
|
+
event_data = self.bulk_edit_validation(event_data)
|
968
1132
|
if event_data["cells"]["table"]:
|
969
1133
|
if undo and self.undo_enabled:
|
970
1134
|
self.undo_stack.append(stored_event_dict(event_data))
|
@@ -1030,7 +1194,6 @@ class MainTable(tk.Canvas):
|
|
1030
1194
|
for _ in range(int(lastbox_numcols / new_data_numcols)):
|
1031
1195
|
data[rn].extend(r.copy())
|
1032
1196
|
new_data_numcols *= int(lastbox_numcols / new_data_numcols)
|
1033
|
-
event_data["data"] = data
|
1034
1197
|
added_rows = 0
|
1035
1198
|
added_cols = 0
|
1036
1199
|
total_data_cols = None
|
@@ -1069,6 +1232,11 @@ class MainTable(tk.Canvas):
|
|
1069
1232
|
): "cells"
|
1070
1233
|
}
|
1071
1234
|
event_data["selection_boxes"] = boxes
|
1235
|
+
for ndr, r in enumerate(range(selected_r, selected_r_adjusted_new_data_numrows)):
|
1236
|
+
datarn = self.datarn(r)
|
1237
|
+
for ndc, c in enumerate(range(selected_c, selected_c_adjusted_new_data_numcols)):
|
1238
|
+
event_data["data"][(datarn, self.datacn(c))] = data[ndr][ndc]
|
1239
|
+
|
1072
1240
|
if not try_binding(self.extra_begin_ctrl_v_func, event_data, "begin_ctrl_v"):
|
1073
1241
|
return
|
1074
1242
|
# the order of actions here is important:
|
@@ -1210,8 +1378,10 @@ class MainTable(tk.Canvas):
|
|
1210
1378
|
event_data["selected"] = self.selected
|
1211
1379
|
self.see(selected_r, selected_c, redraw=False)
|
1212
1380
|
self.refresh()
|
1381
|
+
event_data = self.bulk_edit_validation(event_data)
|
1213
1382
|
if event_data["cells"]["table"] or event_data["added"]["rows"] or event_data["added"]["columns"]:
|
1214
|
-
self.
|
1383
|
+
if self.undo_enabled:
|
1384
|
+
self.undo_stack.append(stored_event_dict(event_data))
|
1215
1385
|
try_binding(self.extra_end_ctrl_v_func, event_data, "end_ctrl_v")
|
1216
1386
|
self.sheet_modified(event_data)
|
1217
1387
|
self.PAR.emit_event("<<Paste>>", event_data)
|
@@ -1243,18 +1413,33 @@ class MainTable(tk.Canvas):
|
|
1243
1413
|
val,
|
1244
1414
|
event_data,
|
1245
1415
|
)
|
1416
|
+
event_data = self.bulk_edit_validation(event_data)
|
1246
1417
|
if event_data["cells"]["table"]:
|
1247
1418
|
self.refresh()
|
1248
|
-
self.
|
1419
|
+
if self.undo_enabled:
|
1420
|
+
self.undo_stack.append(stored_event_dict(event_data))
|
1249
1421
|
try_binding(self.extra_end_delete_key_func, event_data, "end_delete")
|
1250
1422
|
self.sheet_modified(event_data)
|
1251
1423
|
self.PAR.emit_event("<<Delete>>", event_data)
|
1252
1424
|
return event_data
|
1253
1425
|
|
1254
|
-
def event_data_set_cell(self, datarn: int, datacn: int, value: Any, event_data:
|
1426
|
+
def event_data_set_cell(self, datarn: int, datacn: int, value: Any, event_data: EventDataDict) -> EventDataDict:
|
1427
|
+
"""If bulk_table_edit_validation_func -> only updates event_data.data"""
|
1255
1428
|
if self.input_valid_for_cell(datarn, datacn, value):
|
1256
|
-
|
1257
|
-
|
1429
|
+
if self.bulk_table_edit_validation_func:
|
1430
|
+
event_data["data"][(datarn, datacn)] = value
|
1431
|
+
else:
|
1432
|
+
event_data["cells"]["table"][(datarn, datacn)] = self.get_cell_data(datarn, datacn)
|
1433
|
+
self.set_cell_data(datarn, datacn, value)
|
1434
|
+
return event_data
|
1435
|
+
|
1436
|
+
def bulk_edit_validation(self, event_data: EventDataDict) -> EventDataDict:
|
1437
|
+
if self.bulk_table_edit_validation_func:
|
1438
|
+
self.bulk_table_edit_validation_func(event_data)
|
1439
|
+
for (datarn, datacn), value in event_data["data"].items():
|
1440
|
+
if self.input_valid_for_cell(datarn, datacn, value):
|
1441
|
+
event_data["cells"]["table"][(datarn, datacn)] = self.get_cell_data(datarn, datacn)
|
1442
|
+
self.set_cell_data(datarn, datacn, value)
|
1258
1443
|
return event_data
|
1259
1444
|
|
1260
1445
|
def get_args_for_move_columns(
|
@@ -1562,6 +1747,7 @@ class MainTable(tk.Canvas):
|
|
1562
1747
|
event_data: EventDataDict | None = None,
|
1563
1748
|
undo_modification: EventDataDict | None = None,
|
1564
1749
|
node_change: None | tuple[str, str, int] = None,
|
1750
|
+
manage_tree: bool = True,
|
1565
1751
|
) -> tuple[dict[int, int], dict[int, int], EventDataDict]:
|
1566
1752
|
self.saved_row_heights = {}
|
1567
1753
|
if not isinstance(totalrows, int):
|
@@ -1583,7 +1769,7 @@ class MainTable(tk.Canvas):
|
|
1583
1769
|
|
1584
1770
|
if move_data:
|
1585
1771
|
maxidx = len_to_idx(totalrows)
|
1586
|
-
if self.PAR.ops.treeview:
|
1772
|
+
if manage_tree and self.PAR.ops.treeview:
|
1587
1773
|
two_step_move = self.RI.move_rows_mod_nodes(
|
1588
1774
|
data_new_idxs=data_new_idxs,
|
1589
1775
|
data_old_idxs=data_old_idxs,
|
@@ -1615,7 +1801,7 @@ class MainTable(tk.Canvas):
|
|
1615
1801
|
self.RI.cell_options = {full_new_idxs[k]: v for k, v in self.RI.cell_options.items()}
|
1616
1802
|
self.RI.tree_rns = {v: full_new_idxs[k] for v, k in self.RI.tree_rns.items()}
|
1617
1803
|
self.displayed_rows = sorted(full_new_idxs[k] for k in self.displayed_rows)
|
1618
|
-
if self.PAR.ops.treeview:
|
1804
|
+
if manage_tree and self.PAR.ops.treeview:
|
1619
1805
|
next(two_step_move)
|
1620
1806
|
|
1621
1807
|
if self.named_spans:
|
@@ -1814,38 +2000,28 @@ class MainTable(tk.Canvas):
|
|
1814
2000
|
self.purge_redo_stack()
|
1815
2001
|
|
1816
2002
|
def edit_cells_using_modification(self, modification: dict, event_data: dict) -> EventDataDict:
|
1817
|
-
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
2003
|
+
treeview = self.PAR.ops.treeview
|
2004
|
+
for datarn, v in modification["cells"]["index"].items():
|
2005
|
+
if not self.edit_validation_func or (
|
2006
|
+
self.edit_validation_func
|
2007
|
+
and (v := self.edit_validation_func(mod_event_val(event_data, v, row=datarn))) is not None
|
2008
|
+
):
|
2009
|
+
if treeview:
|
1824
2010
|
self._row_index[datarn].text = v
|
1825
|
-
|
1826
|
-
for datarn, v in modification["cells"]["index"].items():
|
1827
|
-
if not self.edit_validation_func or (
|
1828
|
-
self.edit_validation_func
|
1829
|
-
and (v := self.edit_validation_func(mod_event_val(event_data, v, row=datarn))) is not None
|
1830
|
-
):
|
2011
|
+
else:
|
1831
2012
|
self._row_index[datarn] = v
|
1832
|
-
|
1833
|
-
# header
|
1834
2013
|
for datacn, v in modification["cells"]["header"].items():
|
1835
2014
|
if not self.edit_validation_func or (
|
1836
2015
|
self.edit_validation_func
|
1837
2016
|
and (v := self.edit_validation_func(mod_event_val(event_data, v, column=datacn))) is not None
|
1838
2017
|
):
|
1839
2018
|
self._headers[datacn] = v
|
1840
|
-
|
1841
|
-
# table
|
1842
2019
|
for k, v in modification["cells"]["table"].items():
|
1843
2020
|
if not self.edit_validation_func or (
|
1844
2021
|
self.edit_validation_func
|
1845
2022
|
and (v := self.edit_validation_func(mod_event_val(event_data, v, loc=k))) is not None
|
1846
2023
|
):
|
1847
|
-
self.
|
1848
|
-
|
2024
|
+
event_data = self.event_data_set_cell(k[0], k[1], v, event_data)
|
1849
2025
|
return event_data
|
1850
2026
|
|
1851
2027
|
def save_cells_using_modification(self, modification: EventDataDict, event_data: EventDataDict) -> EventDataDict:
|
@@ -2004,6 +2180,7 @@ class MainTable(tk.Canvas):
|
|
2004
2180
|
if not saved_cells:
|
2005
2181
|
event_data = self.save_cells_using_modification(modification, event_data)
|
2006
2182
|
event_data = self.edit_cells_using_modification(modification, event_data)
|
2183
|
+
event_data = self.bulk_edit_validation(event_data)
|
2007
2184
|
|
2008
2185
|
elif modification["eventname"].startswith("add"):
|
2009
2186
|
event_data["eventname"] = modification["eventname"].replace("add", "delete")
|
@@ -2039,8 +2216,10 @@ class MainTable(tk.Canvas):
|
|
2039
2216
|
redraw: bool = True,
|
2040
2217
|
r_pc: float = 0.0,
|
2041
2218
|
c_pc: float = 0.0,
|
2219
|
+
index: bool = True,
|
2042
2220
|
) -> bool:
|
2043
|
-
|
2221
|
+
need_y_redraw = False
|
2222
|
+
need_x_redraw = False
|
2044
2223
|
vis_info = self.cell_visibility_info(r, c)
|
2045
2224
|
yvis, xvis = vis_info["yvis"], vis_info["xvis"]
|
2046
2225
|
top_left_x, top_left_y, bottom_right_x, bottom_right_y = vis_info["visible_region"]
|
@@ -2067,7 +2246,7 @@ class MainTable(tk.Canvas):
|
|
2067
2246
|
y - 1 if y > 1 else y,
|
2068
2247
|
]
|
2069
2248
|
self.set_yviews(*args, redraw=False)
|
2070
|
-
|
2249
|
+
need_y_redraw = True
|
2071
2250
|
else:
|
2072
2251
|
if r is not None and not keep_yscroll:
|
2073
2252
|
y = max(
|
@@ -2079,7 +2258,7 @@ class MainTable(tk.Canvas):
|
|
2079
2258
|
y - 1 if y > 1 else y,
|
2080
2259
|
]
|
2081
2260
|
self.set_yviews(*args, redraw=False)
|
2082
|
-
|
2261
|
+
need_y_redraw = True
|
2083
2262
|
# x scroll
|
2084
2263
|
if not check_cell_visibility or (check_cell_visibility and not xvis) and len(self.col_positions) > 1:
|
2085
2264
|
if bottom_right_corner is None:
|
@@ -2102,7 +2281,7 @@ class MainTable(tk.Canvas):
|
|
2102
2281
|
x - 1 if x > 1 else x,
|
2103
2282
|
]
|
2104
2283
|
self.set_xviews(*args, redraw=False)
|
2105
|
-
|
2284
|
+
need_x_redraw = True
|
2106
2285
|
else:
|
2107
2286
|
if c is not None and not keep_xscroll:
|
2108
2287
|
x = max(
|
@@ -2114,8 +2293,24 @@ class MainTable(tk.Canvas):
|
|
2114
2293
|
x - 1 if x > 1 else x,
|
2115
2294
|
]
|
2116
2295
|
self.set_xviews(*args, redraw=False)
|
2117
|
-
|
2118
|
-
|
2296
|
+
need_x_redraw = True
|
2297
|
+
# the index may have resized after scrolling making x calculation wrong
|
2298
|
+
if need_x_redraw and index and self.PAR.ops.auto_resize_row_index and self.show_index:
|
2299
|
+
self.main_table_redraw_grid_and_text(redraw_header=False, redraw_row_index=False, redraw_table=False)
|
2300
|
+
self.see(
|
2301
|
+
r=r,
|
2302
|
+
c=c,
|
2303
|
+
keep_yscroll=keep_yscroll,
|
2304
|
+
keep_xscroll=keep_xscroll,
|
2305
|
+
check_cell_visibility=check_cell_visibility,
|
2306
|
+
redraw=redraw,
|
2307
|
+
r_pc=r_pc,
|
2308
|
+
c_pc=c_pc,
|
2309
|
+
index=False,
|
2310
|
+
)
|
2311
|
+
if (need_y_redraw or need_x_redraw) and self.find_window.open:
|
2312
|
+
self.reposition_find_window() # prevent it from appearing to move around
|
2313
|
+
if redraw and (need_y_redraw or need_x_redraw):
|
2119
2314
|
self.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
|
2120
2315
|
return True
|
2121
2316
|
return False
|
@@ -2534,7 +2729,7 @@ class MainTable(tk.Canvas):
|
|
2534
2729
|
):
|
2535
2730
|
self.select_cell(r - 1, c, redraw=True)
|
2536
2731
|
|
2537
|
-
def arrowkey_LEFT(self, event: Any = None) ->
|
2732
|
+
def arrowkey_LEFT(self, event: Any = None) -> Literal["break"]:
|
2538
2733
|
if not self.selected:
|
2539
2734
|
return
|
2540
2735
|
r = self.selected.row
|
@@ -2550,6 +2745,7 @@ class MainTable(tk.Canvas):
|
|
2550
2745
|
self.single_selection_enabled or self.toggle_selection_enabled
|
2551
2746
|
):
|
2552
2747
|
self.select_cell(r, c - 1, redraw=True)
|
2748
|
+
return "break"
|
2553
2749
|
|
2554
2750
|
def arrowkey_DOWN(self, event: Any = None) -> None:
|
2555
2751
|
if not self.selected:
|
@@ -2719,13 +2915,7 @@ class MainTable(tk.Canvas):
|
|
2719
2915
|
self.empty_rc_popup_menu,
|
2720
2916
|
):
|
2721
2917
|
menu.delete(0, "end")
|
2722
|
-
mnkwgs =
|
2723
|
-
"font": self.PAR.ops.table_font,
|
2724
|
-
"foreground": self.PAR.ops.popup_menu_fg,
|
2725
|
-
"background": self.PAR.ops.popup_menu_bg,
|
2726
|
-
"activebackground": self.PAR.ops.popup_menu_highlight_bg,
|
2727
|
-
"activeforeground": self.PAR.ops.popup_menu_highlight_fg,
|
2728
|
-
}
|
2918
|
+
mnkwgs = get_menu_kwargs(self.PAR.ops)
|
2729
2919
|
if self.rc_popup_menus_enabled and self.CH.edit_cell_enabled:
|
2730
2920
|
self.menu_add_command(
|
2731
2921
|
self.CH.ch_rc_popup_menu,
|
@@ -3137,11 +3327,13 @@ class MainTable(tk.Canvas):
|
|
3137
3327
|
self.undo_enabled = True
|
3138
3328
|
self._tksheet_bind("undo_bindings", self.undo)
|
3139
3329
|
self._tksheet_bind("redo_bindings", self.redo)
|
3140
|
-
if binding in ("find"
|
3330
|
+
if binding in ("all", "find"):
|
3141
3331
|
self.find_enabled = True
|
3142
3332
|
self._tksheet_bind("find_bindings", self.open_find_window)
|
3143
3333
|
self._tksheet_bind("find_next_bindings", self.find_next)
|
3144
3334
|
self._tksheet_bind("find_previous_bindings", self.find_previous)
|
3335
|
+
if binding in ("all", "replace"):
|
3336
|
+
self._tksheet_bind("toggle_replace_bindings", self.replace_toggle)
|
3145
3337
|
if binding in bind_del_columns:
|
3146
3338
|
self.rc_delete_column_enabled = True
|
3147
3339
|
self.rc_popup_menus_enabled = True
|
@@ -3184,6 +3376,7 @@ class MainTable(tk.Canvas):
|
|
3184
3376
|
# has to be specifically enabled
|
3185
3377
|
if binding in ("ctrl_click_select", "ctrl_select"):
|
3186
3378
|
self.ctrl_select_enabled = True
|
3379
|
+
self.enabled_bindings.add(binding)
|
3187
3380
|
|
3188
3381
|
def _tksheet_bind(self, bindings_key: str, func: Callable) -> None:
|
3189
3382
|
for widget in (self, self.RI, self.CH, self.TL):
|
@@ -3193,6 +3386,10 @@ class MainTable(tk.Canvas):
|
|
3193
3386
|
def _disable_binding(self, binding: Binding) -> None:
|
3194
3387
|
if binding == "disable_all":
|
3195
3388
|
binding = "all"
|
3389
|
+
if binding == "all":
|
3390
|
+
self.enabled_bindings = set()
|
3391
|
+
else:
|
3392
|
+
self.enabled_bindings.discard(binding)
|
3196
3393
|
if binding in (
|
3197
3394
|
"all",
|
3198
3395
|
"single",
|
@@ -3311,6 +3508,8 @@ class MainTable(tk.Canvas):
|
|
3311
3508
|
self._tksheet_unbind("find_next_bindings")
|
3312
3509
|
self._tksheet_unbind("find_previous_bindings")
|
3313
3510
|
self.close_find_window()
|
3511
|
+
if binding in ("all", "replace"):
|
3512
|
+
self._tksheet_unbind("toggle_replace_bindings")
|
3314
3513
|
|
3315
3514
|
def _tksheet_unbind(self, *keys) -> None:
|
3316
3515
|
for widget in (self, self.RI, self.CH, self.TL):
|
@@ -5572,8 +5771,7 @@ class MainTable(tk.Canvas):
|
|
5572
5771
|
|
5573
5772
|
def total_data_cols(self, include_header: bool = True) -> int:
|
5574
5773
|
h_total = len(self._headers) if include_header and isinstance(self._headers, (list, tuple)) else 0
|
5575
|
-
|
5576
|
-
d_total = max(map(len, self.data), default=0)
|
5774
|
+
d_total = max(map(len, self.data), default=0) # max(map(len, )) is faster
|
5577
5775
|
return max(h_total, d_total)
|
5578
5776
|
|
5579
5777
|
def total_data_rows(self, include_index: bool = True) -> int:
|
@@ -6112,10 +6310,12 @@ class MainTable(tk.Canvas):
|
|
6112
6310
|
text_end_row = grid_end_row - 1 if grid_end_row == len(self.row_positions) else grid_end_row
|
6113
6311
|
text_start_col = grid_start_col - 1 if grid_start_col else grid_start_col
|
6114
6312
|
text_end_col = grid_end_col - 1 if grid_end_col == len(self.col_positions) else grid_end_col
|
6115
|
-
|
6313
|
+
# manage find window
|
6314
|
+
if self.find_window.open:
|
6315
|
+
self.reposition_find_window()
|
6116
6316
|
# check if auto resizing row index
|
6117
6317
|
changed_w = False
|
6118
|
-
if self.PAR.ops.auto_resize_row_index and
|
6318
|
+
if self.PAR.ops.auto_resize_row_index and self.show_index:
|
6119
6319
|
changed_w = self.RI.auto_set_index_width(
|
6120
6320
|
end_row=grid_end_row,
|
6121
6321
|
only_rows=map(self.datarn, range(text_start_row, text_end_row)),
|
@@ -6129,16 +6329,6 @@ class MainTable(tk.Canvas):
|
|
6129
6329
|
# important vars
|
6130
6330
|
x_stop = min(last_col_line_pos, scrollpos_right)
|
6131
6331
|
y_stop = min(last_row_line_pos, scrollpos_bot)
|
6132
|
-
# manage find window
|
6133
|
-
if self.find_window.open:
|
6134
|
-
w, h, x, y = self.get_find_window_dimensions_coords(w_width=self.winfo_width())
|
6135
|
-
self.coords(self.find_window.canvas_id, x, y)
|
6136
|
-
self.itemconfig(
|
6137
|
-
self.find_window.canvas_id,
|
6138
|
-
width=w,
|
6139
|
-
height=h,
|
6140
|
-
state="normal",
|
6141
|
-
)
|
6142
6332
|
# redraw table
|
6143
6333
|
if redraw_table:
|
6144
6334
|
# reset canvas item storage
|
@@ -7184,15 +7374,7 @@ class MainTable(tk.Canvas):
|
|
7184
7374
|
w = self.col_positions[c + 1] - x + 1
|
7185
7375
|
h = self.row_positions[r + 1] - y + 1
|
7186
7376
|
kwargs = {
|
7187
|
-
"menu_kwargs":
|
7188
|
-
{
|
7189
|
-
"font": self.PAR.ops.table_font,
|
7190
|
-
"foreground": self.PAR.ops.popup_menu_fg,
|
7191
|
-
"background": self.PAR.ops.popup_menu_bg,
|
7192
|
-
"activebackground": self.PAR.ops.popup_menu_highlight_bg,
|
7193
|
-
"activeforeground": self.PAR.ops.popup_menu_highlight_fg,
|
7194
|
-
}
|
7195
|
-
),
|
7377
|
+
"menu_kwargs": get_menu_kwargs(self.PAR.ops),
|
7196
7378
|
"sheet_ops": self.PAR.ops,
|
7197
7379
|
"border_color": self.PAR.ops.table_selected_box_cells_fg,
|
7198
7380
|
"text": text,
|
@@ -7200,10 +7382,7 @@ class MainTable(tk.Canvas):
|
|
7200
7382
|
"width": w,
|
7201
7383
|
"height": h,
|
7202
7384
|
"show_border": True,
|
7203
|
-
|
7204
|
-
"fg": self.PAR.ops.table_editor_fg,
|
7205
|
-
"select_bg": self.PAR.ops.table_editor_select_bg,
|
7206
|
-
"select_fg": self.PAR.ops.table_editor_select_fg,
|
7385
|
+
**get_bg_fg(self.PAR.ops),
|
7207
7386
|
"align": self.get_cell_align(r, c),
|
7208
7387
|
"r": r,
|
7209
7388
|
"c": c,
|
@@ -7335,7 +7514,7 @@ class MainTable(tk.Canvas):
|
|
7335
7514
|
self.focus_set()
|
7336
7515
|
return
|
7337
7516
|
# setting cell data with text editor value
|
7338
|
-
|
7517
|
+
value = self.text_editor.get()
|
7339
7518
|
r, c = self.text_editor.coords
|
7340
7519
|
datarn, datacn = self.datarn(r), self.datacn(c)
|
7341
7520
|
event_data = event_dict(
|
@@ -7344,30 +7523,26 @@ class MainTable(tk.Canvas):
|
|
7344
7523
|
widget=self,
|
7345
7524
|
cells_table={(datarn, datacn): self.get_cell_data(datarn, datacn)},
|
7346
7525
|
key=event.keysym,
|
7347
|
-
value=
|
7526
|
+
value=value,
|
7348
7527
|
loc=Loc(r, c),
|
7349
7528
|
row=r,
|
7350
7529
|
column=c,
|
7351
7530
|
boxes=self.get_boxes(),
|
7352
7531
|
selected=self.selected,
|
7532
|
+
data={(datarn, datacn): value},
|
7353
7533
|
)
|
7534
|
+
value, event_data = self.single_edit_run_validation(datarn, datacn, event_data)
|
7354
7535
|
edited = False
|
7355
|
-
|
7356
|
-
self.set_cell_data_undo
|
7357
|
-
|
7358
|
-
|
7359
|
-
|
7360
|
-
|
7361
|
-
|
7362
|
-
|
7363
|
-
|
7364
|
-
|
7365
|
-
text_editor_value = self.edit_validation_func(event_data)
|
7366
|
-
if text_editor_value is not None and self.input_valid_for_cell(datarn, datacn, text_editor_value):
|
7367
|
-
edited = set_data(value=text_editor_value)
|
7368
|
-
elif self.input_valid_for_cell(datarn, datacn, text_editor_value):
|
7369
|
-
edited = set_data(value=text_editor_value)
|
7370
|
-
if edited:
|
7536
|
+
if value is not None and (
|
7537
|
+
edited := self.set_cell_data_undo(
|
7538
|
+
r=r,
|
7539
|
+
c=c,
|
7540
|
+
datarn=datarn,
|
7541
|
+
datacn=datacn,
|
7542
|
+
value=value,
|
7543
|
+
redraw=False,
|
7544
|
+
)
|
7545
|
+
):
|
7371
7546
|
try_binding(self.extra_end_edit_cell_func, event_data)
|
7372
7547
|
if (
|
7373
7548
|
r is not None
|
@@ -7376,48 +7551,55 @@ class MainTable(tk.Canvas):
|
|
7376
7551
|
and r == self.selected.row
|
7377
7552
|
and c == self.selected.column
|
7378
7553
|
and (self.single_selection_enabled or self.toggle_selection_enabled)
|
7379
|
-
and (edited or self.cell_equal_to(datarn, datacn,
|
7554
|
+
and (edited or self.cell_equal_to(datarn, datacn, value))
|
7380
7555
|
):
|
7381
|
-
|
7382
|
-
numcols = c2 - c1
|
7383
|
-
numrows = r2 - r1
|
7384
|
-
if numcols == 1 and numrows == 1:
|
7385
|
-
if event.keysym == "Return":
|
7386
|
-
if self.PAR.ops.edit_cell_return == "right":
|
7387
|
-
self.select_right(r, c)
|
7388
|
-
if self.PAR.ops.edit_cell_return == "down":
|
7389
|
-
self.select_down(r, c)
|
7390
|
-
elif event.keysym == "Tab":
|
7391
|
-
if self.PAR.ops.edit_cell_tab == "right":
|
7392
|
-
self.select_right(r, c)
|
7393
|
-
if self.PAR.ops.edit_cell_tab == "down":
|
7394
|
-
self.select_down(r, c)
|
7395
|
-
else:
|
7396
|
-
if event.keysym == "Return":
|
7397
|
-
if self.PAR.ops.edit_cell_return == "right":
|
7398
|
-
new_r, new_c = cell_right_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7399
|
-
elif self.PAR.ops.edit_cell_return == "down":
|
7400
|
-
new_r, new_c = down_cell_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7401
|
-
else:
|
7402
|
-
new_r, new_c = None, None
|
7403
|
-
elif event.keysym == "Tab":
|
7404
|
-
if self.PAR.ops.edit_cell_tab == "right":
|
7405
|
-
new_r, new_c = cell_right_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7406
|
-
elif self.PAR.ops.edit_cell_tab == "down":
|
7407
|
-
new_r, new_c = down_cell_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7408
|
-
else:
|
7409
|
-
new_r, new_c = None, None
|
7410
|
-
else:
|
7411
|
-
new_r, new_c = None, None
|
7412
|
-
if isinstance(new_r, int):
|
7413
|
-
self.set_currently_selected(new_r, new_c, item=self.selected.fill_iid)
|
7414
|
-
self.see(new_r, new_c)
|
7556
|
+
self.go_to_next_cell(r, c, event.keysym)
|
7415
7557
|
self.recreate_all_selection_boxes()
|
7416
7558
|
self.hide_text_editor_and_dropdown()
|
7417
7559
|
if event.keysym != "FocusOut":
|
7418
7560
|
self.focus_set()
|
7419
7561
|
return "break"
|
7420
7562
|
|
7563
|
+
def go_to_next_cell(self, r: int, c: int, key: Any = "Return") -> None:
|
7564
|
+
r1, c1, r2, c2 = self.selection_boxes[self.selected.fill_iid].coords
|
7565
|
+
numrows, numcols = r2 - r1, c2 - c1
|
7566
|
+
if key == "Return":
|
7567
|
+
direction = self.PAR.ops.edit_cell_return
|
7568
|
+
elif key == "Tab":
|
7569
|
+
direction = self.PAR.ops.edit_cell_tab
|
7570
|
+
else:
|
7571
|
+
return
|
7572
|
+
if numcols == 1 and numrows == 1:
|
7573
|
+
if direction == "right":
|
7574
|
+
self.select_right(r, c)
|
7575
|
+
elif direction == "down":
|
7576
|
+
self.select_down(r, c)
|
7577
|
+
else:
|
7578
|
+
if direction == "right":
|
7579
|
+
new_r, new_c = cell_right_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7580
|
+
elif direction == "down":
|
7581
|
+
new_r, new_c = cell_down_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7582
|
+
if direction in ("right", "down"):
|
7583
|
+
self.set_currently_selected(new_r, new_c, item=self.selected.fill_iid)
|
7584
|
+
self.see(new_r, new_c)
|
7585
|
+
|
7586
|
+
def single_edit_run_validation(
|
7587
|
+
self, datarn: int, datacn: int, event_data: EventDataDict
|
7588
|
+
) -> tuple[Any, EventDataDict]:
|
7589
|
+
value = event_data.value
|
7590
|
+
if self.edit_validation_func and (new_value := self.edit_validation_func(event_data)) is not None:
|
7591
|
+
value = new_value
|
7592
|
+
event_data["data"][(datarn, datacn)] = value
|
7593
|
+
event_data["value"] = value
|
7594
|
+
if self.bulk_table_edit_validation_func:
|
7595
|
+
self.bulk_table_edit_validation_func(event_data)
|
7596
|
+
if (datarn, datacn) in event_data["data"]:
|
7597
|
+
if event_data["data"][(datarn, datacn)] is not None:
|
7598
|
+
value = event_data["data"][(datarn, datacn)]
|
7599
|
+
else:
|
7600
|
+
value = None
|
7601
|
+
return value, event_data
|
7602
|
+
|
7421
7603
|
def select_right(self, r: int, c: int) -> None:
|
7422
7604
|
self.select_cell(r, c + 1 if c < len(self.col_positions) - 2 else c)
|
7423
7605
|
self.see(
|
@@ -7628,13 +7810,19 @@ class MainTable(tk.Canvas):
|
|
7628
7810
|
column=c,
|
7629
7811
|
boxes=self.get_boxes(),
|
7630
7812
|
selected=self.selected,
|
7813
|
+
data={(datarn, datacn): selection},
|
7631
7814
|
)
|
7632
7815
|
try_binding(kwargs["select_function"], event_data)
|
7633
|
-
selection =
|
7634
|
-
if selection is not None
|
7635
|
-
|
7636
|
-
|
7637
|
-
|
7816
|
+
selection, event_data = self.single_edit_run_validation(datarn, datacn, event_data)
|
7817
|
+
if selection is not None and self.set_cell_data_undo(
|
7818
|
+
r,
|
7819
|
+
c,
|
7820
|
+
datarn=datarn,
|
7821
|
+
datacn=datacn,
|
7822
|
+
value=selection,
|
7823
|
+
redraw=not redraw,
|
7824
|
+
):
|
7825
|
+
try_binding(self.extra_end_edit_cell_func, event_data)
|
7638
7826
|
self.recreate_all_selection_boxes()
|
7639
7827
|
self.focus_set()
|
7640
7828
|
self.hide_text_editor_and_dropdown(redraw=redraw)
|
@@ -7653,12 +7841,12 @@ class MainTable(tk.Canvas):
|
|
7653
7841
|
self.focus_set()
|
7654
7842
|
return closed_dd_coords
|
7655
7843
|
|
7656
|
-
def mouseclick_outside_editor_or_dropdown_all_canvases(self):
|
7844
|
+
def mouseclick_outside_editor_or_dropdown_all_canvases(self) -> tuple[int, int] | None:
|
7657
7845
|
self.CH.mouseclick_outside_editor_or_dropdown()
|
7658
7846
|
self.RI.mouseclick_outside_editor_or_dropdown()
|
7659
7847
|
return self.mouseclick_outside_editor_or_dropdown()
|
7660
7848
|
|
7661
|
-
def hide_dropdown_editor_all_canvases(self):
|
7849
|
+
def hide_dropdown_editor_all_canvases(self) -> None:
|
7662
7850
|
self.hide_text_editor_and_dropdown(redraw=False)
|
7663
7851
|
self.RI.hide_text_editor_and_dropdown(redraw=False)
|
7664
7852
|
self.CH.hide_text_editor_and_dropdown(redraw=False)
|
@@ -7706,6 +7894,7 @@ class MainTable(tk.Canvas):
|
|
7706
7894
|
column=c,
|
7707
7895
|
boxes=self.get_boxes(),
|
7708
7896
|
selected=self.selected,
|
7897
|
+
data={(datarn, datacn): value},
|
7709
7898
|
)
|
7710
7899
|
if kwargs["check_function"] is not None:
|
7711
7900
|
kwargs["check_function"](event_data)
|
@@ -7900,7 +8089,10 @@ class MainTable(tk.Canvas):
|
|
7900
8089
|
kwargs = self.get_cell_kwargs(datarn, datacn, key="checkbox")
|
7901
8090
|
if kwargs:
|
7902
8091
|
return f"{kwargs['text']}"
|
7903
|
-
|
8092
|
+
try:
|
8093
|
+
value = self.data[datarn][datacn]
|
8094
|
+
except Exception:
|
8095
|
+
value = ""
|
7904
8096
|
kwargs = self.get_cell_kwargs(datarn, datacn, key="format")
|
7905
8097
|
if kwargs:
|
7906
8098
|
if kwargs["formatter"] is None:
|
@@ -7915,28 +8107,24 @@ class MainTable(tk.Canvas):
|
|
7915
8107
|
else:
|
7916
8108
|
# assumed given formatter class has get_data_with_valid_check()
|
7917
8109
|
return f"{value.get_data_with_valid_check()}"
|
7918
|
-
|
8110
|
+
else:
|
8111
|
+
return "" if value is None else value if isinstance(value, str) else f"{value}"
|
7919
8112
|
|
7920
8113
|
def get_cell_data(
|
7921
8114
|
self,
|
7922
8115
|
datarn: int,
|
7923
8116
|
datacn: int,
|
7924
|
-
get_displayed: bool = False,
|
7925
8117
|
none_to_empty_str: bool = False,
|
7926
8118
|
fmt_kw: dict | None = None,
|
7927
|
-
**kwargs,
|
7928
8119
|
) -> Any:
|
7929
|
-
|
7930
|
-
|
7931
|
-
|
7932
|
-
self.
|
7933
|
-
if len(self.data) > datarn and len(self.data[datarn]) > datacn
|
7934
|
-
else self.get_value_for_empty_cell(datarn, datacn)
|
7935
|
-
)
|
8120
|
+
try: # when successful try is more than twice as fast as len check
|
8121
|
+
value = self.data[datarn][datacn]
|
8122
|
+
except Exception:
|
8123
|
+
value = self.get_value_for_empty_cell(datarn, datacn)
|
7936
8124
|
kwargs = self.get_cell_kwargs(datarn, datacn, key="format")
|
7937
8125
|
if kwargs and kwargs["formatter"] is not None:
|
7938
8126
|
value = value.value # assumed given formatter class has value attribute
|
7939
|
-
if
|
8127
|
+
if fmt_kw:
|
7940
8128
|
value = format_data(value=value, **fmt_kw)
|
7941
8129
|
return "" if (value is None and none_to_empty_str) else value
|
7942
8130
|
|
@@ -7953,7 +8141,7 @@ class MainTable(tk.Canvas):
|
|
7953
8141
|
return False
|
7954
8142
|
elif "format" in kwargs:
|
7955
8143
|
return True
|
7956
|
-
elif self.cell_equal_to(datarn, datacn, value, ignore_empty=ignore_empty) or (
|
8144
|
+
elif self.cell_equal_to(datarn, datacn, value, ignore_empty=ignore_empty, check_fmt=False) or (
|
7957
8145
|
(dropdown := kwargs.get("dropdown", {})) and dropdown["validate_input"] and value not in dropdown["values"]
|
7958
8146
|
):
|
7959
8147
|
return False
|
@@ -7962,22 +8150,20 @@ class MainTable(tk.Canvas):
|
|
7962
8150
|
else:
|
7963
8151
|
return True
|
7964
8152
|
|
7965
|
-
def cell_equal_to(
|
7966
|
-
|
7967
|
-
|
7968
|
-
|
7969
|
-
|
7970
|
-
|
7971
|
-
|
7972
|
-
|
7973
|
-
|
7974
|
-
|
7975
|
-
|
7976
|
-
|
7977
|
-
|
7978
|
-
|
7979
|
-
return False
|
7980
|
-
return v == value
|
8153
|
+
def cell_equal_to(
|
8154
|
+
self,
|
8155
|
+
datarn: int,
|
8156
|
+
datacn: int,
|
8157
|
+
new: Any,
|
8158
|
+
ignore_empty: bool = False,
|
8159
|
+
check_fmt: bool = True,
|
8160
|
+
) -> bool:
|
8161
|
+
current = self.get_cell_data(datarn, datacn)
|
8162
|
+
if check_fmt:
|
8163
|
+
kws = self.get_cell_kwargs(datarn, datacn, key="format")
|
8164
|
+
if kws and kws["formatter"] is None:
|
8165
|
+
new = format_data(value=new, **kws)
|
8166
|
+
return current == new and not (ignore_empty and not current and not new)
|
7981
8167
|
|
7982
8168
|
def get_cell_clipboard(self, datarn: int, datacn: int) -> str | int | float | bool:
|
7983
8169
|
value = self.data[datarn][datacn] if len(self.data) > datarn and len(self.data[datarn]) > datacn else ""
|