##// END OF EJS Templates
s/LabelView/HTMLView
s/LabelView/HTMLView

File last commit:

r14446:81608774
r14446:81608774
Show More
Widget Tester.ipynb
278 lines | 11.1 KiB | text/plain | TextLexer
In [1]:
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.

In [2]:
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.

In [3]:
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.

In [4]:
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+'(&nbsp;&nbsp;')
        exec_str = widgets.StringWidget(parent=exec_box)
        close_label = widgets.StringWidget(parent=exec_box,default_view_name='HTMLView', value='&nbsp;&nbsp;)')
        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.

In [5]:
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.

In [6]:
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)