pgwidgets-python 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pgwidgets/async_/application.py +101 -2
- pgwidgets/async_/widget.py +436 -2
- pgwidgets/method_types.py +165 -8
- pgwidgets/sync/application.py +126 -2
- pgwidgets/sync/widget.py +437 -2
- pgwidgets/tree_model.py +356 -0
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/METADATA +3 -4
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/RECORD +11 -12
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/WHEEL +1 -1
- pgwidgets_python-0.3.4.dist-info/scm_file_list.json +0 -71
- pgwidgets_python-0.3.4.dist-info/scm_version.json +0 -8
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/licenses/LICENSE.md +0 -0
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/top_level.txt +0 -0
pgwidgets/sync/widget.py
CHANGED
|
@@ -11,6 +11,7 @@ import base64
|
|
|
11
11
|
import mimetypes
|
|
12
12
|
import os
|
|
13
13
|
|
|
14
|
+
from pgwidgets import tree_model
|
|
14
15
|
from pgwidgets.defs import WIDGETS, CALLBACK_METHODS, WIDGET_METHODS, CONTAINER_METHODS
|
|
15
16
|
from pgwidgets.method_types import (
|
|
16
17
|
classify_method, SETTER, GETTER, CHILD, ACTION, JS_ONLY,
|
|
@@ -248,6 +249,18 @@ class Widget:
|
|
|
248
249
|
"""The Session this widget belongs to."""
|
|
249
250
|
return self._session
|
|
250
251
|
|
|
252
|
+
def batch(self):
|
|
253
|
+
"""Convenience for ``widget.session.batch()``.
|
|
254
|
+
|
|
255
|
+
Batching is per session, not per widget, so updates to other
|
|
256
|
+
widgets made inside the block ride along in the same message::
|
|
257
|
+
|
|
258
|
+
with tree.batch():
|
|
259
|
+
tree.set_cell(path, 'seeing', '1.4')
|
|
260
|
+
status.set_text('updated')
|
|
261
|
+
"""
|
|
262
|
+
return self._session.batch()
|
|
263
|
+
|
|
251
264
|
@property
|
|
252
265
|
def app(self):
|
|
253
266
|
"""The Application this widget belongs to."""
|
|
@@ -771,7 +784,8 @@ def _add_tree_view_methods(attrs, all_methods):
|
|
|
771
784
|
if path is not None:
|
|
772
785
|
key = tuple(path) if isinstance(path, list) else path
|
|
773
786
|
expanded = self._state.setdefault("_expanded_paths", set())
|
|
774
|
-
expanded
|
|
787
|
+
if expanded != "_all":
|
|
788
|
+
expanded.add(key)
|
|
775
789
|
collapsed = self._state.get("_collapsed_paths")
|
|
776
790
|
if collapsed is not None and collapsed != "_all":
|
|
777
791
|
collapsed.discard(key)
|
|
@@ -787,7 +801,7 @@ def _add_tree_view_methods(attrs, all_methods):
|
|
|
787
801
|
if path is not None:
|
|
788
802
|
key = tuple(path) if isinstance(path, list) else path
|
|
789
803
|
expanded = self._state.get("_expanded_paths")
|
|
790
|
-
if expanded is not None:
|
|
804
|
+
if expanded is not None and expanded != "_all":
|
|
791
805
|
expanded.discard(key)
|
|
792
806
|
collapsed = self._state.setdefault("_collapsed_paths", set())
|
|
793
807
|
if collapsed != "_all":
|
|
@@ -812,6 +826,427 @@ def _add_tree_view_methods(attrs, all_methods):
|
|
|
812
826
|
collapse_all_method.__name__ = "collapse_all"
|
|
813
827
|
attrs["collapse_all"] = collapse_all_method
|
|
814
828
|
|
|
829
|
+
_add_tree_model_methods(attrs, all_methods)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def _pad(args, n):
|
|
833
|
+
"""Pad a call's positional args out to `n` with None, so a partially
|
|
834
|
+
specified call still lands in the right state slots."""
|
|
835
|
+
args = list(args)
|
|
836
|
+
while len(args) < n:
|
|
837
|
+
args.append(None)
|
|
838
|
+
return tuple(args)
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _style_path(path):
|
|
842
|
+
"""Hashable form of a path, for use as a style-map key."""
|
|
843
|
+
return tuple(path) if isinstance(path, list) else path
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _model_rows(widget):
|
|
847
|
+
"""The flat row model in _state, whichever bulk setter filled it."""
|
|
848
|
+
for key in ("rows", "data"):
|
|
849
|
+
rows = widget._state.get(key)
|
|
850
|
+
if isinstance(rows, list):
|
|
851
|
+
return rows
|
|
852
|
+
return None
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def _drop_row_styles(widget):
|
|
856
|
+
"""The JS clear() that precedes a bulk load empties the per-cell and
|
|
857
|
+
per-row style maps but keeps the column / table layers."""
|
|
858
|
+
widget._state.pop("_cell_styles", None)
|
|
859
|
+
widget._state.pop("_row_styles", None)
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def _add_tree_model_methods(attrs, all_methods):
|
|
863
|
+
"""Override the tree/table mutators so they maintain the Python-side
|
|
864
|
+
model (see :mod:`pgwidgets.tree_model`).
|
|
865
|
+
|
|
866
|
+
Python is the source of truth: the browser can be rebuilt at any time
|
|
867
|
+
from ``_state``, so every mutation has to be reflected there or it is
|
|
868
|
+
lost on the next reconnect. The bulk setters additionally deep-copy,
|
|
869
|
+
so a caller that keeps editing the structure it passed in cannot
|
|
870
|
+
corrupt the model behind our back.
|
|
871
|
+
"""
|
|
872
|
+
# --- bulk setters: store a private copy, reset row-level styles ---
|
|
873
|
+
for name, state_key in (("set_tree", "tree"), ("set_data", "data"),
|
|
874
|
+
("set_rows", "rows")):
|
|
875
|
+
if name not in all_methods:
|
|
876
|
+
continue
|
|
877
|
+
|
|
878
|
+
def make_bulk(mn, key, pn):
|
|
879
|
+
def method(self, *args, **kwargs):
|
|
880
|
+
args = _resolve_kwargs(mn, pn, args, kwargs)
|
|
881
|
+
self._state[key] = tree_model.copy_tree(
|
|
882
|
+
args[0] if args else None)
|
|
883
|
+
_drop_row_styles(self)
|
|
884
|
+
return self._call(mn, *args)
|
|
885
|
+
method.__name__ = mn
|
|
886
|
+
return method
|
|
887
|
+
attrs[name] = make_bulk(name, state_key, all_methods[name])
|
|
888
|
+
|
|
889
|
+
# --- per-cell writes fold into the model ---
|
|
890
|
+
if "set_cell" in all_methods:
|
|
891
|
+
param_names = all_methods["set_cell"]
|
|
892
|
+
|
|
893
|
+
def set_cell_method(self, *args, **kwargs):
|
|
894
|
+
args = _resolve_kwargs("set_cell", param_names, args, kwargs)
|
|
895
|
+
if len(args) >= 3:
|
|
896
|
+
where, col, value = args[0], args[1], args[2]
|
|
897
|
+
tree = self._state.get("tree")
|
|
898
|
+
if isinstance(tree, dict):
|
|
899
|
+
tree_model.set_cell(tree, where, col, value)
|
|
900
|
+
else:
|
|
901
|
+
rows = _model_rows(self)
|
|
902
|
+
if rows is not None:
|
|
903
|
+
tree_model.row_set_cell(
|
|
904
|
+
rows, self._state.get("columns"),
|
|
905
|
+
where, col, value)
|
|
906
|
+
return self._call("set_cell", *args)
|
|
907
|
+
set_cell_method.__name__ = "set_cell"
|
|
908
|
+
attrs["set_cell"] = set_cell_method
|
|
909
|
+
|
|
910
|
+
# --- structural tree edits ---
|
|
911
|
+
if "add_item" in all_methods:
|
|
912
|
+
param_names = all_methods["add_item"]
|
|
913
|
+
|
|
914
|
+
def add_item_method(self, *args, **kwargs):
|
|
915
|
+
args = _resolve_kwargs("add_item", param_names, args, kwargs)
|
|
916
|
+
tree = self._state.get("tree")
|
|
917
|
+
if isinstance(tree, dict) and len(args) >= 3:
|
|
918
|
+
tree_model.add_item(tree, args[0], args[1], args[2])
|
|
919
|
+
return self._call("add_item", *args)
|
|
920
|
+
add_item_method.__name__ = "add_item"
|
|
921
|
+
attrs["add_item"] = add_item_method
|
|
922
|
+
|
|
923
|
+
if "remove_item" in all_methods:
|
|
924
|
+
param_names = all_methods["remove_item"]
|
|
925
|
+
|
|
926
|
+
def remove_item_method(self, *args, **kwargs):
|
|
927
|
+
args = _resolve_kwargs("remove_item", param_names, args, kwargs)
|
|
928
|
+
tree = self._state.get("tree")
|
|
929
|
+
if isinstance(tree, dict) and args:
|
|
930
|
+
tree_model.remove_item(tree, args[0])
|
|
931
|
+
return self._call("remove_item", *args)
|
|
932
|
+
remove_item_method.__name__ = "remove_item"
|
|
933
|
+
attrs["remove_item"] = remove_item_method
|
|
934
|
+
|
|
935
|
+
if "remove_items" in all_methods:
|
|
936
|
+
param_names = all_methods["remove_items"]
|
|
937
|
+
|
|
938
|
+
def remove_items_method(self, *args, **kwargs):
|
|
939
|
+
args = _resolve_kwargs("remove_items", param_names, args, kwargs)
|
|
940
|
+
tree = self._state.get("tree")
|
|
941
|
+
if isinstance(tree, dict) and args:
|
|
942
|
+
tree_model.remove_items(tree, args[0])
|
|
943
|
+
return self._call("remove_items", *args)
|
|
944
|
+
remove_items_method.__name__ = "remove_items"
|
|
945
|
+
attrs["remove_items"] = remove_items_method
|
|
946
|
+
|
|
947
|
+
if "add_tree" in all_methods:
|
|
948
|
+
param_names = all_methods["add_tree"]
|
|
949
|
+
|
|
950
|
+
def add_tree_method(self, *args, **kwargs):
|
|
951
|
+
args = _resolve_kwargs("add_tree", param_names, args, kwargs)
|
|
952
|
+
args = _pad(args, 2)
|
|
953
|
+
tree = self._state.get("tree")
|
|
954
|
+
if isinstance(tree, dict):
|
|
955
|
+
tree_model.merge_tree(tree, args[0], args[1])
|
|
956
|
+
elif isinstance(args[0], dict) and args[1] in (None, [], ()):
|
|
957
|
+
# merging into an empty widget: the merge *is* the tree
|
|
958
|
+
self._state["tree"] = tree_model.copy_tree(args[0])
|
|
959
|
+
return self._call("add_tree", *args)
|
|
960
|
+
add_tree_method.__name__ = "add_tree"
|
|
961
|
+
attrs["add_tree"] = add_tree_method
|
|
962
|
+
|
|
963
|
+
if "delete_tree" in all_methods:
|
|
964
|
+
param_names = all_methods["delete_tree"]
|
|
965
|
+
|
|
966
|
+
def delete_tree_method(self, *args, **kwargs):
|
|
967
|
+
args = _resolve_kwargs("delete_tree", param_names, args, kwargs)
|
|
968
|
+
args = _pad(args, 2)
|
|
969
|
+
tree = self._state.get("tree")
|
|
970
|
+
prune = True if args[1] is None else bool(args[1])
|
|
971
|
+
if isinstance(tree, dict):
|
|
972
|
+
tree_model.delete_tree(tree, args[0], prune)
|
|
973
|
+
return self._call("delete_tree", *args)
|
|
974
|
+
delete_tree_method.__name__ = "delete_tree"
|
|
975
|
+
attrs["delete_tree"] = delete_tree_method
|
|
976
|
+
|
|
977
|
+
if "update_tree" in all_methods:
|
|
978
|
+
param_names = all_methods["update_tree"]
|
|
979
|
+
|
|
980
|
+
def update_tree_method(self, *args, **kwargs):
|
|
981
|
+
"""Bring the tree to `tree`, sending only what changed.
|
|
982
|
+
|
|
983
|
+
The browser's own ``update_tree`` is a full replacement (it
|
|
984
|
+
rebuilds every row, dropping expansion state, cell styles and
|
|
985
|
+
any open editor). Because the model here is authoritative we
|
|
986
|
+
can diff against it instead and send just the deltas. A diff
|
|
987
|
+
broader than a wholesale replacement falls back to
|
|
988
|
+
``set_tree``.
|
|
989
|
+
"""
|
|
990
|
+
args = _resolve_kwargs("update_tree", param_names, args, kwargs)
|
|
991
|
+
new_tree = args[0] if args else {}
|
|
992
|
+
old_tree = self._state.get("tree")
|
|
993
|
+
|
|
994
|
+
if not isinstance(old_tree, dict) or not isinstance(new_tree,
|
|
995
|
+
dict):
|
|
996
|
+
self._state["tree"] = tree_model.copy_tree(new_tree)
|
|
997
|
+
_drop_row_styles(self)
|
|
998
|
+
return self._call("set_tree", new_tree)
|
|
999
|
+
|
|
1000
|
+
ops = tree_model.diff_tree(old_tree, new_tree)
|
|
1001
|
+
self._state["tree"] = tree_model.copy_tree(new_tree)
|
|
1002
|
+
if ops is None:
|
|
1003
|
+
# wholesale replacement -- the browser clears, so the
|
|
1004
|
+
# row-level style layers go with it
|
|
1005
|
+
_drop_row_styles(self)
|
|
1006
|
+
return self._call("set_tree", new_tree)
|
|
1007
|
+
result = None
|
|
1008
|
+
for op in ops:
|
|
1009
|
+
result = self._call(op[0], *op[1:])
|
|
1010
|
+
return result
|
|
1011
|
+
update_tree_method.__name__ = "update_tree"
|
|
1012
|
+
attrs["update_tree"] = update_tree_method
|
|
1013
|
+
|
|
1014
|
+
# --- incremental flat-data update ---
|
|
1015
|
+
#
|
|
1016
|
+
# The browser diffs these against what it is showing, so the whole
|
|
1017
|
+
# array goes over the wire but only the rows that differ are
|
|
1018
|
+
# touched (selection, colours and scroll position survive). The
|
|
1019
|
+
# model still has to record the new contents for reconnection.
|
|
1020
|
+
for name, default_key in (("update_data", "data"),
|
|
1021
|
+
("update_rows", "rows")):
|
|
1022
|
+
if name not in all_methods:
|
|
1023
|
+
continue
|
|
1024
|
+
|
|
1025
|
+
def make_update_rows(mn, dk, pn):
|
|
1026
|
+
def method(self, *args, **kwargs):
|
|
1027
|
+
args = _resolve_kwargs(mn, pn, args, kwargs)
|
|
1028
|
+
key = dk
|
|
1029
|
+
for candidate in ("rows", "data"):
|
|
1030
|
+
if isinstance(self._state.get(candidate), list):
|
|
1031
|
+
key = candidate # keep filling whichever
|
|
1032
|
+
break # bulk setter was used
|
|
1033
|
+
self._state[key] = tree_model.copy_tree(
|
|
1034
|
+
args[0] if args else None)
|
|
1035
|
+
return self._call(mn, *args)
|
|
1036
|
+
method.__name__ = mn
|
|
1037
|
+
return method
|
|
1038
|
+
attrs[name] = make_update_rows(name, default_key, all_methods[name])
|
|
1039
|
+
|
|
1040
|
+
# --- flat row edits ---
|
|
1041
|
+
for name, fn in (("insert_row", tree_model.insert_row),
|
|
1042
|
+
("append_row", tree_model.append_row),
|
|
1043
|
+
("delete_row", tree_model.delete_row)):
|
|
1044
|
+
if name not in all_methods:
|
|
1045
|
+
continue
|
|
1046
|
+
|
|
1047
|
+
def make_row_op(mn, func, pn):
|
|
1048
|
+
def method(self, *args, **kwargs):
|
|
1049
|
+
args = _resolve_kwargs(mn, pn, args, kwargs)
|
|
1050
|
+
rows = _model_rows(self)
|
|
1051
|
+
if rows is not None:
|
|
1052
|
+
if mn == "append_row":
|
|
1053
|
+
func(rows, args[0] if args else None)
|
|
1054
|
+
elif mn == "delete_row":
|
|
1055
|
+
func(rows, args[0] if args else None)
|
|
1056
|
+
else:
|
|
1057
|
+
args2 = _pad(args, 2)
|
|
1058
|
+
func(rows, args2[0], args2[1])
|
|
1059
|
+
return self._call(mn, *args)
|
|
1060
|
+
method.__name__ = mn
|
|
1061
|
+
return method
|
|
1062
|
+
attrs[name] = make_row_op(name, fn, all_methods[name])
|
|
1063
|
+
|
|
1064
|
+
# --- column edits ---
|
|
1065
|
+
if "insert_column" in all_methods:
|
|
1066
|
+
param_names = all_methods["insert_column"]
|
|
1067
|
+
# TreeView is insert_column(column, before); TableView is
|
|
1068
|
+
# insert_column(index, column) -- tell them apart by the defn
|
|
1069
|
+
by_key = param_names and param_names[0] == "column"
|
|
1070
|
+
|
|
1071
|
+
def insert_column_method(self, *args, **kwargs):
|
|
1072
|
+
args = _resolve_kwargs("insert_column", param_names,
|
|
1073
|
+
args, kwargs)
|
|
1074
|
+
args = _pad(args, 2)
|
|
1075
|
+
columns = self._state.get("columns")
|
|
1076
|
+
if isinstance(columns, list):
|
|
1077
|
+
if by_key:
|
|
1078
|
+
tree_model.insert_column(columns, args[0],
|
|
1079
|
+
before=args[1])
|
|
1080
|
+
else:
|
|
1081
|
+
tree_model.insert_column(columns, args[1],
|
|
1082
|
+
index=args[0])
|
|
1083
|
+
return self._call("insert_column", *args)
|
|
1084
|
+
insert_column_method.__name__ = "insert_column"
|
|
1085
|
+
attrs["insert_column"] = insert_column_method
|
|
1086
|
+
|
|
1087
|
+
if "append_column" in all_methods:
|
|
1088
|
+
param_names = all_methods["append_column"]
|
|
1089
|
+
|
|
1090
|
+
def append_column_method(self, *args, **kwargs):
|
|
1091
|
+
args = _resolve_kwargs("append_column", param_names,
|
|
1092
|
+
args, kwargs)
|
|
1093
|
+
columns = self._state.get("columns")
|
|
1094
|
+
if isinstance(columns, list) and args:
|
|
1095
|
+
tree_model.append_column(columns, args[0])
|
|
1096
|
+
return self._call("append_column", *args)
|
|
1097
|
+
append_column_method.__name__ = "append_column"
|
|
1098
|
+
attrs["append_column"] = append_column_method
|
|
1099
|
+
|
|
1100
|
+
if "delete_column" in all_methods:
|
|
1101
|
+
param_names = all_methods["delete_column"]
|
|
1102
|
+
|
|
1103
|
+
def delete_column_method(self, *args, **kwargs):
|
|
1104
|
+
args = _resolve_kwargs("delete_column", param_names,
|
|
1105
|
+
args, kwargs)
|
|
1106
|
+
columns = self._state.get("columns")
|
|
1107
|
+
if isinstance(columns, list) and args:
|
|
1108
|
+
# TreeView deletes by column key, TableView by index;
|
|
1109
|
+
# tree_model.delete_column accepts either
|
|
1110
|
+
tree_model.delete_column(columns, args[0])
|
|
1111
|
+
return self._call("delete_column", *args)
|
|
1112
|
+
delete_column_method.__name__ = "delete_column"
|
|
1113
|
+
attrs["delete_column"] = delete_column_method
|
|
1114
|
+
|
|
1115
|
+
_add_colour_override_methods(attrs, all_methods)
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def _record_style(widget, state_key, map_key, call_args, nkeys):
|
|
1119
|
+
"""Record (or, when every colour channel is None, drop) one override.
|
|
1120
|
+
|
|
1121
|
+
``call_args`` is the equivalent single-call argument tuple, so the
|
|
1122
|
+
stored entry can be replayed by re-dispatch regardless of whether it
|
|
1123
|
+
arrived singly or as part of a batch.
|
|
1124
|
+
"""
|
|
1125
|
+
if all(v is None for v in call_args[nkeys:]):
|
|
1126
|
+
widget._state.get(state_key, {}).pop(map_key, None)
|
|
1127
|
+
else:
|
|
1128
|
+
widget._state.setdefault(state_key, {})[map_key] = call_args
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def _add_colour_override_methods(attrs, all_methods):
|
|
1132
|
+
"""Accumulate the per-cell / row / column / table colour overrides.
|
|
1133
|
+
|
|
1134
|
+
These have no bulk setter to fold into, so they are kept as maps in
|
|
1135
|
+
_state and replayed on top of the data during reconstruction. An
|
|
1136
|
+
all-null call clears that layer, matching the JS.
|
|
1137
|
+
"""
|
|
1138
|
+
# method, total args, state key, number of leading key args
|
|
1139
|
+
# (the remaining args are the fg / bg / bold channels)
|
|
1140
|
+
specs = [
|
|
1141
|
+
("set_cell_color", 5, "_cell_styles", 2),
|
|
1142
|
+
("set_row_color", 4, "_row_styles", 1),
|
|
1143
|
+
("set_column_color", 4, "_column_styles", 1),
|
|
1144
|
+
("set_table_color", 3, "_table_style", 0),
|
|
1145
|
+
]
|
|
1146
|
+
for name, nargs, state_key, nkeys in specs:
|
|
1147
|
+
if name not in all_methods:
|
|
1148
|
+
continue
|
|
1149
|
+
|
|
1150
|
+
def make_setter(mn, n, sk, nk, pn):
|
|
1151
|
+
def method(self, *args, **kwargs):
|
|
1152
|
+
args = _resolve_kwargs(mn, pn, args, kwargs)
|
|
1153
|
+
args = _pad(args, n)
|
|
1154
|
+
cleared = all(v is None for v in args[nk:])
|
|
1155
|
+
if nk == 0: # table-wide: one slot
|
|
1156
|
+
if cleared:
|
|
1157
|
+
self._state.pop(sk, None)
|
|
1158
|
+
else:
|
|
1159
|
+
self._state[sk] = tuple(args)
|
|
1160
|
+
else:
|
|
1161
|
+
map_key = ((_style_path(args[0]), args[1]) if nk == 2
|
|
1162
|
+
else _style_path(args[0]))
|
|
1163
|
+
if cleared:
|
|
1164
|
+
self._state.get(sk, {}).pop(map_key, None)
|
|
1165
|
+
else:
|
|
1166
|
+
# keep the whole call, so replaying is a
|
|
1167
|
+
# straight re-dispatch
|
|
1168
|
+
self._state.setdefault(sk, {})[map_key] = tuple(args)
|
|
1169
|
+
return self._call(mn, *args)
|
|
1170
|
+
method.__name__ = mn
|
|
1171
|
+
return method
|
|
1172
|
+
attrs[name] = make_setter(name, nargs, state_key, nkeys,
|
|
1173
|
+
all_methods[name])
|
|
1174
|
+
|
|
1175
|
+
clears = [
|
|
1176
|
+
("clear_cell_color", "_cell_styles",
|
|
1177
|
+
lambda a: (_style_path(a[0]), a[1])),
|
|
1178
|
+
("clear_row_color", "_row_styles", lambda a: _style_path(a[0])),
|
|
1179
|
+
("clear_column_color", "_column_styles", lambda a: a[0]),
|
|
1180
|
+
]
|
|
1181
|
+
for name, state_key, keyfn in clears:
|
|
1182
|
+
if name not in all_methods:
|
|
1183
|
+
continue
|
|
1184
|
+
|
|
1185
|
+
def make_clear_one(mn, sk, kf, pn):
|
|
1186
|
+
def method(self, *args, **kwargs):
|
|
1187
|
+
args = _resolve_kwargs(mn, pn, args, kwargs)
|
|
1188
|
+
args = _pad(args, 2)
|
|
1189
|
+
self._state.get(sk, {}).pop(kf(args), None)
|
|
1190
|
+
return self._call(mn, *args)
|
|
1191
|
+
method.__name__ = mn
|
|
1192
|
+
return method
|
|
1193
|
+
attrs[name] = make_clear_one(name, state_key, keyfn,
|
|
1194
|
+
all_methods[name])
|
|
1195
|
+
|
|
1196
|
+
if "set_colors" in all_methods:
|
|
1197
|
+
param_names = all_methods["set_colors"]
|
|
1198
|
+
|
|
1199
|
+
def set_colors_method(self, *args, **kwargs):
|
|
1200
|
+
"""Apply many colour overrides in one call.
|
|
1201
|
+
|
|
1202
|
+
Folds the batch into the same style maps the single-cell
|
|
1203
|
+
setters use, so the model stays accurate for reconnection
|
|
1204
|
+
while costing one round-trip and one browser re-render
|
|
1205
|
+
instead of one of each per cell.
|
|
1206
|
+
"""
|
|
1207
|
+
args = _resolve_kwargs("set_colors", param_names, args, kwargs)
|
|
1208
|
+
spec = args[0] if args else None
|
|
1209
|
+
if isinstance(spec, dict):
|
|
1210
|
+
if spec.get("clear"):
|
|
1211
|
+
for key in ("_cell_styles", "_row_styles",
|
|
1212
|
+
"_column_styles", "_table_style"):
|
|
1213
|
+
self._state.pop(key, None)
|
|
1214
|
+
for e in (spec.get("cells") or []):
|
|
1215
|
+
_record_style(
|
|
1216
|
+
self, "_cell_styles",
|
|
1217
|
+
(_style_path(e.get("path")), e.get("col_key")),
|
|
1218
|
+
(e.get("path"), e.get("col_key"), e.get("fg"),
|
|
1219
|
+
e.get("bg"), e.get("bold")), 2)
|
|
1220
|
+
for e in (spec.get("rows") or []):
|
|
1221
|
+
_record_style(
|
|
1222
|
+
self, "_row_styles", _style_path(e.get("path")),
|
|
1223
|
+
(e.get("path"), e.get("fg"), e.get("bg"),
|
|
1224
|
+
e.get("bold")), 1)
|
|
1225
|
+
for e in (spec.get("columns") or []):
|
|
1226
|
+
_record_style(
|
|
1227
|
+
self, "_column_styles", e.get("col_key"),
|
|
1228
|
+
(e.get("col_key"), e.get("fg"), e.get("bg"),
|
|
1229
|
+
e.get("bold")), 1)
|
|
1230
|
+
if "table" in spec:
|
|
1231
|
+
t = spec.get("table") or {}
|
|
1232
|
+
channels = (t.get("fg"), t.get("bg"), t.get("bold"))
|
|
1233
|
+
if all(v is None for v in channels):
|
|
1234
|
+
self._state.pop("_table_style", None)
|
|
1235
|
+
else:
|
|
1236
|
+
self._state["_table_style"] = channels
|
|
1237
|
+
return self._call("set_colors", *args)
|
|
1238
|
+
set_colors_method.__name__ = "set_colors"
|
|
1239
|
+
attrs["set_colors"] = set_colors_method
|
|
1240
|
+
|
|
1241
|
+
if "clear_all_colors" in all_methods:
|
|
1242
|
+
def clear_all_colors_method(self):
|
|
1243
|
+
for key in ("_cell_styles", "_row_styles", "_column_styles",
|
|
1244
|
+
"_table_style"):
|
|
1245
|
+
self._state.pop(key, None)
|
|
1246
|
+
return self._call("clear_all_colors")
|
|
1247
|
+
clear_all_colors_method.__name__ = "clear_all_colors"
|
|
1248
|
+
attrs["clear_all_colors"] = clear_all_colors_method
|
|
1249
|
+
|
|
815
1250
|
|
|
816
1251
|
def build_widget_class(js_class, defn):
|
|
817
1252
|
"""Build a synchronous stateful Widget subclass from a definition."""
|