from IPython.html import widgets
from IPython.display import display
This notebook shows how widgets can be used to manipulate the properties of other widgets.
To list the properties of a widget and allow the user to modify them, a function that keeps widgets in sync with traits is required. This function will syncronize a traitlet of one widget with a traitlet of another widget. The method also supports one way syncronization of types that have string representations with string traits.
def sync_widgets_properties(widget_a, property_a, widget_b, property_b, str_rep=False):
def set_property_a(name, old, new):
if old != new:
if str_rep:
setattr(widget_a, property_a, str(new))
else:
setattr(widget_a, property_a, new)
def set_property_b(name, old, new):
if old != new and not str_rep:
setattr(widget_b, property_b, new)
widget_a.on_trait_change(set_property_b, property_a)
widget_b.on_trait_change(set_property_a, property_b)
if str_rep:
setattr(widget_a, property_a, str(getattr(widget_b, property_b)))
else:
setattr(widget_a, property_a, getattr(widget_b, property_b))
return widget_a
This function will create a best match widget to represent a traitlet of another widget. The function will then bind the two widgets using the sync_widget_properties
method above.
def create_hooked_widget(control, key):
property_type = type(getattr(control, key))
if key == "orientation":
return sync_widgets_properties(widgets.SelectionWidget(values=['vertical', 'horizontal'], default_view_name='ToggleButtonsView'), 'value', control, key)
elif property_type is int:
return sync_widgets_properties(widgets.IntWidget(), 'value', control, key)
elif property_type is float:
return sync_widgets_properties(widgets.FloatWidget(), 'value', control, key)
elif property_type is bool:
return sync_widgets_properties(widgets.BoolWidget(), 'value', control, key)
elif property_type is str or property_type is unicode:
return sync_widgets_properties(widgets.StringWidget(), 'value', control, key)
else:
return sync_widgets_properties(widgets.StringWidget(disabled=True), 'value', control, key, str_rep=True)
This function creates a modal that allows the user to read the doc string associated with a callable. The user can then invoke the callbable from the same modal.
modals = {}
def get_method_modal(control, method_name, parent=None):
if not method_name in dir(control):
return None
if not control in modals:
modals[control] = {}
if not method_name in modals[control]:
new_modal = widgets.ContainerWidget(default_view_name="ModalView")
new_modal.description="Invoke " + method_name
new_modal.button_text = method_name + "(...)"
doc_str = 'No doc string'
try:
doc_str = '<pre>' + getattr(control, method_name).__doc__ + '</pre>'
except:
pass
doc_label = widgets.StringWidget(parent=new_modal,default_view_name='HTMLView', value=doc_str)
doc_label.set_css('color', 'blue')
exec_box = widgets.ContainerWidget(parent=new_modal)
exec_box.hbox()
exec_box.pack_center()
exec_box.align_center()
open_label = widgets.StringWidget(parent=exec_box,default_view_name='HTMLView', value=method_name+'( ')
exec_str = widgets.StringWidget(parent=exec_box)
close_label = widgets.StringWidget(parent=exec_box,default_view_name='HTMLView', value=' )')
button_row = widgets.ContainerWidget(parent=new_modal)
button_row.hbox()
button_row.pack_end()
button_box = widgets.ContainerWidget(parent=button_row)
button_box.flex0()
exec_button = widgets.ButtonWidget(parent=button_box, description="Execute")
def handle_method_exec():
my_control = control
exec "my_control." + method_name.replace('\n', '') + "(" + exec_str.value + ")" in globals(), locals()
exec_button.on_click(handle_method_exec)
exec_str.on_submit(handle_method_exec)
if parent is not None:
new_modal.parent = parent
modals[control][method_name] = new_modal
display(new_modal)
return modals[control][method_name]
This function renders the control panel.
def hook_control(control, parent=None):
explorer = widgets.ContainerWidget()
if parent is not None:
explorer.parent = parent
explorer.hbox()
viewer = widgets.ContainerWidget(parent=explorer)
viewer.set_css({
'background': 'white',
'border': '1px solid #000',
'overflow': 'hidden',
})
viewer.flex2()
control.parent = viewer
side = widgets.ContainerWidget(parent=explorer)
side.flex1()
side_tab = widgets.MulticontainerWidget(parent=side)
side_tab.set_css('margin', '5px')
properties = widgets.ContainerWidget(parent=side_tab)
methods = widgets.ContainerWidget(parent=side_tab)
side_tab.set_title(0, 'Properties')
side_tab.set_title(1, 'Methods')
for key in sorted(control.keys):
property_control = create_hooked_widget(control, key)
property_control.parent = properties
property_control.description = key + ':'
methods.vbox()
methods.set_css('overflow', 'hidden')
methods_container = widgets.ContainerWidget(parent=methods)
methods_container.flex1()
methods_buttons = widgets.ContainerWidget(parent=methods)
methods_buttons.flex0()
methods_buttons.hbox()
methods_buttons.pack_end()
methods_buttons.set_css({
'padding-top': '5px',
'padding-right': '50px',
})
execute_button = widgets.ButtonWidget(parent=methods_buttons, description="Invoke")
method_list = widgets.SelectionWidget(parent=methods_container, default_view_name="ListBoxView")
method_list.description = "Names:"
method_list.set_css('height', '400px')
method_list.values = [attr_name for attr_name in dir(control) if callable(getattr(control, attr_name)) and not attr_name.startswith('_')]
def handle_execute_method():
get_method_modal(control, method_list.value, parent=parent)
execute_button.on_click(handle_execute_method)
display(explorer)
explorer.add_class('well')
return explorer
This final bit of code allows the user to select what control and view he/she wants to manipulate.
control_tester = widgets.ContainerWidget()
widget_names = [widget_name for widget_name in dir(widgets) if widget_name.endswith('Widget') and widget_name != "Widget"]
widget_selector = widgets.SelectionWidget(parent=control_tester, description="Widget type:", values=widget_names)
view_name = widgets.StringWidget(parent=control_tester, description="View name (optional):")
display_button_container = widgets.ContainerWidget(parent=control_tester)
display_button_container.hbox()
display_button_container.pack_end()
display_button_container.set_css("padding", "5px")
display_button = widgets.ButtonWidget(parent=display_button_container, description="Display")
display(control_tester)
last_displayed = [None]
def handle_display():
if last_displayed[0] is not None:
last_displayed[0].close()
widget_type = getattr(widgets, widget_selector.value)
widget = widget_type()
if len(view_name.value) > 0:
widget.default_view_name = view_name.value
last_displayed[0] = hook_control(widget)
display_button.on_click(handle_display)
view_name.on_submit(handle_display)