tksheet 7.1.5__py3-none-any.whl → 7.1.7__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 CHANGED
@@ -1,96 +1,99 @@
1
- # ruff: noqa: F401
2
-
3
- """
4
- tksheet - A Python tkinter table widget
5
- """
6
-
7
- __version__ = "7.1.5"
8
-
9
- from .colors import (
10
- color_map,
11
- )
12
- from .column_headers import ColumnHeaders
13
- from .formatters import (
14
- Formatter,
15
- bool_formatter,
16
- data_to_str,
17
- float_formatter,
18
- float_to_str,
19
- format_data,
20
- formatter,
21
- get_clipboard_data,
22
- get_data_with_valid_check,
23
- int_formatter,
24
- is_bool_like,
25
- is_none_like,
26
- percentage_formatter,
27
- percentage_to_str,
28
- to_bool,
29
- to_float,
30
- to_int,
31
- to_str,
32
- try_to_bool,
33
- )
34
- from .functions import (
35
- alpha2idx,
36
- alpha2num,
37
- consecutive_chunks,
38
- data_to_displayed_idxs,
39
- displayed_to_data_idxs,
40
- dropdown_search_function,
41
- event_dict,
42
- get_checkbox_dict,
43
- get_checkbox_kwargs,
44
- get_checkbox_points,
45
- get_dropdown_dict,
46
- get_dropdown_kwargs,
47
- get_index_of_gap_in_sorted_integer_seq_forward,
48
- get_index_of_gap_in_sorted_integer_seq_reverse,
49
- get_n2a,
50
- get_new_indexes,
51
- get_seq_without_gaps_at_index,
52
- is_iterable,
53
- move_elements_by_mapping,
54
- move_elements_to,
55
- num2alpha,
56
- span_dict,
57
- tksheet_type_error,
58
- )
59
- from .main_table import MainTable
60
- from .other_classes import (
61
- DotDict,
62
- DraggedRowColumn,
63
- DrawnItem,
64
- EventDataDict,
65
- GeneratedMouseEvent,
66
- Selected,
67
- Span,
68
- SpanRange,
69
- TextCfg,
70
- )
71
- from .row_index import RowIndex
72
- from .sheet import Dropdown, Sheet
73
- from .sheet_options import new_sheet_options
74
- from .text_editor import (
75
- TextEditor,
76
- TextEditorTkText,
77
- )
78
- from .themes import (
79
- theme_black,
80
- theme_dark,
81
- theme_dark_blue,
82
- theme_dark_green,
83
- theme_light_blue,
84
- theme_light_green,
85
- )
86
- from .top_left_rectangle import TopLeftRectangle
87
- from .vars import (
88
- USER_OS,
89
- ctrl_key,
90
- emitted_events,
91
- falsy,
92
- nonelike,
93
- rc_binding,
94
- symbols_set,
95
- truthy,
96
- )
1
+ # ruff: noqa: F401
2
+
3
+ """
4
+ tksheet - A Python tkinter table widget
5
+ """
6
+
7
+ __version__ = "7.1.7"
8
+
9
+ from .colors import (
10
+ color_map,
11
+ )
12
+ from .column_headers import ColumnHeaders
13
+ from .formatters import (
14
+ Formatter,
15
+ bool_formatter,
16
+ data_to_str,
17
+ float_formatter,
18
+ float_to_str,
19
+ format_data,
20
+ formatter,
21
+ get_clipboard_data,
22
+ get_data_with_valid_check,
23
+ int_formatter,
24
+ is_bool_like,
25
+ is_none_like,
26
+ percentage_formatter,
27
+ percentage_to_str,
28
+ to_bool,
29
+ to_float,
30
+ to_int,
31
+ to_str,
32
+ try_to_bool,
33
+ )
34
+ from .functions import (
35
+ alpha2idx,
36
+ alpha2num,
37
+ consecutive_chunks,
38
+ data_to_displayed_idxs,
39
+ displayed_to_data_idxs,
40
+ dropdown_search_function,
41
+ event_dict,
42
+ get_checkbox_dict,
43
+ get_checkbox_kwargs,
44
+ rounded_box_coords,
45
+ get_dropdown_dict,
46
+ get_dropdown_kwargs,
47
+ get_index_of_gap_in_sorted_integer_seq_forward,
48
+ get_index_of_gap_in_sorted_integer_seq_reverse,
49
+ get_n2a,
50
+ get_new_indexes,
51
+ get_seq_without_gaps_at_index,
52
+ is_iterable,
53
+ move_elements_by_mapping,
54
+ move_elements_to,
55
+ num2alpha,
56
+ span_dict,
57
+ tksheet_type_error,
58
+ )
59
+ from .listbox import (
60
+ ListBox,
61
+ )
62
+ from .main_table import MainTable
63
+ from .other_classes import (
64
+ DotDict,
65
+ DraggedRowColumn,
66
+ DrawnItem,
67
+ EventDataDict,
68
+ GeneratedMouseEvent,
69
+ Selected,
70
+ Span,
71
+ SpanRange,
72
+ TextCfg,
73
+ )
74
+ from .row_index import RowIndex
75
+ from .sheet import Dropdown, Sheet
76
+ from .sheet_options import new_sheet_options
77
+ from .text_editor import (
78
+ TextEditor,
79
+ TextEditorTkText,
80
+ )
81
+ from .themes import (
82
+ theme_black,
83
+ theme_dark,
84
+ theme_dark_blue,
85
+ theme_dark_green,
86
+ theme_light_blue,
87
+ theme_light_green,
88
+ )
89
+ from .top_left_rectangle import TopLeftRectangle
90
+ from .vars import (
91
+ USER_OS,
92
+ ctrl_key,
93
+ emitted_events,
94
+ falsy,
95
+ nonelike,
96
+ rc_binding,
97
+ symbols_set,
98
+ truthy,
99
+ )
tksheet/column_headers.py CHANGED
@@ -13,16 +13,17 @@ from itertools import (
13
13
  islice,
14
14
  )
15
15
  from math import ceil, floor
16
+ from typing import Literal
16
17
 
17
18
  from .colors import (
18
19
  color_map,
19
20
  )
20
21
  from .formatters import is_bool_like, try_to_bool
21
22
  from .functions import (
22
- consecutive_chunks,
23
+ consecutive_ranges,
23
24
  ev_stack_dict,
24
25
  event_dict,
25
- get_checkbox_points,
26
+ rounded_box_coords,
26
27
  get_n2a,
27
28
  is_contiguous,
28
29
  try_binding,
@@ -687,10 +688,10 @@ class ColumnHeaders(tk.Canvas):
687
688
  tag="move_columns",
688
689
  )
689
690
  self.MT.create_resize_line(xpos, y1, xpos, y2, width=3, fill=self.PAR.ops.drag_and_drop_bg, tag="move_columns")
690
- for chunk in consecutive_chunks(cols):
691
+ for boxst, boxend in consecutive_ranges(cols):
691
692
  self.MT.show_ctrl_outline(
692
- start_cell=(chunk[0], 0),
693
- end_cell=(chunk[-1] + 1, len(self.MT.row_positions) - 1),
693
+ start_cell=(boxst, 0),
694
+ end_cell=(boxend, len(self.MT.row_positions) - 1),
694
695
  dash=(),
695
696
  outline=self.PAR.ops.drag_and_drop_bg,
696
697
  delete_on_timer=False,
@@ -934,14 +935,26 @@ class ColumnHeaders(tk.Canvas):
934
935
  outline: str,
935
936
  state: str,
936
937
  tags: str | tuple[str],
938
+ iid: None | int = None,
937
939
  ) -> int:
938
- if self.hidd_boxes:
939
- iid = self.hidd_boxes.pop()
940
- self.coords(iid, x1, y1, x2, y2)
940
+ coords = rounded_box_coords(
941
+ x1,
942
+ y1,
943
+ x2,
944
+ y2,
945
+ radius=9 if self.PAR.ops.rounded_boxes else 0,
946
+ )
947
+ if isinstance(iid, int):
948
+ self.coords(iid, coords)
941
949
  self.itemconfig(iid, fill=fill, outline=outline, state=state, tags=tags)
942
950
  else:
943
- iid = self.create_rectangle(x1, y1, x2, y2, fill=fill, outline=outline, state=state, tags=tags)
944
- self.disp_boxes.add(iid)
951
+ if self.hidd_boxes:
952
+ iid = self.hidd_boxes.pop()
953
+ self.coords(iid, coords)
954
+ self.itemconfig(iid, fill=fill, outline=outline, state=state, tags=tags)
955
+ else:
956
+ iid = self.create_polygon(coords, fill=fill, outline=outline, state=state, tags=tags, smooth=True)
957
+ self.disp_boxes.add(iid)
945
958
  return iid
946
959
 
947
960
  def hide_box(self, item: int) -> None:
@@ -1275,7 +1288,7 @@ class ColumnHeaders(tk.Canvas):
1275
1288
  self.disp_dropdown[t] = True
1276
1289
 
1277
1290
  def redraw_checkbox(self, x1, y1, x2, y2, fill, outline, tag, draw_check=False):
1278
- points = get_checkbox_points(x1, y1, x2, y2)
1291
+ points = rounded_box_coords(x1, y1, x2, y2)
1279
1292
  if self.hidd_checkbox:
1280
1293
  t, sh = self.hidd_checkbox.popitem()
1281
1294
  self.coords(t, points)
@@ -1293,7 +1306,7 @@ class ColumnHeaders(tk.Canvas):
1293
1306
  y1 = y1 + 4
1294
1307
  x2 = x2 - 3
1295
1308
  y2 = y2 - 3
1296
- points = get_checkbox_points(x1, y1, x2, y2, radius=4)
1309
+ points = rounded_box_coords(x1, y1, x2, y2, radius=4)
1297
1310
  if self.hidd_checkbox:
1298
1311
  t, sh = self.hidd_checkbox.popitem()
1299
1312
  self.coords(t, points)
@@ -1573,16 +1586,14 @@ class ColumnHeaders(tk.Canvas):
1573
1586
  dct[iid] = False
1574
1587
  return True
1575
1588
 
1576
- def get_redraw_selections(self, startc, endc):
1577
- d = defaultdict(list)
1589
+ def get_redraw_selections(self, startc: int, endc: int) -> dict[str, set[int]]:
1590
+ d = defaultdict(set)
1578
1591
  for item, box in self.MT.get_selection_items(rows=False):
1579
- d[box.type_].append(box.coords)
1580
- d2 = {}
1581
- if "cells" in d:
1582
- d2["cells"] = {c for c in range(startc, endc) for r1, c1, r2, c2 in d["cells"] if c1 <= c and c2 > c}
1583
- if "columns" in d:
1584
- d2["columns"] = {c for c in range(startc, endc) for r1, c1, r2, c2 in d["columns"] if c1 <= c and c2 > c}
1585
- return d2
1592
+ r1, c1, r2, c2 = box.coords
1593
+ for c in range(startc, endc):
1594
+ if c1 <= c and c2 > c:
1595
+ d[box.type_].add(c)
1596
+ return d
1586
1597
 
1587
1598
  def open_cell(self, event: object = None, ignore_existing_editor=False):
1588
1599
  if not self.MT.anything_selected() or (not ignore_existing_editor and self.text_editor.open):
@@ -1622,14 +1633,13 @@ class ColumnHeaders(tk.Canvas):
1622
1633
  ) -> bool:
1623
1634
  text = None
1624
1635
  extra_func_key = "??"
1625
- datacn = c if self.MT.all_columns_displayed else self.MT.displayed_columns[c]
1626
1636
  if event is None or self.MT.event_opens_dropdown_or_checkbox(event):
1627
1637
  if event is not None:
1628
1638
  if hasattr(event, "keysym") and event.keysym == "Return":
1629
1639
  extra_func_key = "Return"
1630
1640
  elif hasattr(event, "keysym") and event.keysym == "F2":
1631
1641
  extra_func_key = "F2"
1632
- text = self.get_cell_data(datacn, none_to_empty_str=True, redirect_int=True)
1642
+ text = self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)
1633
1643
  elif event is not None and (
1634
1644
  (hasattr(event, "keysym") and event.keysym == "BackSpace") or event.keycode in (8, 855638143)
1635
1645
  ):
@@ -1679,9 +1689,8 @@ class ColumnHeaders(tk.Canvas):
1679
1689
  y = 0
1680
1690
  w = self.MT.col_positions[c + 1] - x
1681
1691
  h = self.current_height + 1
1682
- datacn = c if self.MT.all_columns_displayed else self.MT.displayed_columns[c]
1683
1692
  if text is None:
1684
- text = self.get_cell_data(datacn, none_to_empty_str=True, redirect_int=True)
1693
+ text = self.get_cell_data(self.MT.datacn(c), none_to_empty_str=True, redirect_int=True)
1685
1694
  bg, fg = self.PAR.ops.header_bg, self.PAR.ops.header_fg
1686
1695
  kwargs = {
1687
1696
  "menu_kwargs": DotDict(
@@ -1754,7 +1763,7 @@ class ColumnHeaders(tk.Canvas):
1754
1763
  not check_lines
1755
1764
  or self.MT.get_lines_cell_height(
1756
1765
  self.text_editor.window.get_num_lines() + 1,
1757
- font=self.PAR.ops.header_font,
1766
+ font=self.text_editor.tktext.cget("font"),
1758
1767
  )
1759
1768
  > curr_height
1760
1769
  ):
@@ -1774,41 +1783,52 @@ class ColumnHeaders(tk.Canvas):
1774
1783
  )
1775
1784
  self.itemconfig(self.dropdown.canvas_id, anchor=anchor, height=win_h)
1776
1785
 
1777
- def refresh_open_window_positions(self):
1786
+ def refresh_open_window_positions(self, zoom: Literal["in", "out"]):
1778
1787
  if self.text_editor.open:
1779
1788
  c = self.text_editor.column
1780
- self.text_editor.window.config(height=self.MT.col_positions[c + 1] - self.MT.col_positions[c])
1789
+ self.text_editor.window.config(
1790
+ height=self.current_height,
1791
+ width=self.MT.col_positions[c + 1] - self.MT.col_positions[c] + 1,
1792
+ )
1793
+ self.text_editor.tktext.config(font=self.PAR.ops.header_font)
1781
1794
  self.coords(
1782
1795
  self.text_editor.canvas_id,
1783
- 0,
1784
1796
  self.MT.col_positions[c],
1797
+ 0,
1785
1798
  )
1786
1799
  if self.dropdown.open:
1800
+ if zoom == "in":
1801
+ self.dropdown.window.zoom_in()
1802
+ elif zoom == "out":
1803
+ self.dropdown.window.zoom_out()
1787
1804
  c = self.dropdown.get_coords()
1788
1805
  if self.text_editor.open:
1789
1806
  text_editor_h = self.text_editor.window.winfo_height()
1790
1807
  win_h, anchor = self.get_dropdown_height_anchor(c, text_editor_h)
1791
1808
  else:
1792
- text_editor_h = self.MT.col_positions[c + 1] - self.MT.col_positions[c]
1809
+ text_editor_h = self.current_height
1793
1810
  anchor = self.itemcget(self.dropdown.canvas_id, "anchor")
1794
1811
  # win_h = 0
1812
+ self.dropdown.window.config(width=self.MT.col_positions[c + 1] - self.MT.col_positions[c] + 1)
1795
1813
  if anchor == "nw":
1796
1814
  self.coords(
1797
1815
  self.dropdown.canvas_id,
1798
- 0,
1799
- self.MT.col_positions[c] + text_editor_h - 1,
1816
+ self.MT.col_positions[c],
1817
+ text_editor_h - 1,
1800
1818
  )
1801
1819
  # self.itemconfig(self.dropdown.canvas_id, anchor=anchor, height=win_h)
1802
1820
  elif anchor == "sw":
1803
1821
  self.coords(
1804
1822
  self.dropdown.canvas_id,
1805
- 0,
1806
1823
  self.MT.col_positions[c],
1824
+ 0,
1807
1825
  )
1808
1826
  # self.itemconfig(self.dropdown.canvas_id, anchor=anchor, height=win_h)
1809
1827
 
1810
1828
  def hide_text_editor(self, reason: None | str = None) -> None:
1811
1829
  if self.text_editor.open:
1830
+ for b in ("<Alt-Return>", "<Option-Return>", "<Tab>", "<Return>", "<FocusOut>", "<Escape>"):
1831
+ self.text_editor.tktext.unbind(b)
1812
1832
  self.itemconfig(self.text_editor.canvas_id, state="hidden")
1813
1833
  self.text_editor.open = False
1814
1834
  if reason == "Escape":
@@ -1832,7 +1852,7 @@ class ColumnHeaders(tk.Canvas):
1832
1852
  self.hide_text_editor_and_dropdown()
1833
1853
  return
1834
1854
  # setting cell data with text editor value
1835
- self.text_editor_value = self.text_editor.get()
1855
+ text_editor_value = self.text_editor.get()
1836
1856
  c = editor_info[0]
1837
1857
  datacn = c if self.MT.all_columns_displayed else self.MT.displayed_columns[c]
1838
1858
  event_data = event_dict(
@@ -1840,7 +1860,7 @@ class ColumnHeaders(tk.Canvas):
1840
1860
  sheet=self.PAR.name,
1841
1861
  cells_header={datacn: self.get_cell_data(datacn)},
1842
1862
  key=editor_info[1] if len(editor_info) >= 2 else "FocusOut",
1843
- value=self.text_editor_value,
1863
+ value=text_editor_value,
1844
1864
  loc=c,
1845
1865
  boxes=self.MT.get_boxes(),
1846
1866
  selected=self.MT.selected,
@@ -1853,11 +1873,11 @@ class ColumnHeaders(tk.Canvas):
1853
1873
  check_input_valid=False,
1854
1874
  )
1855
1875
  if self.MT.edit_validation_func:
1856
- self.text_editor_value = self.MT.edit_validation_func(event_data)
1857
- if self.text_editor_value is not None and self.input_valid_for_cell(datacn, self.text_editor_value):
1858
- edited = set_data(value=self.text_editor_value)
1859
- elif self.input_valid_for_cell(datacn, self.text_editor_value):
1860
- edited = set_data(value=self.text_editor_value)
1876
+ text_editor_value = self.MT.edit_validation_func(event_data)
1877
+ if text_editor_value is not None and self.input_valid_for_cell(datacn, text_editor_value):
1878
+ edited = set_data(value=text_editor_value)
1879
+ elif self.input_valid_for_cell(datacn, text_editor_value):
1880
+ edited = set_data(value=text_editor_value)
1861
1881
  if edited:
1862
1882
  try_binding(self.extra_end_edit_cell_func, event_data)
1863
1883
  self.MT.recreate_all_selection_boxes()
@@ -1901,11 +1921,9 @@ class ColumnHeaders(tk.Canvas):
1901
1921
  modified_func(event)
1902
1922
  dd_window.search_and_see(event)
1903
1923
 
1904
- def open_dropdown_window(self, c, datacn=None, event: object = None):
1924
+ def open_dropdown_window(self, c, event: object = None):
1905
1925
  self.hide_text_editor("Escape")
1906
- if datacn is None:
1907
- datacn = c if self.MT.all_columns_displayed else self.MT.displayed_columns[c]
1908
- kwargs = self.get_cell_kwargs(datacn, key="dropdown")
1926
+ kwargs = self.get_cell_kwargs(self.MT.datacn(c), key="dropdown")
1909
1927
  if kwargs["state"] == "normal":
1910
1928
  if not self.open_text_editor(event=event, c=c, dropdown=True):
1911
1929
  return
@@ -2031,6 +2049,7 @@ class ColumnHeaders(tk.Canvas):
2031
2049
 
2032
2050
  def hide_dropdown_window(self) -> None:
2033
2051
  if self.dropdown.open:
2052
+ self.dropdown.window.unbind("<FocusOut>")
2034
2053
  self.itemconfig(self.dropdown.canvas_id, state="hidden")
2035
2054
  self.dropdown.open = False
2036
2055
 
tksheet/functions.py CHANGED
@@ -245,6 +245,12 @@ def is_iterable(o: object) -> bool:
245
245
  return False
246
246
 
247
247
 
248
+ def int_x_iter(i: Iterator[int] | int) -> Iterator[int]:
249
+ if isinstance(i, int):
250
+ return (i,)
251
+ return i
252
+
253
+
248
254
  def unpack(t: tuple[object] | tuple[Iterator[object]]) -> tuple[object]:
249
255
  if not len(t):
250
256
  return t
@@ -345,21 +351,27 @@ def get_seq_without_gaps_at_index(
345
351
  return seq
346
352
 
347
353
 
348
- def consecutive_chunks(seq: list[object]) -> Generator[object]:
349
- if not seq:
350
- yield seq
354
+ def consecutive_chunks(seq: list[int]) -> Generator[list[int]]:
351
355
  start = 0
352
- end = 0
353
- for index, value in enumerate(seq):
354
- if index < len(seq) - 1:
355
- if seq[index + 1] > value + 1:
356
- end = index + 1
357
- yield seq[start:end]
358
- start = end
359
- else:
356
+ for index, value in enumerate(seq, 1):
357
+ try:
358
+ if seq[index] > value + 1:
359
+ yield seq[start : (start := index)]
360
+ except Exception:
360
361
  yield seq[start : len(seq)]
361
362
 
362
363
 
364
+ def consecutive_ranges(seq: Sequence[int]) -> Generator[tuple[int, int]]:
365
+ start = 0
366
+ for index, value in enumerate(seq, 1):
367
+ try:
368
+ if seq[index] > value + 1:
369
+ yield seq[start], seq[index - 1] + 1
370
+ start = index
371
+ except Exception:
372
+ yield seq[start], seq[-1] + 1
373
+
374
+
363
375
  def is_contiguous(seq: list[int]) -> bool:
364
376
  itr = iter(seq)
365
377
  prev = next(itr)
@@ -492,14 +504,14 @@ def displayed_to_data_idxs(
492
504
  return [displayed[e] for e in to_convert]
493
505
 
494
506
 
495
- def get_checkbox_points(
507
+ def rounded_box_coords(
496
508
  x1: float,
497
509
  y1: float,
498
510
  x2: float,
499
511
  y2: float,
500
512
  radius: int = 8,
501
- ) -> list[float]:
502
- return [
513
+ ) -> tuple[float]:
514
+ return (
503
515
  x1 + radius,
504
516
  y1,
505
517
  x1 + radius,
@@ -540,7 +552,7 @@ def get_checkbox_points(
540
552
  y1 + radius,
541
553
  x1,
542
554
  y1,
543
- ]
555
+ )
544
556
 
545
557
 
546
558
  def diff_list(seq: list[float]) -> list[int]:
tksheet/listbox.py ADDED
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+ import tkinter as tk
3
+ # from collections.abc import Callable, Generator, Iterator, Sequence
4
+
5
+ from .sheet import (
6
+ Sheet,
7
+ )
8
+
9
+
10
+ class ListBox(Sheet):
11
+ def __init__(
12
+ self,
13
+ parent: tk.Misc,
14
+ ) -> None:
15
+ Sheet.__init__(
16
+ self,
17
+ parent=parent,
18
+ )
19
+ self.parent = parent