tksheet 7.4.7__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 +1 -1
- tksheet/column_headers.py +2 -0
- tksheet/constants.py +195 -0
- tksheet/find_window.py +324 -30
- tksheet/functions.py +89 -39
- tksheet/main_table.py +443 -288
- tksheet/row_index.py +9 -9
- tksheet/sheet.py +138 -488
- tksheet/sheet_options.py +4 -0
- tksheet/tksheet_types.py +3 -0
- {tksheet-7.4.7.dist-info → tksheet-7.4.8.dist-info}/METADATA +1 -1
- tksheet-7.4.8.dist-info/RECORD +22 -0
- tksheet-7.4.7.dist-info/RECORD +0 -22
- {tksheet-7.4.7.dist-info → tksheet-7.4.8.dist-info}/LICENSE.txt +0 -0
- {tksheet-7.4.7.dist-info → tksheet-7.4.8.dist-info}/WHEEL +0 -0
- {tksheet-7.4.7.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,23 +565,123 @@ 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
|
|
@@ -559,86 +690,98 @@ class MainTable(tk.Canvas):
|
|
559
690
|
value = self.data[r][c]
|
560
691
|
except Exception:
|
561
692
|
value = ""
|
562
|
-
kwargs = self.get_cell_kwargs(r, c, key=
|
693
|
+
kwargs = self.get_cell_kwargs(r, c, key="format")
|
563
694
|
if kwargs:
|
564
|
-
|
565
|
-
|
566
|
-
if kwargs["text"] is not None and find in str(kwargs["text"]).lower():
|
567
|
-
return True
|
568
|
-
elif "checkbox" in kwargs:
|
569
|
-
kwargs = kwargs["checkbox"]
|
570
|
-
if find in str(kwargs["text"]).lower() or (not find and find in "False"):
|
571
|
-
return True
|
572
|
-
elif "format" in kwargs:
|
573
|
-
if kwargs["formatter"] is None:
|
574
|
-
if find in data_to_str(value, **kwargs).lower():
|
575
|
-
return True
|
576
|
-
# assumed given formatter class has __str__() or value attribute
|
577
|
-
elif find in str(value).lower() or find in str(value.value).lower():
|
578
|
-
return True
|
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)
|
579
697
|
if value is None:
|
580
698
|
return find == ""
|
699
|
+
elif not find:
|
700
|
+
return str(value) == ""
|
581
701
|
else:
|
582
702
|
return find in str(value).lower()
|
583
703
|
|
584
|
-
def find_within_match(self, find: str, r: int, c: int) -> bool:
|
585
|
-
if not self.all_rows_displayed:
|
586
|
-
r = self.datarn(r)
|
587
|
-
if not self.all_columns_displayed:
|
588
|
-
c = self.datacn(c)
|
589
|
-
return self.find_match(find, r, c)
|
590
|
-
|
591
704
|
def find_within_current_box(
|
592
|
-
self,
|
593
|
-
|
594
|
-
|
595
|
-
reverse: bool,
|
596
|
-
) -> None | tuple[int, int]:
|
597
|
-
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(
|
598
708
|
*current_box.coords,
|
599
709
|
self.selected.row,
|
600
710
|
self.selected.column,
|
601
711
|
reverse=reverse,
|
602
712
|
)
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
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
|
+
)
|
608
735
|
|
609
|
-
def find_within_non_current_boxes(
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
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
|
+
)
|
615
745
|
if reverse:
|
616
746
|
# iterate backwards through selection boxes from the box before current
|
617
747
|
idx = next(i for i, k in enumerate(reversed(self.selection_boxes)) if k == current_id)
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
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
|
+
)
|
625
764
|
else:
|
626
765
|
# iterate forwards through selection boxes from the box after current
|
627
766
|
idx = next(i for i, k in enumerate(self.selection_boxes) if k == current_id)
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
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
|
+
)
|
636
783
|
|
637
|
-
def find_within(
|
638
|
-
self,
|
639
|
-
find: str,
|
640
|
-
reverse: bool = False,
|
641
|
-
) -> tuple[int, int, int] | None:
|
784
|
+
def find_within(self, find: str, reverse: bool = False) -> tuple[int, int, int] | None:
|
642
785
|
if not self.selected:
|
643
786
|
return None
|
644
787
|
current_box = self.selection_boxes[self.selected.fill_iid]
|
@@ -655,70 +798,73 @@ class MainTable(tk.Canvas):
|
|
655
798
|
return coord
|
656
799
|
return None
|
657
800
|
|
658
|
-
def find_all_cells(
|
659
|
-
self
|
660
|
-
|
661
|
-
|
662
|
-
) -> 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)
|
663
805
|
if self.selected:
|
664
|
-
|
806
|
+
start_r, start_c = next_cell(
|
665
807
|
0,
|
666
808
|
0,
|
667
|
-
|
668
|
-
|
669
|
-
self.selected.row,
|
670
|
-
self.selected.column,
|
809
|
+
totalrows,
|
810
|
+
totalcols,
|
811
|
+
self.datarn(self.selected.row),
|
812
|
+
self.datacn(self.selected.column),
|
671
813
|
reverse=reverse,
|
672
814
|
)
|
673
815
|
else:
|
674
|
-
|
675
|
-
|
676
|
-
result = next(
|
816
|
+
start_r, start_c = 0, 0
|
817
|
+
return next(
|
677
818
|
(
|
678
|
-
(r, c)
|
819
|
+
(r, c, None)
|
679
820
|
for r, c in box_gen_coords(
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
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,
|
684
827
|
reverse=reverse,
|
685
828
|
)
|
686
829
|
if (
|
687
830
|
self.find_match(find, r, c)
|
688
|
-
and (self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
831
|
+
and (tree or self.all_rows_displayed or bisect_in(self.displayed_rows, r))
|
689
832
|
and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
|
690
833
|
)
|
691
834
|
),
|
692
835
|
None,
|
693
836
|
)
|
694
|
-
if result:
|
695
|
-
return result + (None,)
|
696
|
-
return None
|
697
837
|
|
698
|
-
def
|
699
|
-
find = self.find_window.get().lower()
|
838
|
+
def replace_toggle(self, event: tk.Event | None) -> None:
|
700
839
|
if not self.find_window.open:
|
701
840
|
self.open_find_window(focus=False)
|
702
|
-
if self.find_window.window.
|
703
|
-
self.
|
704
|
-
|
705
|
-
self.find_see_and_set(self.find_all_cells(find))
|
706
|
-
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()
|
707
844
|
|
708
|
-
def
|
709
|
-
|
710
|
-
|
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:
|
711
855
|
self.open_find_window(focus=False)
|
712
|
-
if self.find_window.window.find_in_selection:
|
713
|
-
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)
|
714
858
|
else:
|
715
|
-
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)
|
716
860
|
return "break"
|
717
861
|
|
718
|
-
def
|
719
|
-
self,
|
720
|
-
|
721
|
-
|
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:
|
722
868
|
if self.find_window.open:
|
723
869
|
self.itemconfig(self.find_window.canvas_id, state="hidden")
|
724
870
|
self.find_window.open = False
|
@@ -925,11 +1071,13 @@ class MainTable(tk.Canvas):
|
|
925
1071
|
else:
|
926
1072
|
self.clipboard_append(s.getvalue())
|
927
1073
|
self.update_idletasks()
|
928
|
-
self.
|
929
|
-
for r1, c1, r2, c2 in boxes:
|
930
|
-
self.show_ctrl_outline(canvas="table", start_cell=(c1, r1), end_cell=(c2, r2))
|
1074
|
+
event_data = self.bulk_edit_validation(event_data)
|
931
1075
|
if event_data["cells"]["table"]:
|
932
|
-
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))
|
933
1081
|
try_binding(self.extra_end_ctrl_x_func, event_data, "end_ctrl_x")
|
934
1082
|
self.sheet_modified(event_data)
|
935
1083
|
self.PAR.emit_event("<<Cut>>", event_data)
|
@@ -980,6 +1128,7 @@ class MainTable(tk.Canvas):
|
|
980
1128
|
value=val,
|
981
1129
|
event_data=event_data,
|
982
1130
|
)
|
1131
|
+
event_data = self.bulk_edit_validation(event_data)
|
983
1132
|
if event_data["cells"]["table"]:
|
984
1133
|
if undo and self.undo_enabled:
|
985
1134
|
self.undo_stack.append(stored_event_dict(event_data))
|
@@ -1045,7 +1194,6 @@ class MainTable(tk.Canvas):
|
|
1045
1194
|
for _ in range(int(lastbox_numcols / new_data_numcols)):
|
1046
1195
|
data[rn].extend(r.copy())
|
1047
1196
|
new_data_numcols *= int(lastbox_numcols / new_data_numcols)
|
1048
|
-
event_data["data"] = data
|
1049
1197
|
added_rows = 0
|
1050
1198
|
added_cols = 0
|
1051
1199
|
total_data_cols = None
|
@@ -1084,6 +1232,11 @@ class MainTable(tk.Canvas):
|
|
1084
1232
|
): "cells"
|
1085
1233
|
}
|
1086
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
|
+
|
1087
1240
|
if not try_binding(self.extra_begin_ctrl_v_func, event_data, "begin_ctrl_v"):
|
1088
1241
|
return
|
1089
1242
|
# the order of actions here is important:
|
@@ -1225,8 +1378,10 @@ class MainTable(tk.Canvas):
|
|
1225
1378
|
event_data["selected"] = self.selected
|
1226
1379
|
self.see(selected_r, selected_c, redraw=False)
|
1227
1380
|
self.refresh()
|
1381
|
+
event_data = self.bulk_edit_validation(event_data)
|
1228
1382
|
if event_data["cells"]["table"] or event_data["added"]["rows"] or event_data["added"]["columns"]:
|
1229
|
-
self.
|
1383
|
+
if self.undo_enabled:
|
1384
|
+
self.undo_stack.append(stored_event_dict(event_data))
|
1230
1385
|
try_binding(self.extra_end_ctrl_v_func, event_data, "end_ctrl_v")
|
1231
1386
|
self.sheet_modified(event_data)
|
1232
1387
|
self.PAR.emit_event("<<Paste>>", event_data)
|
@@ -1258,18 +1413,33 @@ class MainTable(tk.Canvas):
|
|
1258
1413
|
val,
|
1259
1414
|
event_data,
|
1260
1415
|
)
|
1416
|
+
event_data = self.bulk_edit_validation(event_data)
|
1261
1417
|
if event_data["cells"]["table"]:
|
1262
1418
|
self.refresh()
|
1263
|
-
self.
|
1419
|
+
if self.undo_enabled:
|
1420
|
+
self.undo_stack.append(stored_event_dict(event_data))
|
1264
1421
|
try_binding(self.extra_end_delete_key_func, event_data, "end_delete")
|
1265
1422
|
self.sheet_modified(event_data)
|
1266
1423
|
self.PAR.emit_event("<<Delete>>", event_data)
|
1267
1424
|
return event_data
|
1268
1425
|
|
1269
|
-
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"""
|
1270
1428
|
if self.input_valid_for_cell(datarn, datacn, value):
|
1271
|
-
|
1272
|
-
|
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)
|
1273
1443
|
return event_data
|
1274
1444
|
|
1275
1445
|
def get_args_for_move_columns(
|
@@ -1577,6 +1747,7 @@ class MainTable(tk.Canvas):
|
|
1577
1747
|
event_data: EventDataDict | None = None,
|
1578
1748
|
undo_modification: EventDataDict | None = None,
|
1579
1749
|
node_change: None | tuple[str, str, int] = None,
|
1750
|
+
manage_tree: bool = True,
|
1580
1751
|
) -> tuple[dict[int, int], dict[int, int], EventDataDict]:
|
1581
1752
|
self.saved_row_heights = {}
|
1582
1753
|
if not isinstance(totalrows, int):
|
@@ -1598,7 +1769,7 @@ class MainTable(tk.Canvas):
|
|
1598
1769
|
|
1599
1770
|
if move_data:
|
1600
1771
|
maxidx = len_to_idx(totalrows)
|
1601
|
-
if self.PAR.ops.treeview:
|
1772
|
+
if manage_tree and self.PAR.ops.treeview:
|
1602
1773
|
two_step_move = self.RI.move_rows_mod_nodes(
|
1603
1774
|
data_new_idxs=data_new_idxs,
|
1604
1775
|
data_old_idxs=data_old_idxs,
|
@@ -1630,7 +1801,7 @@ class MainTable(tk.Canvas):
|
|
1630
1801
|
self.RI.cell_options = {full_new_idxs[k]: v for k, v in self.RI.cell_options.items()}
|
1631
1802
|
self.RI.tree_rns = {v: full_new_idxs[k] for v, k in self.RI.tree_rns.items()}
|
1632
1803
|
self.displayed_rows = sorted(full_new_idxs[k] for k in self.displayed_rows)
|
1633
|
-
if self.PAR.ops.treeview:
|
1804
|
+
if manage_tree and self.PAR.ops.treeview:
|
1634
1805
|
next(two_step_move)
|
1635
1806
|
|
1636
1807
|
if self.named_spans:
|
@@ -1829,38 +2000,28 @@ class MainTable(tk.Canvas):
|
|
1829
2000
|
self.purge_redo_stack()
|
1830
2001
|
|
1831
2002
|
def edit_cells_using_modification(self, modification: dict, event_data: dict) -> EventDataDict:
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
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:
|
1839
2010
|
self._row_index[datarn].text = v
|
1840
|
-
|
1841
|
-
for datarn, v in modification["cells"]["index"].items():
|
1842
|
-
if not self.edit_validation_func or (
|
1843
|
-
self.edit_validation_func
|
1844
|
-
and (v := self.edit_validation_func(mod_event_val(event_data, v, row=datarn))) is not None
|
1845
|
-
):
|
2011
|
+
else:
|
1846
2012
|
self._row_index[datarn] = v
|
1847
|
-
|
1848
|
-
# header
|
1849
2013
|
for datacn, v in modification["cells"]["header"].items():
|
1850
2014
|
if not self.edit_validation_func or (
|
1851
2015
|
self.edit_validation_func
|
1852
2016
|
and (v := self.edit_validation_func(mod_event_val(event_data, v, column=datacn))) is not None
|
1853
2017
|
):
|
1854
2018
|
self._headers[datacn] = v
|
1855
|
-
|
1856
|
-
# table
|
1857
2019
|
for k, v in modification["cells"]["table"].items():
|
1858
2020
|
if not self.edit_validation_func or (
|
1859
2021
|
self.edit_validation_func
|
1860
2022
|
and (v := self.edit_validation_func(mod_event_val(event_data, v, loc=k))) is not None
|
1861
2023
|
):
|
1862
|
-
self.
|
1863
|
-
|
2024
|
+
event_data = self.event_data_set_cell(k[0], k[1], v, event_data)
|
1864
2025
|
return event_data
|
1865
2026
|
|
1866
2027
|
def save_cells_using_modification(self, modification: EventDataDict, event_data: EventDataDict) -> EventDataDict:
|
@@ -2019,6 +2180,7 @@ class MainTable(tk.Canvas):
|
|
2019
2180
|
if not saved_cells:
|
2020
2181
|
event_data = self.save_cells_using_modification(modification, event_data)
|
2021
2182
|
event_data = self.edit_cells_using_modification(modification, event_data)
|
2183
|
+
event_data = self.bulk_edit_validation(event_data)
|
2022
2184
|
|
2023
2185
|
elif modification["eventname"].startswith("add"):
|
2024
2186
|
event_data["eventname"] = modification["eventname"].replace("add", "delete")
|
@@ -2146,6 +2308,8 @@ class MainTable(tk.Canvas):
|
|
2146
2308
|
c_pc=c_pc,
|
2147
2309
|
index=False,
|
2148
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
|
2149
2313
|
if redraw and (need_y_redraw or need_x_redraw):
|
2150
2314
|
self.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
|
2151
2315
|
return True
|
@@ -2751,13 +2915,7 @@ class MainTable(tk.Canvas):
|
|
2751
2915
|
self.empty_rc_popup_menu,
|
2752
2916
|
):
|
2753
2917
|
menu.delete(0, "end")
|
2754
|
-
mnkwgs =
|
2755
|
-
"font": self.PAR.ops.table_font,
|
2756
|
-
"foreground": self.PAR.ops.popup_menu_fg,
|
2757
|
-
"background": self.PAR.ops.popup_menu_bg,
|
2758
|
-
"activebackground": self.PAR.ops.popup_menu_highlight_bg,
|
2759
|
-
"activeforeground": self.PAR.ops.popup_menu_highlight_fg,
|
2760
|
-
}
|
2918
|
+
mnkwgs = get_menu_kwargs(self.PAR.ops)
|
2761
2919
|
if self.rc_popup_menus_enabled and self.CH.edit_cell_enabled:
|
2762
2920
|
self.menu_add_command(
|
2763
2921
|
self.CH.ch_rc_popup_menu,
|
@@ -3169,11 +3327,13 @@ class MainTable(tk.Canvas):
|
|
3169
3327
|
self.undo_enabled = True
|
3170
3328
|
self._tksheet_bind("undo_bindings", self.undo)
|
3171
3329
|
self._tksheet_bind("redo_bindings", self.redo)
|
3172
|
-
if binding in ("find"
|
3330
|
+
if binding in ("all", "find"):
|
3173
3331
|
self.find_enabled = True
|
3174
3332
|
self._tksheet_bind("find_bindings", self.open_find_window)
|
3175
3333
|
self._tksheet_bind("find_next_bindings", self.find_next)
|
3176
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)
|
3177
3337
|
if binding in bind_del_columns:
|
3178
3338
|
self.rc_delete_column_enabled = True
|
3179
3339
|
self.rc_popup_menus_enabled = True
|
@@ -3216,6 +3376,7 @@ class MainTable(tk.Canvas):
|
|
3216
3376
|
# has to be specifically enabled
|
3217
3377
|
if binding in ("ctrl_click_select", "ctrl_select"):
|
3218
3378
|
self.ctrl_select_enabled = True
|
3379
|
+
self.enabled_bindings.add(binding)
|
3219
3380
|
|
3220
3381
|
def _tksheet_bind(self, bindings_key: str, func: Callable) -> None:
|
3221
3382
|
for widget in (self, self.RI, self.CH, self.TL):
|
@@ -3225,6 +3386,10 @@ class MainTable(tk.Canvas):
|
|
3225
3386
|
def _disable_binding(self, binding: Binding) -> None:
|
3226
3387
|
if binding == "disable_all":
|
3227
3388
|
binding = "all"
|
3389
|
+
if binding == "all":
|
3390
|
+
self.enabled_bindings = set()
|
3391
|
+
else:
|
3392
|
+
self.enabled_bindings.discard(binding)
|
3228
3393
|
if binding in (
|
3229
3394
|
"all",
|
3230
3395
|
"single",
|
@@ -3343,6 +3508,8 @@ class MainTable(tk.Canvas):
|
|
3343
3508
|
self._tksheet_unbind("find_next_bindings")
|
3344
3509
|
self._tksheet_unbind("find_previous_bindings")
|
3345
3510
|
self.close_find_window()
|
3511
|
+
if binding in ("all", "replace"):
|
3512
|
+
self._tksheet_unbind("toggle_replace_bindings")
|
3346
3513
|
|
3347
3514
|
def _tksheet_unbind(self, *keys) -> None:
|
3348
3515
|
for widget in (self, self.RI, self.CH, self.TL):
|
@@ -6143,7 +6310,9 @@ class MainTable(tk.Canvas):
|
|
6143
6310
|
text_end_row = grid_end_row - 1 if grid_end_row == len(self.row_positions) else grid_end_row
|
6144
6311
|
text_start_col = grid_start_col - 1 if grid_start_col else grid_start_col
|
6145
6312
|
text_end_col = grid_end_col - 1 if grid_end_col == len(self.col_positions) else grid_end_col
|
6146
|
-
|
6313
|
+
# manage find window
|
6314
|
+
if self.find_window.open:
|
6315
|
+
self.reposition_find_window()
|
6147
6316
|
# check if auto resizing row index
|
6148
6317
|
changed_w = False
|
6149
6318
|
if self.PAR.ops.auto_resize_row_index and self.show_index:
|
@@ -6160,16 +6329,6 @@ class MainTable(tk.Canvas):
|
|
6160
6329
|
# important vars
|
6161
6330
|
x_stop = min(last_col_line_pos, scrollpos_right)
|
6162
6331
|
y_stop = min(last_row_line_pos, scrollpos_bot)
|
6163
|
-
# manage find window
|
6164
|
-
if self.find_window.open:
|
6165
|
-
w, h, x, y = self.get_find_window_dimensions_coords(w_width=self.winfo_width())
|
6166
|
-
self.coords(self.find_window.canvas_id, x, y)
|
6167
|
-
self.itemconfig(
|
6168
|
-
self.find_window.canvas_id,
|
6169
|
-
width=w,
|
6170
|
-
height=h,
|
6171
|
-
state="normal",
|
6172
|
-
)
|
6173
6332
|
# redraw table
|
6174
6333
|
if redraw_table:
|
6175
6334
|
# reset canvas item storage
|
@@ -7215,15 +7374,7 @@ class MainTable(tk.Canvas):
|
|
7215
7374
|
w = self.col_positions[c + 1] - x + 1
|
7216
7375
|
h = self.row_positions[r + 1] - y + 1
|
7217
7376
|
kwargs = {
|
7218
|
-
"menu_kwargs":
|
7219
|
-
{
|
7220
|
-
"font": self.PAR.ops.table_font,
|
7221
|
-
"foreground": self.PAR.ops.popup_menu_fg,
|
7222
|
-
"background": self.PAR.ops.popup_menu_bg,
|
7223
|
-
"activebackground": self.PAR.ops.popup_menu_highlight_bg,
|
7224
|
-
"activeforeground": self.PAR.ops.popup_menu_highlight_fg,
|
7225
|
-
}
|
7226
|
-
),
|
7377
|
+
"menu_kwargs": get_menu_kwargs(self.PAR.ops),
|
7227
7378
|
"sheet_ops": self.PAR.ops,
|
7228
7379
|
"border_color": self.PAR.ops.table_selected_box_cells_fg,
|
7229
7380
|
"text": text,
|
@@ -7231,10 +7382,7 @@ class MainTable(tk.Canvas):
|
|
7231
7382
|
"width": w,
|
7232
7383
|
"height": h,
|
7233
7384
|
"show_border": True,
|
7234
|
-
|
7235
|
-
"fg": self.PAR.ops.table_editor_fg,
|
7236
|
-
"select_bg": self.PAR.ops.table_editor_select_bg,
|
7237
|
-
"select_fg": self.PAR.ops.table_editor_select_fg,
|
7385
|
+
**get_bg_fg(self.PAR.ops),
|
7238
7386
|
"align": self.get_cell_align(r, c),
|
7239
7387
|
"r": r,
|
7240
7388
|
"c": c,
|
@@ -7366,7 +7514,7 @@ class MainTable(tk.Canvas):
|
|
7366
7514
|
self.focus_set()
|
7367
7515
|
return
|
7368
7516
|
# setting cell data with text editor value
|
7369
|
-
|
7517
|
+
value = self.text_editor.get()
|
7370
7518
|
r, c = self.text_editor.coords
|
7371
7519
|
datarn, datacn = self.datarn(r), self.datacn(c)
|
7372
7520
|
event_data = event_dict(
|
@@ -7375,30 +7523,26 @@ class MainTable(tk.Canvas):
|
|
7375
7523
|
widget=self,
|
7376
7524
|
cells_table={(datarn, datacn): self.get_cell_data(datarn, datacn)},
|
7377
7525
|
key=event.keysym,
|
7378
|
-
value=
|
7526
|
+
value=value,
|
7379
7527
|
loc=Loc(r, c),
|
7380
7528
|
row=r,
|
7381
7529
|
column=c,
|
7382
7530
|
boxes=self.get_boxes(),
|
7383
7531
|
selected=self.selected,
|
7532
|
+
data={(datarn, datacn): value},
|
7384
7533
|
)
|
7534
|
+
value, event_data = self.single_edit_run_validation(datarn, datacn, event_data)
|
7385
7535
|
edited = False
|
7386
|
-
|
7387
|
-
self.set_cell_data_undo
|
7388
|
-
|
7389
|
-
|
7390
|
-
|
7391
|
-
|
7392
|
-
|
7393
|
-
|
7394
|
-
|
7395
|
-
|
7396
|
-
text_editor_value = self.edit_validation_func(event_data)
|
7397
|
-
if text_editor_value is not None and self.input_valid_for_cell(datarn, datacn, text_editor_value):
|
7398
|
-
edited = set_data(value=text_editor_value)
|
7399
|
-
elif self.input_valid_for_cell(datarn, datacn, text_editor_value):
|
7400
|
-
edited = set_data(value=text_editor_value)
|
7401
|
-
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
|
+
):
|
7402
7546
|
try_binding(self.extra_end_edit_cell_func, event_data)
|
7403
7547
|
if (
|
7404
7548
|
r is not None
|
@@ -7407,48 +7551,55 @@ class MainTable(tk.Canvas):
|
|
7407
7551
|
and r == self.selected.row
|
7408
7552
|
and c == self.selected.column
|
7409
7553
|
and (self.single_selection_enabled or self.toggle_selection_enabled)
|
7410
|
-
and (edited or self.cell_equal_to(datarn, datacn,
|
7554
|
+
and (edited or self.cell_equal_to(datarn, datacn, value))
|
7411
7555
|
):
|
7412
|
-
|
7413
|
-
numcols = c2 - c1
|
7414
|
-
numrows = r2 - r1
|
7415
|
-
if numcols == 1 and numrows == 1:
|
7416
|
-
if event.keysym == "Return":
|
7417
|
-
if self.PAR.ops.edit_cell_return == "right":
|
7418
|
-
self.select_right(r, c)
|
7419
|
-
if self.PAR.ops.edit_cell_return == "down":
|
7420
|
-
self.select_down(r, c)
|
7421
|
-
elif event.keysym == "Tab":
|
7422
|
-
if self.PAR.ops.edit_cell_tab == "right":
|
7423
|
-
self.select_right(r, c)
|
7424
|
-
if self.PAR.ops.edit_cell_tab == "down":
|
7425
|
-
self.select_down(r, c)
|
7426
|
-
else:
|
7427
|
-
if event.keysym == "Return":
|
7428
|
-
if self.PAR.ops.edit_cell_return == "right":
|
7429
|
-
new_r, new_c = cell_right_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7430
|
-
elif self.PAR.ops.edit_cell_return == "down":
|
7431
|
-
new_r, new_c = down_cell_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7432
|
-
else:
|
7433
|
-
new_r, new_c = None, None
|
7434
|
-
elif event.keysym == "Tab":
|
7435
|
-
if self.PAR.ops.edit_cell_tab == "right":
|
7436
|
-
new_r, new_c = cell_right_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7437
|
-
elif self.PAR.ops.edit_cell_tab == "down":
|
7438
|
-
new_r, new_c = down_cell_within_box(r, c, r1, c1, r2, c2, numrows, numcols)
|
7439
|
-
else:
|
7440
|
-
new_r, new_c = None, None
|
7441
|
-
else:
|
7442
|
-
new_r, new_c = None, None
|
7443
|
-
if isinstance(new_r, int):
|
7444
|
-
self.set_currently_selected(new_r, new_c, item=self.selected.fill_iid)
|
7445
|
-
self.see(new_r, new_c)
|
7556
|
+
self.go_to_next_cell(r, c, event.keysym)
|
7446
7557
|
self.recreate_all_selection_boxes()
|
7447
7558
|
self.hide_text_editor_and_dropdown()
|
7448
7559
|
if event.keysym != "FocusOut":
|
7449
7560
|
self.focus_set()
|
7450
7561
|
return "break"
|
7451
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
|
+
|
7452
7603
|
def select_right(self, r: int, c: int) -> None:
|
7453
7604
|
self.select_cell(r, c + 1 if c < len(self.col_positions) - 2 else c)
|
7454
7605
|
self.see(
|
@@ -7659,13 +7810,19 @@ class MainTable(tk.Canvas):
|
|
7659
7810
|
column=c,
|
7660
7811
|
boxes=self.get_boxes(),
|
7661
7812
|
selected=self.selected,
|
7813
|
+
data={(datarn, datacn): selection},
|
7662
7814
|
)
|
7663
7815
|
try_binding(kwargs["select_function"], event_data)
|
7664
|
-
selection =
|
7665
|
-
if selection is not None
|
7666
|
-
|
7667
|
-
|
7668
|
-
|
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)
|
7669
7826
|
self.recreate_all_selection_boxes()
|
7670
7827
|
self.focus_set()
|
7671
7828
|
self.hide_text_editor_and_dropdown(redraw=redraw)
|
@@ -7737,6 +7894,7 @@ class MainTable(tk.Canvas):
|
|
7737
7894
|
column=c,
|
7738
7895
|
boxes=self.get_boxes(),
|
7739
7896
|
selected=self.selected,
|
7897
|
+
data={(datarn, datacn): value},
|
7740
7898
|
)
|
7741
7899
|
if kwargs["check_function"] is not None:
|
7742
7900
|
kwargs["check_function"](event_data)
|
@@ -7931,7 +8089,10 @@ class MainTable(tk.Canvas):
|
|
7931
8089
|
kwargs = self.get_cell_kwargs(datarn, datacn, key="checkbox")
|
7932
8090
|
if kwargs:
|
7933
8091
|
return f"{kwargs['text']}"
|
7934
|
-
|
8092
|
+
try:
|
8093
|
+
value = self.data[datarn][datacn]
|
8094
|
+
except Exception:
|
8095
|
+
value = ""
|
7935
8096
|
kwargs = self.get_cell_kwargs(datarn, datacn, key="format")
|
7936
8097
|
if kwargs:
|
7937
8098
|
if kwargs["formatter"] is None:
|
@@ -7946,28 +8107,24 @@ class MainTable(tk.Canvas):
|
|
7946
8107
|
else:
|
7947
8108
|
# assumed given formatter class has get_data_with_valid_check()
|
7948
8109
|
return f"{value.get_data_with_valid_check()}"
|
7949
|
-
|
8110
|
+
else:
|
8111
|
+
return "" if value is None else value if isinstance(value, str) else f"{value}"
|
7950
8112
|
|
7951
8113
|
def get_cell_data(
|
7952
8114
|
self,
|
7953
8115
|
datarn: int,
|
7954
8116
|
datacn: int,
|
7955
|
-
get_displayed: bool = False,
|
7956
8117
|
none_to_empty_str: bool = False,
|
7957
8118
|
fmt_kw: dict | None = None,
|
7958
|
-
**kwargs,
|
7959
8119
|
) -> Any:
|
7960
|
-
|
7961
|
-
|
7962
|
-
|
7963
|
-
self.
|
7964
|
-
if len(self.data) > datarn and len(self.data[datarn]) > datacn
|
7965
|
-
else self.get_value_for_empty_cell(datarn, datacn)
|
7966
|
-
)
|
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)
|
7967
8124
|
kwargs = self.get_cell_kwargs(datarn, datacn, key="format")
|
7968
8125
|
if kwargs and kwargs["formatter"] is not None:
|
7969
8126
|
value = value.value # assumed given formatter class has value attribute
|
7970
|
-
if
|
8127
|
+
if fmt_kw:
|
7971
8128
|
value = format_data(value=value, **fmt_kw)
|
7972
8129
|
return "" if (value is None and none_to_empty_str) else value
|
7973
8130
|
|
@@ -7984,7 +8141,7 @@ class MainTable(tk.Canvas):
|
|
7984
8141
|
return False
|
7985
8142
|
elif "format" in kwargs:
|
7986
8143
|
return True
|
7987
|
-
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 (
|
7988
8145
|
(dropdown := kwargs.get("dropdown", {})) and dropdown["validate_input"] and value not in dropdown["values"]
|
7989
8146
|
):
|
7990
8147
|
return False
|
@@ -7993,22 +8150,20 @@ class MainTable(tk.Canvas):
|
|
7993
8150
|
else:
|
7994
8151
|
return True
|
7995
8152
|
|
7996
|
-
def cell_equal_to(
|
7997
|
-
|
7998
|
-
|
7999
|
-
|
8000
|
-
|
8001
|
-
|
8002
|
-
|
8003
|
-
|
8004
|
-
|
8005
|
-
|
8006
|
-
|
8007
|
-
|
8008
|
-
|
8009
|
-
|
8010
|
-
return False
|
8011
|
-
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)
|
8012
8167
|
|
8013
8168
|
def get_cell_clipboard(self, datarn: int, datacn: int) -> str | int | float | bool:
|
8014
8169
|
value = self.data[datarn][datacn] if len(self.data) > datarn and len(self.data[datarn]) > datacn else ""
|