From 403050660ea0f3d379076677cf88f48433a765d6 Mon Sep 17 00:00:00 2001 From: jgunstone Date: Wed, 11 Sep 2024 12:33:23 +0100 Subject: [PATCH 01/10] add support button to `CrudButtonBar` --- src/ipyautoui/constants.py | 7 ++ src/ipyautoui/custom/buttonbars.py | 104 ++++++++++++++++++++--------- 2 files changed, 81 insertions(+), 30 deletions(-) diff --git a/src/ipyautoui/constants.py b/src/ipyautoui/constants.py index 5c638f0..5cb3287 100644 --- a/src/ipyautoui/constants.py +++ b/src/ipyautoui/constants.py @@ -140,6 +140,13 @@ layout={"width": BUTTON_WIDTH_MIN, "height": BUTTON_HEIGHT_MIN}, disabled=True, ) +HELP_BUTTON_KWARGS = frozenmap( + icon="question", + style={}, + # button_style="primary", + tooltip="help", + layout={"width": BUTTON_WIDTH_MIN}, # , "height": BUTTON_HEIGHT_MIN +) DOWNARROW_BUTTON_KWARGS = frozenmap( icon="arrow-down", diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index 2b15f09..6f161db 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -6,7 +6,7 @@ # extension: .py # format_name: light # format_version: '1.5' -# jupytext_version: 1.15.2 +# jupytext_version: 1.16.1 # kernelspec: # display_name: Python 3 (ipykernel) # language: python @@ -14,9 +14,6 @@ # --- # + -# %run ../_dev_maplocal_params.py - - import ipywidgets as w import traitlets as tr import typing as ty @@ -28,9 +25,9 @@ COPY_BUTTON_KWARGS, DELETE_BUTTON_KWARGS, RELOAD_BUTTON_KWARGS, + HELP_BUTTON_KWARGS ) - -from IPython.display import display +from IPython.display import display, clear_output from datetime import datetime import logging from enum import Enum @@ -272,40 +269,66 @@ class CrudView(ty.TypedDict): edit: CrudOptions copy: CrudOptions delete: CrudOptions + reload: CrudOptions DEFAULT_BUTTONBAR_CONFIG = CrudView( - add=CrudOptions( + add=CrudOptions(**dict(ADD_BUTTON_KWARGS) | dict( tooltip="Add item", tooltip_clicked="Go back to table", button_style="success", message="➕ Adding Value", - ), - edit=CrudOptions( + )), + edit=CrudOptions(**dict(EDIT_BUTTON_KWARGS) | dict( tooltip="Edit item", tooltip_clicked="Go back to table", button_style="warning", message="✏️ Editing Value", - ), - copy=CrudOptions( + )), + copy=CrudOptions(**dict(COPY_BUTTON_KWARGS) | dict( tooltip="Copy item", tooltip_clicked="Go back to table", button_style="primary", message="📝 Copying Value", - ), - delete=CrudOptions( + )), + delete=CrudOptions(**dict(DELETE_BUTTON_KWARGS) | dict( tooltip="Delete item", tooltip_clicked="Go back to table", button_style="danger", message="🗑️ Deleting Value", - ), + )), + reload=CrudOptions(**dict(RELOAD_BUTTON_KWARGS) | dict( + tooltip="reload data", + tooltip_clicked="", + button_style="info", + message="♻ reloading data|", + )), + support=CrudOptions(**dict(HELP_BUTTON_KWARGS) | dict( + tooltip="help - click to show description of all buttons in the toolbar", + tooltip_clicked="hide help dialogue", + message="❔ Help Selected", + )), ) -# - - +# + +def display_ui_tooltips(uiobj): + """pass a ui object and display all items that contain tooltips with the tooltips exposed. NOT IN USE""" + li = [] + for k, v in uiobj.__dict__.items(): + try: + if "tooltip" in v.__dict__["_trait_values"]: + if v.tooltip is not None: + li.append(v) + else: + pass + except: + pass + return w.VBox( + [w.HBox([l, w.HTML(f"{l.tooltip}")]) for l in li] + ) -class CrudButtonBar(w.HBox): +class CrudButtonBar(w.VBox): # w.HBox active = tr.Unicode(default_value=None, allow_none=True) crud_view = tr.Dict(default_value=DEFAULT_BUTTONBAR_CONFIG) fn_add = tr.Callable(default_value=lambda: print("add")) @@ -313,7 +336,9 @@ class CrudButtonBar(w.HBox): fn_copy = tr.Callable(default_value=lambda: print("copy")) fn_delete = tr.Callable(default_value=lambda: print("delete")) fn_backward = tr.Callable(default_value=lambda: print("backward")) + fn_support = tr.Callable(default_value=lambda: print("support")) fn_reload = tr.Callable(default_value=None, allow_none=True) + show_support = tr.Bool(default_value=True) @tr.observe("fn_reload") def _observe_fn_reload(self, change): @@ -333,21 +358,31 @@ def active_index(self): else: return list(self.crud_view.keys()).index(self.active) + def _fn_support(self): + + with self.out: + clear_output() + display(display_ui_tooltips(self)) + + def __init__( self, **kwargs, ): self._init_form() super().__init__(**kwargs) # main container + self.fn_support = self._fn_support self.out = w.Output() - self.children = [ + self.hbx_bbar = w.HBox([ self.add, self.edit, self.copy, self.delete, self.reload, + self.support, self.message, - ] + ]) + self.children = [self.hbx_bbar, self.out] self._init_controls() def _init_form(self): @@ -357,7 +392,7 @@ def _init_form(self): self.copy = w.ToggleButton(**COPY_BUTTON_KWARGS) self.delete = w.ToggleButton(**DELETE_BUTTON_KWARGS) self.reload = w.Button(**RELOAD_BUTTON_KWARGS) - self.reload.layout.display = "None" + self.support = w.ToggleButton(**HELP_BUTTON_KWARGS) self.message = w.HTML() self._set_crud_view_options() @@ -366,24 +401,27 @@ def _init_controls(self): self.edit.observe(self._edit, "value") self.copy.observe(self._copy, "value") self.delete.observe(self._delete, "value") + self.support.observe(self._support, "value") self.reload.on_click(self._reload) def _onclick(self, button_name): - w = getattr(self, button_name) + wi = getattr(self, button_name) fn = getattr(self, ("fn_" + button_name)) - if w.value: + if wi.value: self.reset_toggles_except(button_name) self.active = button_name - w.tooltip = self.crud_view[button_name]["tooltip_clicked"] - w.layout.border = TOGGLEBUTTON_ONCLICK_BORDER_LAYOUT + wi.tooltip = self.crud_view[button_name]["tooltip_clicked"] + wi.layout.border = TOGGLEBUTTON_ONCLICK_BORDER_LAYOUT self.message.value = self.crud_view[button_name]["message"] fn() else: self.active = None - w.tooltip = self.crud_view[button_name]["tooltip"] - w.layout.border = None + wi.tooltip = self.crud_view[button_name]["tooltip"] + wi.layout.border = None self.message.value = "" self.fn_backward() + with self.out: + clear_output() def _add(self, onchange): self._onclick("add") @@ -397,6 +435,13 @@ def _copy(self, onchange): def _delete(self, onchange): self._onclick("delete") + def _support(self, onchange): + self._onclick("support") + + def _reload(self, on_click): + logger.info("Reloading all data") + self.fn_reload() + def _set_crud_view_options(self): for button_name in self.crud_view.keys(): w = getattr(self, button_name) @@ -414,10 +459,8 @@ def reset_toggles_except(self, name=None): for n in names: setattr(getattr(self, n), "value", False) - def _reload(self, on_click): - logger.info("Reloading all data") - self.fn_reload() +# - if __name__ == "__main__": @@ -444,5 +487,6 @@ def backward(): fn_backward=backward, fn_reload=lambda: print("fn_reload"), ) - display(buttonbar) + + From ae2e01da044114631d566b2c44b9e1c7b0873e91 Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 12:39:10 +0100 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=8E=A8=20Format=20code=20with=20bla?= =?UTF-8?q?ck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 122 ++++++++++++++++------------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index 6f161db..2e78e32 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -25,7 +25,7 @@ COPY_BUTTON_KWARGS, DELETE_BUTTON_KWARGS, RELOAD_BUTTON_KWARGS, - HELP_BUTTON_KWARGS + HELP_BUTTON_KWARGS, ) from IPython.display import display, clear_output from datetime import datetime @@ -273,41 +273,59 @@ class CrudView(ty.TypedDict): DEFAULT_BUTTONBAR_CONFIG = CrudView( - add=CrudOptions(**dict(ADD_BUTTON_KWARGS) | dict( - tooltip="Add item", - tooltip_clicked="Go back to table", - button_style="success", - message="➕ Adding Value", - )), - edit=CrudOptions(**dict(EDIT_BUTTON_KWARGS) | dict( - tooltip="Edit item", - tooltip_clicked="Go back to table", - button_style="warning", - message="✏️ Editing Value", - )), - copy=CrudOptions(**dict(COPY_BUTTON_KWARGS) | dict( - tooltip="Copy item", - tooltip_clicked="Go back to table", - button_style="primary", - message="📝 Copying Value", - )), - delete=CrudOptions(**dict(DELETE_BUTTON_KWARGS) | dict( - tooltip="Delete item", - tooltip_clicked="Go back to table", - button_style="danger", - message="🗑️ Deleting Value", - )), - reload=CrudOptions(**dict(RELOAD_BUTTON_KWARGS) | dict( - tooltip="reload data", - tooltip_clicked="", - button_style="info", - message="♻ reloading data|", - )), - support=CrudOptions(**dict(HELP_BUTTON_KWARGS) | dict( - tooltip="help - click to show description of all buttons in the toolbar", - tooltip_clicked="hide help dialogue", - message="❔ Help Selected", - )), + add=CrudOptions( + **dict(ADD_BUTTON_KWARGS) + | dict( + tooltip="Add item", + tooltip_clicked="Go back to table", + button_style="success", + message="➕ Adding Value", + ) + ), + edit=CrudOptions( + **dict(EDIT_BUTTON_KWARGS) + | dict( + tooltip="Edit item", + tooltip_clicked="Go back to table", + button_style="warning", + message="✏️ Editing Value", + ) + ), + copy=CrudOptions( + **dict(COPY_BUTTON_KWARGS) + | dict( + tooltip="Copy item", + tooltip_clicked="Go back to table", + button_style="primary", + message="📝 Copying Value", + ) + ), + delete=CrudOptions( + **dict(DELETE_BUTTON_KWARGS) + | dict( + tooltip="Delete item", + tooltip_clicked="Go back to table", + button_style="danger", + message="🗑️ Deleting Value", + ) + ), + reload=CrudOptions( + **dict(RELOAD_BUTTON_KWARGS) + | dict( + tooltip="reload data", + tooltip_clicked="", + button_style="info", + message="♻ reloading data|", + ) + ), + support=CrudOptions( + **dict(HELP_BUTTON_KWARGS) + | dict( + tooltip="help - click to show description of all buttons in the toolbar", + tooltip_clicked="hide help dialogue", + message="❔ Help Selected", + ) + ), ) @@ -324,11 +342,10 @@ def display_ui_tooltips(uiobj): pass except: pass - return w.VBox( - [w.HBox([l, w.HTML(f"{l.tooltip}")]) for l in li] - ) + return w.VBox([w.HBox([l, w.HTML(f"{l.tooltip}")]) for l in li]) + -class CrudButtonBar(w.VBox): # w.HBox +class CrudButtonBar(w.VBox): # w.HBox active = tr.Unicode(default_value=None, allow_none=True) crud_view = tr.Dict(default_value=DEFAULT_BUTTONBAR_CONFIG) fn_add = tr.Callable(default_value=lambda: print("add")) @@ -359,11 +376,10 @@ def active_index(self): return list(self.crud_view.keys()).index(self.active) def _fn_support(self): - + with self.out: clear_output() display(display_ui_tooltips(self)) - def __init__( self, @@ -373,15 +389,17 @@ def __init__( super().__init__(**kwargs) # main container self.fn_support = self._fn_support self.out = w.Output() - self.hbx_bbar = w.HBox([ - self.add, - self.edit, - self.copy, - self.delete, - self.reload, - self.support, - self.message, - ]) + self.hbx_bbar = w.HBox( + [ + self.add, + self.edit, + self.copy, + self.delete, + self.reload, + self.support, + self.message, + ] + ) self.children = [self.hbx_bbar, self.out] self._init_controls() @@ -488,5 +506,3 @@ def backward(): fn_reload=lambda: print("fn_reload"), ) display(buttonbar) - - From 244d9e6d2cffabc15b91480e98c83a731c47cca4 Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 15:20:59 +0100 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=90=9B=20Fix=20EditGrid=20=5Fsetvie?= =?UTF-8?q?w=20method=20following=20changes=20to=20CrudButtonBar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/editgrid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ipyautoui/custom/editgrid.py b/src/ipyautoui/custom/editgrid.py index 8bfd88b..96b21fd 100644 --- a/src/ipyautoui/custom/editgrid.py +++ b/src/ipyautoui/custom/editgrid.py @@ -480,6 +480,8 @@ def _grid_changed(self, onchange): def _setview(self, onchange): if self.buttonbar_grid.active is None: self.stk_crud.selected_index = None + elif self.buttonbar_grid.active == "support": + self.stk_crud.selected_index = None else: self.stk_crud.selected_index = self.buttonbar_grid.active_index From 063b003a3c28af27a2d93ce910b9149c12006830 Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 15:56:01 +0100 Subject: [PATCH 04/10] =?UTF-8?q?=E2=9C=A8=20Implement=20`show=5Fsupport`?= =?UTF-8?q?=20trait?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index 2e78e32..a318bef 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -320,6 +320,7 @@ class CrudView(ty.TypedDict): ), support=CrudOptions( **dict(HELP_BUTTON_KWARGS) + | dict(layout=dict(display="None")) | dict( tooltip="help - click to show description of all buttons in the toolbar", tooltip_clicked="hide help dialogue", @@ -355,7 +356,15 @@ class CrudButtonBar(w.VBox): # w.HBox fn_backward = tr.Callable(default_value=lambda: print("backward")) fn_support = tr.Callable(default_value=lambda: print("support")) fn_reload = tr.Callable(default_value=None, allow_none=True) - show_support = tr.Bool(default_value=True) + show_support = tr.Bool(default_value=False) + + @tr.observe("show_support") + def _observe_show_support(self, change): + print(change) + if change["new"]: + self.support.layout.display = "" + else: + self.support.layout.display = "None" @tr.observe("fn_reload") def _observe_fn_reload(self, change): @@ -376,7 +385,6 @@ def active_index(self): return list(self.crud_view.keys()).index(self.active) def _fn_support(self): - with self.out: clear_output() display(display_ui_tooltips(self)) @@ -404,13 +412,13 @@ def __init__( self._init_controls() def _init_form(self): - # self.transpose w.ToggleButton(icon="arrow-right") - self.add = w.ToggleButton(**ADD_BUTTON_KWARGS) - self.edit = w.ToggleButton(**EDIT_BUTTON_KWARGS) - self.copy = w.ToggleButton(**COPY_BUTTON_KWARGS) - self.delete = w.ToggleButton(**DELETE_BUTTON_KWARGS) - self.reload = w.Button(**RELOAD_BUTTON_KWARGS) - self.support = w.ToggleButton(**HELP_BUTTON_KWARGS) + self.add = w.ToggleButton() + self.edit = w.ToggleButton() + self.copy = w.ToggleButton() + self.delete = w.ToggleButton() + self.reload = w.Button() + self.support = w.ToggleButton() + # ^ KWARGS for the buttons are set by CrudView self.message = w.HTML() self._set_crud_view_options() @@ -506,3 +514,5 @@ def backward(): fn_reload=lambda: print("fn_reload"), ) display(buttonbar) + + From 4157a14e33b1e7d146dea539ce675d4554896f08 Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 15:57:39 +0100 Subject: [PATCH 05/10] =?UTF-8?q?=F0=9F=94=8A=20Add=20warning=20log=20to?= =?UTF-8?q?=20display=5Fui=5Ftooltips=20=F0=9F=94=A5=20Remove=20unnecessar?= =?UTF-8?q?y=20else=20statement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index a318bef..1d12183 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -334,15 +334,13 @@ class CrudView(ty.TypedDict): def display_ui_tooltips(uiobj): """pass a ui object and display all items that contain tooltips with the tooltips exposed. NOT IN USE""" li = [] - for k, v in uiobj.__dict__.items(): + for _, v in uiobj.__dict__.items(): try: if "tooltip" in v.__dict__["_trait_values"]: if v.tooltip is not None: li.append(v) - else: - pass - except: - pass + except Exception as err: + logging.warning(err) return w.VBox([w.HBox([l, w.HTML(f"{l.tooltip}")]) for l in li]) From 2da00cbb733dd92219c52185a8154b73cbfc59be Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 16:06:17 +0100 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20Add=20type=20hint?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index 1d12183..b5014b7 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -331,8 +331,8 @@ class CrudView(ty.TypedDict): # + -def display_ui_tooltips(uiobj): - """pass a ui object and display all items that contain tooltips with the tooltips exposed. NOT IN USE""" +def display_ui_tooltips(uiobj: w.DOMWidget) -> w.VBox: + """Pass a UI object and display all widgets within it with their tooltips.""" li = [] for _, v in uiobj.__dict__.items(): try: From 6b771be94c2fca1a9deeebe8c27ddc180bd665e8 Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 16:15:57 +0100 Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=94=A5=20Remove=20print=20statement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index b5014b7..acf7108 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -358,7 +358,6 @@ class CrudButtonBar(w.VBox): # w.HBox @tr.observe("show_support") def _observe_show_support(self, change): - print(change) if change["new"]: self.support.layout.display = "" else: From 0f224a20d486da200a21010693a46da47d74d50e Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 17:25:26 +0100 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=8E=A8=20Lint=20with=20black?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index acf7108..a498fcb 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -341,7 +341,10 @@ def display_ui_tooltips(uiobj: w.DOMWidget) -> w.VBox: li.append(v) except Exception as err: logging.warning(err) - return w.VBox([w.HBox([l, w.HTML(f"{l.tooltip}")]) for l in li]) + replace_newlines = lambda x: x.replace("\n", "
") + return w.VBox( + [w.HBox([l, w.HTML(f"{replace_newlines(l.tooltip)}")]) for l in li] + ) class CrudButtonBar(w.VBox): # w.HBox @@ -511,5 +514,3 @@ def backward(): fn_reload=lambda: print("fn_reload"), ) display(buttonbar) - - From 104e5c34c4a1a69815a2a1b7d2ecd1ba631d495b Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 17:53:24 +0100 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=93=8C=20pydantic<2.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 65bf401..9333919 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "numpy", "openpyxl", "pandas", - "pydantic>2", + "pydantic<2.9", "pydantic-settings", "pydantic-extra-types", "PyYAML", From 239a17113b66ebfe52f77afb718ecb815abe263d Mon Sep 17 00:00:00 2001 From: Oliver Hensby Date: Wed, 11 Sep 2024 17:57:10 +0100 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unneeded=20commen?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipyautoui/custom/buttonbars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipyautoui/custom/buttonbars.py b/src/ipyautoui/custom/buttonbars.py index a498fcb..aba7e8b 100644 --- a/src/ipyautoui/custom/buttonbars.py +++ b/src/ipyautoui/custom/buttonbars.py @@ -347,7 +347,7 @@ def display_ui_tooltips(uiobj: w.DOMWidget) -> w.VBox: ) -class CrudButtonBar(w.VBox): # w.HBox +class CrudButtonBar(w.VBox): active = tr.Unicode(default_value=None, allow_none=True) crud_view = tr.Dict(default_value=DEFAULT_BUTTONBAR_CONFIG) fn_add = tr.Callable(default_value=lambda: print("add"))