tksheet 7.4.6__tar.gz → 7.4.7__tar.gz

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.
Files changed (26) hide show
  1. {tksheet-7.4.6/tksheet.egg-info → tksheet-7.4.7}/PKG-INFO +1 -1
  2. {tksheet-7.4.6 → tksheet-7.4.7}/pyproject.toml +1 -1
  3. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/__init__.py +2 -1
  4. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/functions.py +168 -266
  5. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/main_table.py +55 -24
  6. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/sheet.py +15 -8
  7. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/sheet_options.py +3 -1
  8. {tksheet-7.4.6 → tksheet-7.4.7/tksheet.egg-info}/PKG-INFO +1 -1
  9. {tksheet-7.4.6 → tksheet-7.4.7}/LICENSE.txt +0 -0
  10. {tksheet-7.4.6 → tksheet-7.4.7}/README.md +0 -0
  11. {tksheet-7.4.6 → tksheet-7.4.7}/setup.cfg +0 -0
  12. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/colors.py +0 -0
  13. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/column_headers.py +0 -0
  14. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/constants.py +0 -0
  15. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/find_window.py +0 -0
  16. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/formatters.py +0 -0
  17. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/other_classes.py +0 -0
  18. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/row_index.py +0 -0
  19. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/sorting.py +0 -0
  20. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/text_editor.py +0 -0
  21. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/themes.py +0 -0
  22. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/tksheet_types.py +0 -0
  23. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet/top_left_rectangle.py +0 -0
  24. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet.egg-info/SOURCES.txt +0 -0
  25. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet.egg-info/dependency_links.txt +0 -0
  26. {tksheet-7.4.6 → tksheet-7.4.7}/tksheet.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.6
3
+ Version: 7.4.7
4
4
  Summary: Tkinter table / sheet and treeview widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "tksheet"
7
7
  description = "Tkinter table / sheet and treeview widget"
8
8
  readme = "README.md"
9
- version = "7.4.6"
9
+ version = "7.4.7"
10
10
  authors = [{ name = "ragardner", email = "github@ragardner.simplelogin.com" }]
11
11
  requires-python = ">=3.8"
12
12
  license = {file = "LICENSE.txt"}
@@ -4,7 +4,7 @@
4
4
  tksheet - A Python tkinter table widget
5
5
  """
6
6
 
7
- __version__ = "7.4.6"
7
+ __version__ = "7.4.7"
8
8
 
9
9
  from .colors import (
10
10
  color_map,
@@ -73,6 +73,7 @@ from .functions import (
73
73
  move_elements_to,
74
74
  new_tk_event,
75
75
  num2alpha,
76
+ push_n,
76
77
  rounded_box_coords,
77
78
  span_dict,
78
79
  tksheet_type_error,
@@ -20,6 +20,7 @@ from .tksheet_types import AnyIter
20
20
 
21
21
  unpickle_obj = pickle.loads
22
22
  lines_re = re.compile(r"[^\n]+")
23
+ ORD_A = ord("A")
23
24
 
24
25
 
25
26
  def wrap_text(
@@ -517,9 +518,8 @@ def force_bool(o: Any) -> bool:
517
518
  def alpha2idx(a: str) -> int | None:
518
519
  try:
519
520
  n = 0
520
- orda = ord("A")
521
521
  for c in a.upper():
522
- n = n * 26 + ord(c) - orda + 1
522
+ n = n * 26 + ord(c) - ORD_A + 1
523
523
  return n - 1
524
524
  except Exception:
525
525
  return None
@@ -1108,6 +1108,37 @@ def coords_to_span(
1108
1108
  )
1109
1109
 
1110
1110
 
1111
+ PATTERN_ROW = re.compile(r"^(\d+)$") # "1"
1112
+ PATTERN_COL = re.compile(r"^([A-Z]+)$") # "A"
1113
+ PATTERN_CELL = re.compile(r"^([A-Z]+)(\d+)$") # "A1"
1114
+ PATTERN_RANGE = re.compile(r"^([A-Z]+)(\d+):([A-Z]+)(\d+)$") # "A1:B2"
1115
+ PATTERN_ROW_RANGE = re.compile(r"^(\d+):(\d+)$") # "1:2"
1116
+ PATTERN_ROW_START = re.compile(r"^(\d+):$") # "2:"
1117
+ PATTERN_ROW_END = re.compile(r"^:(\d+)$") # ":2"
1118
+ PATTERN_COL_RANGE = re.compile(r"^([A-Z]+):([A-Z]+)$") # "A:B"
1119
+ PATTERN_COL_START = re.compile(r"^([A-Z]+):$") # "A:"
1120
+ PATTERN_COL_END = re.compile(r"^:([A-Z]+)$") # ":B"
1121
+ PATTERN_CELL_START = re.compile(r"^([A-Z]+)(\d+):$") # "A1:"
1122
+ PATTERN_CELL_END = re.compile(r"^:([A-Z]+)(\d+)$") # ":B1"
1123
+ PATTERN_CELL_COL = re.compile(r"^([A-Z]+)(\d+):([A-Z]+)$") # "A1:B"
1124
+ PATTERN_CELL_ROW = re.compile(r"^([A-Z]+)(\d+):(\d+)$") # "A1:2"
1125
+ PATTERN_ALL = re.compile(r"^:$") # ":"
1126
+
1127
+
1128
+ def span_a2i(a: str) -> int | None:
1129
+ n = 0
1130
+ for c in a:
1131
+ n = n * 26 + ord(c) - ORD_A + 1
1132
+ return n - 1
1133
+
1134
+
1135
+ def span_a2n(a: str) -> int | None:
1136
+ n = 0
1137
+ for c in a:
1138
+ n = n * 26 + ord(c) - ORD_A + 1
1139
+ return n
1140
+
1141
+
1111
1142
  def key_to_span(
1112
1143
  key: (
1113
1144
  str
@@ -1120,76 +1151,32 @@ def key_to_span(
1120
1151
  spans: dict[str, Span],
1121
1152
  widget: Any = None,
1122
1153
  ) -> Span:
1154
+ """
1155
+ Convert various input types to a Span object representing a 2D range.
1156
+
1157
+ Args:
1158
+ key: The input to convert (str, int, slice, sequence, or None).
1159
+ spans: A dictionary of named spans (e.g., {"<name>": Span(...)}).
1160
+ widget: Optional widget context for span creation.
1161
+
1162
+ Returns:
1163
+ A Span object or an error message string if the key is invalid.
1164
+ """
1165
+ # Handle Span object directly
1123
1166
  if isinstance(key, Span):
1124
1167
  return key
1168
+
1169
+ # Handle None as full span
1125
1170
  elif key is None:
1126
- key = (None, None, None, None)
1171
+ return coords_to_span(widget=widget, from_r=None, from_c=None, upto_r=None, upto_c=None)
1172
+
1173
+ # Validate input type
1127
1174
  elif not isinstance(key, (str, int, slice, list, tuple)):
1128
- return f"Key type must be either str, int, list, tuple or slice, not '{type(key)}'."
1175
+ return f"Key type must be either str, int, list, tuple or slice, not '{type(key).__name__}'."
1176
+
1129
1177
  try:
1130
- if isinstance(key, (list, tuple)):
1131
- if isinstance(key[0], int) or key[0] is None:
1132
- if len(key) == 2:
1133
- """
1134
- (int | None, int | None) -
1135
- (0, 0) - row 0, column 0 - the first cell
1136
- (0, None) - row 0, all columns
1137
- (None, 0) - column 0, all rows
1138
- """
1139
- return span_dict(
1140
- from_r=key[0] if isinstance(key[0], int) else 0,
1141
- from_c=key[1] if isinstance(key[1], int) else 0,
1142
- upto_r=(key[0] + 1) if isinstance(key[0], int) else None,
1143
- upto_c=(key[1] + 1) if isinstance(key[1], int) else None,
1144
- widget=widget,
1145
- )
1146
-
1147
- elif len(key) == 4:
1148
- """
1149
- (int | None, int | None, int | None, int | None) -
1150
- (from row, from column, up to row, up to column)
1151
- """
1152
- return coords_to_span(
1153
- widget=widget,
1154
- from_r=key[0],
1155
- from_c=key[1],
1156
- upto_r=key[2],
1157
- upto_c=key[3],
1158
- )
1159
- # return span_dict(
1160
- # from_r=key[0] if isinstance(key[0], int) else 0,
1161
- # from_c=key[1] if isinstance(key[1], int) else 0,
1162
- # upto_r=key[2] if isinstance(key[2], int) else None,
1163
- # upto_c=key[3] if isinstance(key[3], int) else None,
1164
- # widget=widget,
1165
- # )
1166
-
1167
- elif isinstance(key[0], (list, tuple)):
1168
- """
1169
- ((int | None, int | None), (int | None, int | None))
1170
-
1171
- First Sequence is start row and column
1172
- Second Sequence is up to but not including row and column
1173
- """
1174
- return coords_to_span(
1175
- widget=widget,
1176
- from_r=key[0][0],
1177
- from_c=key[0][1],
1178
- upto_r=key[1][0],
1179
- upto_c=key[1][1],
1180
- )
1181
- # return span_dict(
1182
- # from_r=key[0][0] if isinstance(key[0][0], int) else 0,
1183
- # from_c=key[0][1] if isinstance(key[0][1], int) else 0,
1184
- # upto_r=key[1][0],
1185
- # upto_c=key[1][1],
1186
- # widget=widget,
1187
- # )
1188
-
1189
- elif isinstance(key, int):
1190
- """
1191
- [int] - Whole row at that index
1192
- """
1178
+ # Integer key: whole row
1179
+ if isinstance(key, int):
1193
1180
  return span_dict(
1194
1181
  from_r=key,
1195
1182
  from_c=None,
@@ -1198,29 +1185,8 @@ def key_to_span(
1198
1185
  widget=widget,
1199
1186
  )
1200
1187
 
1188
+ # Slice key: row range
1201
1189
  elif isinstance(key, slice):
1202
- """
1203
- [slice]
1204
- """
1205
- """
1206
- [:] - All rows
1207
- """
1208
- if key.start is None and key.stop is None:
1209
- """
1210
- [:]
1211
- """
1212
- return span_dict(
1213
- from_r=0,
1214
- from_c=None,
1215
- upto_r=None,
1216
- upto_c=None,
1217
- widget=widget,
1218
- )
1219
- """
1220
- [1:3] - Rows 1, 2
1221
- [:2] - Rows up to but not including 2
1222
- [2:] - Rows starting from and including 2
1223
- """
1224
1190
  start = 0 if key.start is None else key.start
1225
1191
  return span_dict(
1226
1192
  from_r=start,
@@ -1230,251 +1196,187 @@ def key_to_span(
1230
1196
  widget=widget,
1231
1197
  )
1232
1198
 
1199
+ # Sequence key: various span formats
1200
+ elif isinstance(key, (list, tuple)):
1201
+ if (
1202
+ len(key) == 2
1203
+ and (isinstance(key[0], int) or key[0] is None)
1204
+ and (isinstance(key[1], int) or key[1] is None)
1205
+ ):
1206
+ # Single cell or partial span: (row, col)
1207
+ r_int = isinstance(key[0], int)
1208
+ c_int = isinstance(key[1], int)
1209
+ return span_dict(
1210
+ from_r=key[0] if r_int else 0,
1211
+ from_c=key[1] if c_int else 0,
1212
+ upto_r=key[0] + 1 if r_int else None,
1213
+ upto_c=key[1] + 1 if c_int else None,
1214
+ widget=widget,
1215
+ )
1216
+ elif len(key) == 4:
1217
+ # Full span coordinates: (from_r, from_c, upto_r, upto_c)
1218
+ return coords_to_span(
1219
+ widget=widget,
1220
+ from_r=key[0],
1221
+ from_c=key[1],
1222
+ upto_r=key[2],
1223
+ upto_c=key[3],
1224
+ )
1225
+ elif len(key) == 2 and all(isinstance(k, (list, tuple)) for k in key):
1226
+ # Start and end points: ((from_r, from_c), (upto_r, upto_c))
1227
+ return coords_to_span(
1228
+ widget=widget,
1229
+ from_r=key[0][0],
1230
+ from_c=key[0][1],
1231
+ upto_r=key[1][0],
1232
+ upto_c=key[1][1],
1233
+ )
1234
+
1235
+ # String key: parse various span formats
1233
1236
  elif isinstance(key, str):
1234
1237
  if not key:
1235
- key = ":"
1236
-
1237
- if key.startswith("<") and key.endswith(">"):
1238
- if (key := key[1:-1]) in spans:
1239
- """
1240
- ["<name>"] - Surrounded by "<" ">" cells from a named range
1241
- """
1242
- return spans[key]
1243
- return f"'{key}' not in named spans."
1244
-
1245
- key = key.upper()
1246
-
1247
- if key.isdigit():
1248
- """
1249
- ["1"] - Row 0
1250
- """
1238
+ # Empty string treated as full span
1251
1239
  return span_dict(
1252
- from_r=int(key) - 1,
1240
+ from_r=0,
1253
1241
  from_c=None,
1254
- upto_r=int(key),
1242
+ upto_r=None,
1255
1243
  upto_c=None,
1256
1244
  widget=widget,
1257
1245
  )
1246
+ elif key.startswith("<") and key.endswith(">"):
1247
+ name = key[1:-1]
1248
+ return spans.get(name, f"'{name}' not in named spans.")
1258
1249
 
1259
- if key.isalpha():
1260
- """
1261
- ["A"] - Column 0
1262
- """
1250
+ key = key.upper() # Case-insensitive parsing
1251
+
1252
+ # Match string against precompiled patterns
1253
+ if m := PATTERN_ROW.match(key):
1254
+ return span_dict(
1255
+ from_r=int(m[1]) - 1,
1256
+ from_c=None,
1257
+ upto_r=int(m[1]),
1258
+ upto_c=None,
1259
+ widget=widget,
1260
+ )
1261
+ elif m := PATTERN_COL.match(key):
1263
1262
  return span_dict(
1264
1263
  from_r=None,
1265
- from_c=alpha2idx(key),
1264
+ from_c=span_a2i(m[1]),
1266
1265
  upto_r=None,
1267
- upto_c=alpha2idx(key) + 1,
1266
+ upto_c=span_a2n(m[1]),
1268
1267
  widget=widget,
1269
1268
  )
1270
-
1271
- splitk = key.split(":")
1272
- if len(splitk) > 2:
1273
- return f"'{key}' could not be converted to span."
1274
-
1275
- if len(splitk) == 1 and not splitk[0].isdigit() and not splitk[0].isalpha() and not splitk[0][0].isdigit():
1276
- """
1277
- ["A1"] - Cell (0, 0)
1278
- """
1279
- keys_digits = re.search(r"\d", splitk[0])
1280
- if keys_digits:
1281
- digits_start = keys_digits.start()
1282
- if not digits_start:
1283
- return f"'{key}' could not be converted to span."
1284
- if digits_start:
1285
- key_row = splitk[0][digits_start:]
1286
- key_column = splitk[0][:digits_start]
1287
- return span_dict(
1288
- from_r=int(key_row) - 1,
1289
- from_c=alpha2idx(key_column),
1290
- upto_r=int(key_row),
1291
- upto_c=alpha2idx(key_column) + 1,
1292
- widget=widget,
1293
- )
1294
-
1295
- if not splitk[0] and not splitk[1]:
1296
- """
1297
- [":"] - All rows
1298
- """
1269
+ elif m := PATTERN_CELL.match(key):
1270
+ c = span_a2i(m[1])
1271
+ r = int(m[2]) - 1
1299
1272
  return span_dict(
1300
- from_r=0,
1301
- from_c=None,
1302
- upto_r=None,
1303
- upto_c=None,
1273
+ from_r=r,
1274
+ from_c=c,
1275
+ upto_r=r + 1,
1276
+ upto_c=c + 1,
1304
1277
  widget=widget,
1305
1278
  )
1306
-
1307
- if splitk[0].isdigit() and not splitk[1]:
1308
- """
1309
- ["2:"] - Rows starting from and including 1
1310
- """
1279
+ elif m := PATTERN_RANGE.match(key):
1280
+ return span_dict(
1281
+ from_r=int(m[2]) - 1,
1282
+ from_c=span_a2i(m[1]),
1283
+ upto_r=int(m[4]),
1284
+ upto_c=span_a2n(m[3]),
1285
+ widget=widget,
1286
+ )
1287
+ elif m := PATTERN_ROW_RANGE.match(key):
1311
1288
  return span_dict(
1312
- from_r=int(splitk[0]) - 1,
1289
+ from_r=int(m[1]) - 1,
1313
1290
  from_c=None,
1314
- upto_r=None,
1291
+ upto_r=int(m[2]),
1315
1292
  upto_c=None,
1316
1293
  widget=widget,
1317
1294
  )
1318
-
1319
- if splitk[1].isdigit() and not splitk[0]:
1320
- """
1321
- [":2"] - Rows up to and including 1
1322
- """
1295
+ elif m := PATTERN_ROW_START.match(key):
1323
1296
  return span_dict(
1324
- from_r=0,
1297
+ from_r=int(m[1]) - 1,
1325
1298
  from_c=None,
1326
- upto_r=int(splitk[1]),
1299
+ upto_r=None,
1327
1300
  upto_c=None,
1328
1301
  widget=widget,
1329
1302
  )
1330
-
1331
- if splitk[0].isdigit() and splitk[1].isdigit():
1332
- """
1333
- ["1:2"] - Rows 0, 1
1334
- """
1303
+ elif m := PATTERN_ROW_END.match(key):
1335
1304
  return span_dict(
1336
- from_r=int(splitk[0]) - 1,
1305
+ from_r=0,
1337
1306
  from_c=None,
1338
- upto_r=int(splitk[1]),
1307
+ upto_r=int(m[1]),
1339
1308
  upto_c=None,
1340
1309
  widget=widget,
1341
1310
  )
1342
-
1343
- if splitk[0].isalpha() and not splitk[1]:
1344
- """
1345
- ["B:"] - Columns starting from and including 2
1346
- """
1311
+ elif m := PATTERN_COL_RANGE.match(key):
1347
1312
  return span_dict(
1348
1313
  from_r=None,
1349
- from_c=alpha2idx(splitk[0]),
1314
+ from_c=span_a2i(m[1]),
1350
1315
  upto_r=None,
1351
- upto_c=None,
1316
+ upto_c=span_a2n(m[2]),
1352
1317
  widget=widget,
1353
1318
  )
1354
-
1355
- if splitk[1].isalpha() and not splitk[0]:
1356
- """
1357
- [":B"] - Columns up to and including 2
1358
- """
1319
+ elif m := PATTERN_COL_START.match(key):
1359
1320
  return span_dict(
1360
1321
  from_r=None,
1361
- from_c=0,
1322
+ from_c=span_a2i(m[1]),
1362
1323
  upto_r=None,
1363
- upto_c=alpha2idx(splitk[1]) + 1,
1324
+ upto_c=None,
1364
1325
  widget=widget,
1365
1326
  )
1366
-
1367
- if splitk[0].isalpha() and splitk[1].isalpha():
1368
- """
1369
- ["A:B"] - Columns 0, 1
1370
- """
1327
+ elif m := PATTERN_COL_END.match(key):
1371
1328
  return span_dict(
1372
1329
  from_r=None,
1373
- from_c=alpha2idx(splitk[0]),
1330
+ from_c=0,
1374
1331
  upto_r=None,
1375
- upto_c=alpha2idx(splitk[1]) + 1,
1332
+ upto_c=span_a2n(m[1]),
1376
1333
  widget=widget,
1377
1334
  )
1378
-
1379
- m1 = re.search(r"\d", splitk[0])
1380
- m2 = re.search(r"\d", splitk[1])
1381
- m1start = m1.start() if m1 else None
1382
- m2start = m2.start() if m2 else None
1383
- if m1start and m2start:
1384
- """
1385
- ["A1:B1"] - Cells (0, 0), (0, 1)
1386
- """
1387
- c1 = splitk[0][:m1start]
1388
- r1 = splitk[0][m1start:]
1389
- c2 = splitk[1][:m2start]
1390
- r2 = splitk[1][m2start:]
1335
+ elif m := PATTERN_CELL_START.match(key):
1391
1336
  return span_dict(
1392
- from_r=int(r1) - 1,
1393
- from_c=alpha2idx(c1),
1394
- upto_r=int(r2),
1395
- upto_c=alpha2idx(c2) + 1,
1337
+ from_r=int(m[2]) - 1,
1338
+ from_c=span_a2i(m[1]),
1339
+ upto_r=None,
1340
+ upto_c=None,
1396
1341
  widget=widget,
1397
1342
  )
1398
-
1399
- if not splitk[0] and m2start:
1400
- """
1401
- [":B1"] - Cells (0, 0), (0, 1)
1402
- """
1403
- c2 = splitk[1][:m2start]
1404
- r2 = splitk[1][m2start:]
1343
+ elif m := PATTERN_CELL_END.match(key):
1405
1344
  return span_dict(
1406
1345
  from_r=0,
1407
1346
  from_c=0,
1408
- upto_r=int(r2),
1409
- upto_c=alpha2idx(c2) + 1,
1347
+ upto_r=int(m[2]),
1348
+ upto_c=span_a2n(m[1]),
1410
1349
  widget=widget,
1411
1350
  )
1412
-
1413
- if not splitk[1] and m1start:
1414
- """
1415
- ["A1:"] - Cells starting from and including (0, 0)
1416
- """
1417
- c1 = splitk[0][:m1start]
1418
- r1 = splitk[0][m1start:]
1351
+ elif m := PATTERN_CELL_COL.match(key):
1419
1352
  return span_dict(
1420
- from_r=int(r1) - 1,
1421
- from_c=alpha2idx(c1),
1353
+ from_r=int(m[2]) - 1,
1354
+ from_c=span_a2i(m[1]),
1422
1355
  upto_r=None,
1423
- upto_c=None,
1356
+ upto_c=span_a2n(m[3]),
1424
1357
  widget=widget,
1425
1358
  )
1426
-
1427
- if m1start and splitk[1].isalpha():
1428
- """
1429
- ["A1:B"] - All the cells starting from (0, 0)
1430
- expanding out to include column 1
1431
- but not including cells beyond column
1432
- 1 and expanding down to include all rows
1433
- A B C D
1434
- 1 x x
1435
- 2 x x
1436
- 3 x x
1437
- 4 x x
1438
- ...
1439
- """
1440
- c1 = splitk[0][:m1start]
1441
- r1 = splitk[0][m1start:]
1359
+ elif m := PATTERN_CELL_ROW.match(key):
1442
1360
  return span_dict(
1443
- from_r=int(r1) - 1,
1444
- from_c=alpha2idx(c1),
1445
- upto_r=None,
1446
- upto_c=alpha2idx(splitk[1]) + 1,
1361
+ from_r=int(m[2]) - 1,
1362
+ from_c=span_a2i(m[1]),
1363
+ upto_r=int(m[3]),
1364
+ upto_c=None,
1447
1365
  widget=widget,
1448
1366
  )
1449
-
1450
- if m1start and splitk[1].isdigit():
1451
- """
1452
- ["A1:2"] - All the cells starting from (0, 0)
1453
- expanding down to include row 1
1454
- but not including cells beyond row
1455
- 1 and expanding out to include all
1456
- columns
1457
- A B C D
1458
- 1 x x x x
1459
- 2 x x x x
1460
- 3
1461
- 4
1462
- ...
1463
- """
1464
- c1 = splitk[0][:m1start]
1465
- r1 = splitk[0][m1start:]
1367
+ elif PATTERN_ALL.match(key):
1466
1368
  return span_dict(
1467
- from_r=int(r1) - 1,
1468
- from_c=alpha2idx(c1),
1469
- upto_r=int(splitk[1]),
1369
+ from_r=0,
1370
+ from_c=None,
1371
+ upto_r=None,
1470
1372
  upto_c=None,
1471
1373
  widget=widget,
1472
1374
  )
1375
+ else:
1376
+ return f"'{key}' could not be converted to span."
1473
1377
 
1474
1378
  except ValueError as error:
1475
1379
  return f"Error, '{key}' could not be converted to span: {error}"
1476
- else:
1477
- return f"'{key}' could not be converted to span."
1478
1380
 
1479
1381
 
1480
1382
  def span_is_cell(span: Span) -> bool:
@@ -555,16 +555,31 @@ class MainTable(tk.Canvas):
555
555
  return coords
556
556
 
557
557
  def find_match(self, find: str, r: int, c: int) -> bool:
558
- return (
559
- not find
560
- and (not self.get_valid_cell_data_as_str(r, c, True).lower() or not f"{self.get_cell_data(r, c)}".lower())
561
- ) or (
562
- find
563
- and (
564
- find in self.get_valid_cell_data_as_str(r, c, True).lower()
565
- or find in f"{self.get_cell_data(r, c)}".lower()
566
- )
567
- )
558
+ try:
559
+ value = self.data[r][c]
560
+ except Exception:
561
+ value = ""
562
+ kwargs = self.get_cell_kwargs(r, c, key=None)
563
+ if kwargs:
564
+ if "dropdown" in kwargs:
565
+ kwargs = kwargs["dropdown"]
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
579
+ if value is None:
580
+ return find == ""
581
+ else:
582
+ return find in str(value).lower()
568
583
 
569
584
  def find_within_match(self, find: str, r: int, c: int) -> bool:
570
585
  if not self.all_rows_displayed:
@@ -669,9 +684,9 @@ class MainTable(tk.Canvas):
669
684
  reverse=reverse,
670
685
  )
671
686
  if (
672
- (self.all_rows_displayed or bisect_in(self.displayed_rows, r))
687
+ self.find_match(find, r, c)
688
+ and (self.all_rows_displayed or bisect_in(self.displayed_rows, r))
673
689
  and (self.all_columns_displayed or bisect_in(self.displayed_columns, c))
674
- and self.find_match(find, r, c)
675
690
  )
676
691
  ),
677
692
  None,
@@ -2039,8 +2054,10 @@ class MainTable(tk.Canvas):
2039
2054
  redraw: bool = True,
2040
2055
  r_pc: float = 0.0,
2041
2056
  c_pc: float = 0.0,
2057
+ index: bool = True,
2042
2058
  ) -> bool:
2043
- need_redraw = False
2059
+ need_y_redraw = False
2060
+ need_x_redraw = False
2044
2061
  vis_info = self.cell_visibility_info(r, c)
2045
2062
  yvis, xvis = vis_info["yvis"], vis_info["xvis"]
2046
2063
  top_left_x, top_left_y, bottom_right_x, bottom_right_y = vis_info["visible_region"]
@@ -2067,7 +2084,7 @@ class MainTable(tk.Canvas):
2067
2084
  y - 1 if y > 1 else y,
2068
2085
  ]
2069
2086
  self.set_yviews(*args, redraw=False)
2070
- need_redraw = True
2087
+ need_y_redraw = True
2071
2088
  else:
2072
2089
  if r is not None and not keep_yscroll:
2073
2090
  y = max(
@@ -2079,7 +2096,7 @@ class MainTable(tk.Canvas):
2079
2096
  y - 1 if y > 1 else y,
2080
2097
  ]
2081
2098
  self.set_yviews(*args, redraw=False)
2082
- need_redraw = True
2099
+ need_y_redraw = True
2083
2100
  # x scroll
2084
2101
  if not check_cell_visibility or (check_cell_visibility and not xvis) and len(self.col_positions) > 1:
2085
2102
  if bottom_right_corner is None:
@@ -2102,7 +2119,7 @@ class MainTable(tk.Canvas):
2102
2119
  x - 1 if x > 1 else x,
2103
2120
  ]
2104
2121
  self.set_xviews(*args, redraw=False)
2105
- need_redraw = True
2122
+ need_x_redraw = True
2106
2123
  else:
2107
2124
  if c is not None and not keep_xscroll:
2108
2125
  x = max(
@@ -2114,8 +2131,22 @@ class MainTable(tk.Canvas):
2114
2131
  x - 1 if x > 1 else x,
2115
2132
  ]
2116
2133
  self.set_xviews(*args, redraw=False)
2117
- need_redraw = True
2118
- if redraw and need_redraw:
2134
+ need_x_redraw = True
2135
+ # the index may have resized after scrolling making x calculation wrong
2136
+ if need_x_redraw and index and self.PAR.ops.auto_resize_row_index and self.show_index:
2137
+ self.main_table_redraw_grid_and_text(redraw_header=False, redraw_row_index=False, redraw_table=False)
2138
+ self.see(
2139
+ r=r,
2140
+ c=c,
2141
+ keep_yscroll=keep_yscroll,
2142
+ keep_xscroll=keep_xscroll,
2143
+ check_cell_visibility=check_cell_visibility,
2144
+ redraw=redraw,
2145
+ r_pc=r_pc,
2146
+ c_pc=c_pc,
2147
+ index=False,
2148
+ )
2149
+ if redraw and (need_y_redraw or need_x_redraw):
2119
2150
  self.main_table_redraw_grid_and_text(redraw_header=True, redraw_row_index=True)
2120
2151
  return True
2121
2152
  return False
@@ -2534,7 +2565,7 @@ class MainTable(tk.Canvas):
2534
2565
  ):
2535
2566
  self.select_cell(r - 1, c, redraw=True)
2536
2567
 
2537
- def arrowkey_LEFT(self, event: Any = None) -> None:
2568
+ def arrowkey_LEFT(self, event: Any = None) -> Literal["break"]:
2538
2569
  if not self.selected:
2539
2570
  return
2540
2571
  r = self.selected.row
@@ -2550,6 +2581,7 @@ class MainTable(tk.Canvas):
2550
2581
  self.single_selection_enabled or self.toggle_selection_enabled
2551
2582
  ):
2552
2583
  self.select_cell(r, c - 1, redraw=True)
2584
+ return "break"
2553
2585
 
2554
2586
  def arrowkey_DOWN(self, event: Any = None) -> None:
2555
2587
  if not self.selected:
@@ -5572,8 +5604,7 @@ class MainTable(tk.Canvas):
5572
5604
 
5573
5605
  def total_data_cols(self, include_header: bool = True) -> int:
5574
5606
  h_total = len(self._headers) if include_header and isinstance(self._headers, (list, tuple)) else 0
5575
- # map() for some reason is 15% faster than max(key=len) using python 3.11 windows 11
5576
- d_total = max(map(len, self.data), default=0)
5607
+ d_total = max(map(len, self.data), default=0) # max(map(len, )) is faster
5577
5608
  return max(h_total, d_total)
5578
5609
 
5579
5610
  def total_data_rows(self, include_index: bool = True) -> int:
@@ -6115,7 +6146,7 @@ class MainTable(tk.Canvas):
6115
6146
 
6116
6147
  # check if auto resizing row index
6117
6148
  changed_w = False
6118
- if self.PAR.ops.auto_resize_row_index and redraw_row_index and self.show_index:
6149
+ if self.PAR.ops.auto_resize_row_index and self.show_index:
6119
6150
  changed_w = self.RI.auto_set_index_width(
6120
6151
  end_row=grid_end_row,
6121
6152
  only_rows=map(self.datarn, range(text_start_row, text_end_row)),
@@ -7653,12 +7684,12 @@ class MainTable(tk.Canvas):
7653
7684
  self.focus_set()
7654
7685
  return closed_dd_coords
7655
7686
 
7656
- def mouseclick_outside_editor_or_dropdown_all_canvases(self):
7687
+ def mouseclick_outside_editor_or_dropdown_all_canvases(self) -> tuple[int, int] | None:
7657
7688
  self.CH.mouseclick_outside_editor_or_dropdown()
7658
7689
  self.RI.mouseclick_outside_editor_or_dropdown()
7659
7690
  return self.mouseclick_outside_editor_or_dropdown()
7660
7691
 
7661
- def hide_dropdown_editor_all_canvases(self):
7692
+ def hide_dropdown_editor_all_canvases(self) -> None:
7662
7693
  self.hide_text_editor_and_dropdown(redraw=False)
7663
7694
  self.RI.hide_text_editor_and_dropdown(redraw=False)
7664
7695
  self.CH.hide_text_editor_and_dropdown(redraw=False)
@@ -5238,14 +5238,6 @@ class Sheet(tk.Frame):
5238
5238
  """
5239
5239
  if item not in self.RI.tree:
5240
5240
  raise ValueError(f"Item '{item}' does not exist.")
5241
- if isinstance(iid, str):
5242
- if iid in self.RI.tree:
5243
- raise ValueError(f"Cannot rename '{iid}', it already exists.")
5244
- self.RI.tree[item].iid = iid
5245
- self.RI.tree[iid] = self.RI.tree.pop(item)
5246
- self.RI.tree_rns[iid] = self.RI.tree_rns.pop(item)
5247
- if iid in self.RI.tree_open_ids:
5248
- self.RI.tree_open_ids[iid] = self.RI.tree_open_ids.pop(item)
5249
5241
  if isinstance(text, str):
5250
5242
  self.RI.tree[item].text = text
5251
5243
  if isinstance(values, list):
@@ -5273,6 +5265,21 @@ class Sheet(tk.Frame):
5273
5265
  )
5274
5266
  else:
5275
5267
  self.RI.tree_open_ids.discard(item)
5268
+ if isinstance(iid, str):
5269
+ if iid in self.RI.tree:
5270
+ raise ValueError(f"Cannot rename '{iid}', it already exists.")
5271
+ for ciid in self.RI.tree[item].children:
5272
+ self.RI.tree[ciid].parent = iid
5273
+ if self.RI.tree[item].parent:
5274
+ parent_node = self.RI.parent_node(item)
5275
+ item_index = parent_node.children.index(item)
5276
+ parent_node.children[item_index] = iid
5277
+ self.RI.tree[item].iid = iid
5278
+ self.RI.tree[iid] = self.RI.tree.pop(item)
5279
+ self.RI.tree_rns[iid] = self.RI.tree_rns.pop(item)
5280
+ if item in self.RI.tree_open_ids:
5281
+ self.RI.tree_open_ids.discard(item)
5282
+ self.RI.tree_open_ids.add(iid)
5276
5283
  get = not (isinstance(iid, str) or isinstance(text, str) or isinstance(values, list) or isinstance(open_, bool))
5277
5284
  self.set_refresh_timer(redraw=not get and redraw)
5278
5285
  if get:
@@ -144,7 +144,9 @@ def new_sheet_options() -> DotDict:
144
144
  "row_start_bindings": [
145
145
  "<Command-Left>",
146
146
  "<Home>",
147
- ],
147
+ ]
148
+ if USER_OS == "darwin"
149
+ else ["<Home>"],
148
150
  "table_start_bindings": [
149
151
  f"<{ctrl_key}-Home>",
150
152
  ],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.6
3
+ Version: 7.4.7
4
4
  Summary: Tkinter table / sheet and treeview widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes