Example #1
0
 def on_click_repr_dialog(_):
     from nglview.widget_box import DraggableBox
     if self.widget_repr_parameters is not None and self.widget_repr_choices:
         self.widget_repr_parameters_dialog = DraggableBox(
             [self.widget_repr_choices, self.widget_repr_parameters])
         self.widget_repr_parameters_dialog._ipython_display_()
         self.widget_repr_parameters_dialog._dialog = 'on'
Example #2
0
 def on_click_repr_dialog(_):
     from nglview.widget_box import DraggableBox
     if self.widget_repr_parameters is not None and self.widget_repr_choices:
         self.widget_repr_parameters_dialog = DraggableBox([self.widget_repr_choices,
                              self.widget_repr_parameters])
         self.widget_repr_parameters_dialog._ipython_display_()
         self.widget_repr_parameters_dialog._dialog = 'on'
Example #3
0
class TrajectoryPlayer(HasTraits):
    # should set default values here different from desired defaults
    # so `observe` can be triggered
    step = Int(0)
    sync_frame = Bool(True)
    interpolate = Bool(False)
    delay = Float(0.0)
    parameters = Dict()
    iparams = Dict()
    _interpolation_t = Float()
    _iterpolation_type = CaselessStrEnum(['linear', 'spline'])
    spin = Bool(False)
    _spin_x = Int(1)
    _spin_y = Int(0)
    _spin_z = Int(0)
    _spin_speed = Float(0.005)
    camera = CaselessStrEnum(
        ['perspective', 'orthographic'],
        default_value='perspective')
    _render_params = Dict()
    _real_time_update = Bool(False)

    widget_tab = Any(None)
    widget_repr = Any(None)
    widget_repr_parameters = Any(None)
    widget_quick_repr = Any(None)
    widget_general = Any(None)
    widget_picked = Any(None)
    widget_preference = Any(None)
    widget_extra = Any(None)
    widget_theme = Any(None)
    widget_help = Any(None)
    widget_export_image = Any(None)
    widget_component_slider = Any(None)
    widget_repr_slider = Any(None)
    widget_repr_choices = Any(None)
    widget_repr_control_buttons = Any(None)
    widget_repr_add = Any(None)
    widget_accordion_repr_parameters = Any(None)
    widget_repr_parameters_dialog = Any(None)
    widget_repr_name = Any(None)
    widget_component_dropdown = Any(None)
    widget_drag = Any(None)

    def __init__(self, view, step=1, delay=100, sync_frame=False,
                 min_delay=40):
        self._view = view
        self.step = step
        self.sync_frame = sync_frame
        self.delay = delay
        self.min_delay = min_delay
        self._interpolation_t = 0.5
        self._iterpolation_type = 'linear'
        self.iparams = dict(
            t=self._interpolation_t, step=1, type=self._iterpolation_type)
        self._render_params = dict(
            factor=4, antialias=True, trim=False, transparent=False)

        self._widget_names = [w for w in dir(self) if w.startswith('wiget_')]

    def _update_padding(self, padding=default.DEFAULT_PADDING):
        widget_collection = [
            self.widget_general, self.widget_repr, self.widget_preference,
            self.widget_repr_parameters, self.widget_help, self.widget_extra,
            self.widget_picked
        ]
        for widget in widget_collection:
            if widget is not None:
                widget.layout.padding = padding

    def _create_all_widgets(self):
        if self.widget_tab is None:
            self.widget_tab = self._display()

        old_index = self.widget_tab.selected_index
        for index, _ in enumerate(self.widget_tab.children):
            self.widget_tab.selected_index = index

        self.widget_tab.selected_index = old_index

    def smooth(self):
        self.interpolate = True

    @observe('camera')
    def on_camera_changed(self, change):
        camera_type = change['new']
        self._view._remote_call(
            "setParameters",
            target='Stage',
            kwargs=dict(cameraType=camera_type))

    @property
    def frame(self):
        return self._view.frame

    @frame.setter
    def frame(self, value):
        self._view.frame = value

    @property
    def count(self):
        return self._view.count

    @observe('sync_frame')
    def update_sync_frame(self, change):
        value = change['new']
        if value:
            self._view._set_sync_frame()
        else:
            self._view._set_unsync_frame()

    @observe("delay")
    def update_delay(self, change):
        delay = change['new']
        self._view._set_delay(delay)

    @observe('parameters')
    def update_parameters(self, change):
        params = change['new']
        self.sync_frame = params.get("sync_frame", self.sync_frame)
        self.delay = params.get("delay", self.delay)
        self.step = params.get("step", self.step)

    @observe('_interpolation_t')
    def _interpolation_t_changed(self, change):
        self.iparams['t'] = change['new']

    @observe('spin')
    def on_spin_changed(self, change):
        self.spin = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)
        else:
            # stop
            self._view._set_spin(None, None)

    @observe('_spin_x')
    def on_spin_x_changed(self, change):
        self._spin_x = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    @observe('_spin_y')
    def on_spin_y_changed(self, change):
        self._spin_y = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    @observe('_spin_z')
    def on_spin_z_changed(self, change):
        self._spin_z = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    @observe('_spin_speed')
    def on_spin_speed_changed(self, change):
        self._spin_speed = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    def _display(self):
        box_factory = [(self._make_general_box, 'General'),
                       (self._make_widget_repr, 'Representation'),
                       (self._make_widget_preference, 'Preference'),
                       (self._make_theme_box, 'Theme'),
                       (self._make_extra_box, 'Extra'),
                       (self._show_website, 'Help')]

        tab = _make_delay_tab(box_factory, selected_index=0)
        # tab = _make_autofit(tab)
        tab.layout.align_self = 'center'
        tab.layout.align_items = 'stretch'

        self.widget_tab = tab

        return self.widget_tab

    def _make_widget_tab(self):
        return self._display()

    def _make_button_center(self):
        button = Button(description=' Center', icon='fa-bullseye')

        @button.on_click
        def on_click(button):
            self._view.center()

        return button

    def _make_button_theme(self):
        button = Button(description='Oceans16')

        @button.on_click
        def on_click(button):
            from nglview import theme
            display(theme.oceans16())
            self._view._remote_call('cleanOutput', target='Widget')

        return button

    def _make_button_reset_theme(self, hide_toolbar=False):
        from nglview import theme

        if hide_toolbar:
            button = Button(description='Simplified Default')

            @button.on_click
            def on_click(button):
                theme.reset(hide_toolbar=True)
        else:
            button = Button(description='Default')

            @button.on_click
            def on_click(button):
                theme.reset()

        return button

    def _make_button_clean_error_output(self):
        button = Button(description='Clear Error')

        @button.on_click
        def on_click(_):
            js_utils.clean_error_output()

        return button

    def _make_widget_preference(self, width='100%'):
        def make_func():
            parameters = self._view._full_stage_parameters

            def func(pan_speed=parameters.get('panSpeed', 0.8),
                     rotate_speed=parameters.get('rotateSpeed', 2),
                     zoom_speed=parameters.get('zoomSpeed', 1.2),
                     clip_dist=parameters.get('clipDist', 10),
                     camera_fov=parameters.get('cameraFov', 40),
                     clip_far=parameters.get('clipFar', 100),
                     clip_near=parameters.get('clipNear', 0),
                     fog_far=parameters.get('fogFar', 100),
                     fog_near=parameters.get('fogNear', 50),
                     impostor=parameters.get('impostor', True),
                     light_intensity=parameters.get('lightIntensity', 1),
                     quality=parameters.get('quality', 'medium'),
                     sample_level=parameters.get('sampleLevel', 1)):

                self._view.parameters = dict(
                    panSpeed=pan_speed,
                    rotateSpeed=rotate_speed,
                    zoomSpeed=zoom_speed,
                    clipDist=clip_dist,
                    clipFar=clip_far,
                    clipNear=clip_near,
                    cameraFov=camera_fov,
                    fogFar=fog_far,
                    fogNear=fog_near,
                    impostor=impostor,
                    lightIntensity=light_intensity,
                    quality=quality,
                    sampleLevel=sample_level)

            return func

        def make_widget_box():
            widget_sliders = interactive(
                make_func(),
                pan_speed=(0, 10, 0.1),
                rotate_speed=(0, 10, 1),
                zoom_speed=(0, 10, 1),
                clip_dist=(0, 200, 5),
                clip_far=(0, 100, 1),
                clip_near=(0, 100, 1),
                camera_fov=(15, 120, 1),
                fog_far=(0, 100, 1),
                fog_near=(0, 100, 1),
                light_intensity=(0, 10, 0.02),
                quality=['low', 'medium', 'high'],
                sample_level=(-1, 5, 1))

            for child in widget_sliders.children:
                if isinstance(child, (IntSlider, FloatSlider)):
                    child.layout.width = default.DEFAULT_SLIDER_WIDTH
            return widget_sliders

        if self.widget_preference is None:
            widget_sliders = make_widget_box()
            reset_button = Button(description='Reset')
            widget_sliders.children = [
                reset_button,
            ] + list(widget_sliders.children)

            @reset_button.on_click
            def on_click(reset_button):
                self._view.parameters = self._view._original_stage_parameters
                self._view._full_stage_parameters = self._view._original_stage_parameters
                widget_sliders.children = [
                    reset_button,
                ] + list(make_widget_box().children)

            self.widget_preference = _relayout_master(
                widget_sliders, width=width)
        return self.widget_preference

    def _show_download_image(self):
        # "interactive" does not work for True/False in ipywidgets 4 yet.
        button = Button(description=' Screenshot', icon='fa-camera')

        @button.on_click
        def on_click(button):
            self._view.download_image()

        return button

    def _make_button_url(self, url, description):
        button = Button(description=description)

        @button.on_click
        def on_click(button):
            display(Javascript(js_utils.open_url_template.format(url=url)))

        return button

    def _show_website(self, ngl_base_url=default.NGL_BASE_URL):
        buttons = [
            self._make_button_url(url.format(ngl_base_url), description)
            for url, description in
            [("'http://arose.github.io/nglview/latest/'", "nglview"
              ), ("'{}/index.html'", "NGL"
                  ), ("'{}/tutorial-selection-language.html'", "Selection"),
             ("'{}/tutorial-molecular-representations.html'", "Representation"
              )]
        ]
        self.widget_help = _make_autofit(HBox(buttons))
        return self.widget_help

    def _make_button_qtconsole(self):
        from nglview import js_utils
        button = Button(description='qtconsole', tooltip='pop up qtconsole')

        @button.on_click
        def on_click(button):
            js_utils.launch_qtconsole()

        return button

    def _make_text_picked(self):
        ta = Textarea(
            value=json.dumps(self._view.picked), description='Picked atom')
        ta.layout.width = '300px'
        return ta

    def _refresh(self, component_slider, repr_slider):
        """update representation and component information
        """
        self._view._request_repr_parameters(
            component=component_slider.value, repr_index=repr_slider.value)
        self._view._remote_call('requestReprInfo', target='Widget')
        self._view._handle_repr_dict_changed(change=dict(
            new=self._view._repr_dict))

    def _make_button_repr_control(self, component_slider, repr_slider,
                                  repr_selection):
        button_refresh = Button(
            description=' Refresh',
            tooltip='Get representation info',
            icon='fa-refresh')
        button_center_selection = Button(
            description=' Center',
            tooltip='center selected atoms',
            icon='fa-bullseye')
        button_center_selection._ngl_name = 'button_center_selection'
        button_hide = Button(
            description=' Hide',
            icon='fa-eye-slash',
            tooltip='Hide/Show current representation')
        button_remove = Button(
            description=' Remove',
            icon='fa-trash',
            tooltip='Remove current representation')
        button_repr_parameter_dialog = Button(
            description=' Dialog',
            tooltip='Pop up representation parameters control dialog')

        @button_refresh.on_click
        def on_click_refresh(button):
            self._refresh(component_slider, repr_slider)

        @button_center_selection.on_click
        def on_click_center(center_selection):
            self._view.center_view(
                selection=repr_selection.value,
                component=component_slider.value)

        @button_hide.on_click
        def on_click_hide(button_hide):
            component = component_slider.value
            repr_index = repr_slider.value

            if button_hide.description == 'Hide':
                hide = True
                button_hide.description = 'Show'
            else:
                hide = False
                button_hide.description = 'Hide'

            self._view._remote_call(
                'setVisibilityForRepr',
                target='Widget',
                args=[component, repr_index, not hide])

        @button_remove.on_click
        def on_click_remove(button_remove):
            self._view._remove_representation(
                component=component_slider.value, repr_index=repr_slider.value)
            self._view._request_repr_parameters(
                component=component_slider.value, repr_index=repr_slider.value)

        @button_repr_parameter_dialog.on_click
        def on_click_repr_dialog(_):
            from nglview.widget_box import DraggableBox
            if self.widget_repr_parameters is not None and self.widget_repr_choices:
                self.widget_repr_parameters_dialog = DraggableBox(
                    [self.widget_repr_choices, self.widget_repr_parameters])
                self.widget_repr_parameters_dialog._ipython_display_()
                self.widget_repr_parameters_dialog._dialog = 'on'

        bbox = _make_autofit(
            HBox([
                button_refresh, button_center_selection, button_hide,
                button_remove, button_repr_parameter_dialog
            ]))
        return bbox

    def _make_widget_repr(self):
        self.widget_repr_name = Text(value='', description='representation')
        self.widget_repr_name._ngl_name = 'repr_name_text'
        repr_selection = Text(value=' ', description='selection')
        repr_selection._ngl_name = 'repr_selection'
        repr_selection.width = self.widget_repr_name.width = default.DEFAULT_TEXT_WIDTH

        max_n_components = max(self._view.n_components - 1, 0)
        self.widget_component_slider = IntSlider(
            value=0, max=max_n_components, min=0, description='component')
        self.widget_component_slider._ngl_name = 'component_slider'

        cvalue = ' '
        self.widget_component_dropdown = Dropdown(
            value=cvalue, options=[
                cvalue,
            ], description='component')
        self.widget_component_dropdown._ngl_name = 'component_dropdown'

        self.widget_repr_slider = IntSlider(
            value=0,
            description='representation',
            width=default.DEFAULT_SLIDER_WIDTH)
        self.widget_repr_slider._ngl_name = 'repr_slider'
        self.widget_repr_slider.visible = True

        self.widget_component_slider.layout.width = default.DEFAULT_SLIDER_WIDTH
        self.widget_repr_slider.layout.width = default.DEFAULT_SLIDER_WIDTH
        self.widget_component_dropdown.layout.width = self.widget_component_dropdown.max_width = default.DEFAULT_TEXT_WIDTH

        # turn off for now
        self.widget_component_dropdown.layout.display = 'none'
        self.widget_component_dropdown.description = ''

        # self.widget_accordion_repr_parameters = Accordion()
        self.widget_accordion_repr_parameters = Tab()
        self.widget_repr_parameters = self._make_widget_repr_parameters(
            self.widget_component_slider, self.widget_repr_slider,
            self.widget_repr_name)
        self.widget_accordion_repr_parameters.children = [
            self.widget_repr_parameters, Box()
        ]
        self.widget_accordion_repr_parameters.set_title(0, 'Parameters')
        self.widget_accordion_repr_parameters.set_title(1, 'Hide')
        self.widget_accordion_repr_parameters.selected_index = 1

        checkbox_reprlist = Checkbox(value=False, description='reprlist')
        checkbox_reprlist._ngl_name = 'checkbox_reprlist'
        self.widget_repr_choices = self._make_repr_name_choices(
            self.widget_component_slider, self.widget_repr_slider)
        self.widget_repr_choices._ngl_name = 'reprlist_choices'

        self.widget_repr_add = self._make_add_widget_repr(
            self.widget_component_slider)

        def on_update_checkbox_reprlist(change):
            self.widget_repr_choices.visible = change['new']

        checkbox_reprlist.observe(on_update_checkbox_reprlist, names='value')

        def on_repr_name_text_value_changed(change):
            name = change['new'].strip()
            old = change['old'].strip()

            should_update = (self._real_time_update and old and name and
                             name in REPRESENTATION_NAMES and
                             name != change['old'].strip())

            if should_update:
                component = self.widget_component_slider.value
                repr_index = self.widget_repr_slider.value
                self._view._remote_call(
                    'setRepresentation',
                    target='Widget',
                    args=[change['new'], {}, component, repr_index])
                self._view._request_repr_parameters(component, repr_index)

        def on_component_or_repr_slider_value_changed(change):
            self._view._request_repr_parameters(
                component=self.widget_component_slider.value,
                repr_index=self.widget_repr_slider.value)
            self.widget_component_dropdown.options = tuple(
                self._view._ngl_component_names)

            if self.widget_accordion_repr_parameters.selected_index >= 0:
                self.widget_repr_parameters.name = self.widget_repr_name.value
                self.widget_repr_parameters.repr_index = self.widget_repr_slider.value
                self.widget_repr_parameters.component_index = self.widget_component_slider.value

        def on_repr_selection_value_changed(change):
            if self._real_time_update:
                component = self.widget_component_slider.value
                repr_index = self.widget_repr_slider.value
                self._view._set_selection(
                    change['new'], component=component, repr_index=repr_index)

        def on_change_component_dropdown(change):
            choice = change['new']
            if choice and choice.strip(): # bool(" ") is True
                self.widget_component_slider.value = self._view._ngl_component_names.index(
                    choice)

        self.widget_component_dropdown.observe(
            on_change_component_dropdown, names='value')

        self.widget_repr_slider.observe(
            on_component_or_repr_slider_value_changed, names='value')
        self.widget_component_slider.observe(
            on_component_or_repr_slider_value_changed, names='value')
        self.widget_repr_name.observe(
            on_repr_name_text_value_changed, names='value')
        repr_selection.observe(on_repr_selection_value_changed, names='value')

        self.widget_repr_control_buttons = self._make_button_repr_control(
            self.widget_component_slider, self.widget_repr_slider,
            repr_selection)

        blank_box = Box([Label("")])

        all_kids = [
            self.widget_repr_control_buttons, blank_box, self.widget_repr_add,
            self.widget_component_dropdown, self.widget_repr_name,
            repr_selection, self.widget_component_slider,
            self.widget_repr_slider, self.widget_repr_choices,
            self.widget_accordion_repr_parameters
        ]

        vbox = VBox(all_kids)

        self._view._request_repr_parameters(
            component=self.widget_component_slider.value,
            repr_index=self.widget_repr_slider.value)

        self.widget_repr = _relayout_master(vbox, width='100%')

        self._refresh(self.widget_component_slider, self.widget_repr_slider)

        setattr(self.widget_repr, "_saved_widgets", [])
        for _box in self.widget_repr.children:
            if hasattr(_box, 'children'):
                for kid in _box.children:
                    self.widget_repr._saved_widgets.append(kid)

        return self.widget_repr

    def _make_widget_repr_parameters(self,
                                     component_slider,
                                     repr_slider,
                                     repr_name_text=None):
        name = repr_name_text.value if repr_name_text is not None else ' '
        widget = self._view._display_repr(
            component=component_slider.value,
            repr_index=repr_slider.value,
            name=name)
        widget._ngl_name = 'repr_parameters_box'
        return widget

    def _make_button_export_image(self):
        slider_factor = IntSlider(value=4, min=1, max=10, description='scale')
        checkbox_antialias = Checkbox(value=True, description='antialias')
        checkbox_trim = Checkbox(value=False, description='trim')
        checkbox_transparent = Checkbox(value=False, description='transparent')
        filename_text = Text(value='Screenshot', description='Filename')
        delay_text = FloatText(
            value=1, description='delay (s)', tooltip='hello')

        start_text, stop_text, step_text = (
            IntText(value=0, description='start'),
            IntText(value=self._view.count, description='stop'),
            IntText(value=1, description='step'))

        start_text.layout.max_width = stop_text.layout.max_width = step_text.layout.max_width \
                = filename_text.layout.max_width = delay_text.layout.max_width = default.DEFAULT_TEXT_WIDTH

        button_movie_images = Button(description='Export Images')

        def download_image(filename):
            self._view.download_image(
                factor=slider_factor.value,
                antialias=checkbox_antialias.value,
                trim=checkbox_trim.value,
                transparent=checkbox_transparent.value,
                filename=filename)

        @button_movie_images.on_click
        def on_click_images(button_movie_images):
            for i in range(start_text.value, stop_text.value, step_text.value):
                self._view.frame = i
                time.sleep(delay_text.value)
                download_image(filename=filename_text.value + str(i))
                time.sleep(delay_text.value)

        vbox = VBox([
            button_movie_images,
            start_text,
            stop_text,
            step_text,
            delay_text,
            filename_text,
            slider_factor,
            checkbox_antialias,
            checkbox_trim,
            checkbox_transparent,
        ])

        form_items = _relayout(vbox, make_form_item_layout())
        form = Box(form_items, layout=_make_box_layout())
        # form = _relayout_master(vbox)
        return form

    def _make_resize_notebook_slider(self):
        resize_notebook_slider = IntSlider(
            min=300, max=2000, description='resize notebook')

        def on_resize_notebook(change):
            width = change['new']
            self._view._remote_call(
                'resizeNotebook', target='Widget', args=[
                    width,
                ])

        resize_notebook_slider.observe(on_resize_notebook, names='value')
        return resize_notebook_slider

    def _make_add_widget_repr(self, component_slider):
        dropdown_repr_name = Dropdown(
            options=REPRESENTATION_NAMES, value='cartoon')
        repr_selection = Text(value='*', description='')
        repr_button = Button(
            description='Add',
            tooltip="""Add representation.
        You can also hit Enter in selection box""")
        repr_button.layout = Layout(width='auto', flex='1 1 auto')

        dropdown_repr_name.layout.width = repr_selection.layout.width = default.DEFAULT_TEXT_WIDTH

        def on_click_or_submit(button_or_text_area):
            self._view.add_representation(
                selection=repr_selection.value.strip(),
                repr_type=dropdown_repr_name.value,
                component=component_slider.value)

        repr_button.on_click(on_click_or_submit)
        repr_selection.on_submit(on_click_or_submit)
        add_repr_box = HBox([repr_button, dropdown_repr_name, repr_selection])
        add_repr_box._ngl_name = 'add_repr_box'

        return add_repr_box

    def _make_repr_playground(self):
        vbox = VBox()
        children = []

        rep_names = REPRESENTATION_NAMES[:]
        excluded_names = ['ball+stick', 'distance']
        for name in excluded_names:
            rep_names.remove(name)

        repr_selection = Text(value='*')
        repr_selection.layout.width = default.DEFAULT_TEXT_WIDTH
        repr_selection_box = HBox([Label('selection'), repr_selection])
        setattr(repr_selection_box, 'value', repr_selection.value)

        for index, name in enumerate(rep_names):
            button = ToggleButton(description=name)

            def make_func():
                def on_toggle_button_value_change(change, button=button):
                    selection = repr_selection.value
                    new = change['new']  # True/False
                    if new:
                        self._view.add_representation(
                            button.description, selection=selection)
                    else:
                        self._view._remove_representations_by_name(
                            button.description)

                return on_toggle_button_value_change

            button.observe(make_func(), names='value')
            children.append(button)

        button_clear = Button(
            description='clear', button_style='info', icon='fa-eraser')

        @button_clear.on_click
        def on_clear(button_clear):
            self._view.clear()
            for kid in children:
                # unselect
                kid.value = False

        vbox.children = children + [repr_selection, button_clear]
        _make_autofit(vbox)
        self.widget_quick_repr = vbox
        return self.widget_quick_repr

    def _make_repr_name_choices(self, component_slider, repr_slider):
        repr_choices = Dropdown(options=[
            " ",
        ])

        def on_chosen(change):
            repr_name = change.get('new', " ")
            try:
                repr_index = repr_choices.options.index(repr_name)
                repr_slider.value = repr_index
            except ValueError:
                pass

        repr_choices.observe(on_chosen, names='value')
        repr_choices.layout.width = default.DEFAULT_TEXT_WIDTH

        self.widget_repr_choices = repr_choices
        return self.widget_repr_choices

    def _make_drag_widget(self):
        button_drag = Button(
            description='widget drag: off', tooltip='dangerous')
        drag_nb = Button(description='notebook drag: off', tooltip='dangerous')
        button_reset_notebook = Button(
            description='notebook: reset', tooltip='reset?')
        button_dialog = Button(description='dialog', tooltip='make a dialog')
        button_split_half = Button(
            description='split screen',
            tooltip='try best to make a good layout')

        @button_drag.on_click
        def on_drag(button_drag):
            if button_drag.description == 'widget drag: off':
                self._view._set_draggable(True)
                button_drag.description = 'widget drag: on'
            else:
                self._view._set_draggable(False)
                button_drag.description = 'widget drag: off'

        @drag_nb.on_click
        def on_drag_nb(button_drag):
            if drag_nb.description == 'notebook drag: off':
                js_utils._set_notebook_draggable(True)
                drag_nb.description = 'notebook drag: on'
            else:
                js_utils._set_notebook_draggable(False)
                drag_nb.description = 'notebook drag: off'

        @button_reset_notebook.on_click
        def on_reset(button_reset_notebook):
            js_utils._reset_notebook()

        @button_dialog.on_click
        def on_dialog(button_dialog):
            self._view._remote_call('setDialog', target='Widget')

        @button_split_half.on_click
        def on_split_half(button_dialog):
            from nglview import js_utils
            import time
            js_utils._move_notebook_to_the_left()
            js_utils._set_notebook_width('5%')
            time.sleep(0.1)
            self._view._remote_call('setDialog', target='Widget')

        drag_box = HBox([
            button_drag, drag_nb, button_reset_notebook, button_dialog,
            button_split_half
        ])
        drag_box = _make_autofit(drag_box)
        self.widget_drag = drag_box
        return drag_box

    def _make_spin_box(self):
        checkbox_spin = Checkbox(self.spin, description='spin')
        spin_x_slide = IntSlider(
            self._spin_x, min=-1, max=1, description='spin_x')
        spin_y_slide = IntSlider(
            self._spin_y, min=-1, max=1, description='spin_y')
        spin_z_slide = IntSlider(
            self._spin_z, min=-1, max=1, description='spin_z')
        spin_speed_slide = FloatSlider(
            self._spin_speed,
            min=0,
            max=0.2,
            step=0.001,
            description='spin speed')
        # spin
        link((checkbox_spin, 'value'), (self, 'spin'))
        link((spin_x_slide, 'value'), (self, '_spin_x'))
        link((spin_y_slide, 'value'), (self, '_spin_y'))
        link((spin_z_slide, 'value'), (self, '_spin_z'))
        link((spin_speed_slide, 'value'), (self, '_spin_speed'))

        spin_box = VBox([
            checkbox_spin, spin_x_slide, spin_y_slide, spin_z_slide,
            spin_speed_slide
        ])
        spin_box = _relayout_master(spin_box, width='75%')
        return spin_box

    def _make_widget_picked(self):
        self.widget_picked = self._make_text_picked()
        picked_box = HBox([
            self.widget_picked,
        ])
        return _relayout_master(picked_box, width='75%')

    def _make_export_image_widget(self):
        if self.widget_export_image is None:
            self.widget_export_image = HBox([self._make_button_export_image()])
        return self.widget_export_image

    def _make_extra_box(self):
        if self.widget_extra is None:
            extra_list = [(self._make_drag_widget, 'Drag'),
                          (self._make_spin_box, 'Spin'),
                          (self._make_widget_picked, 'Picked'),
                          (self._make_repr_playground, 'Quick'),
                          (self._make_export_image_widget, 'Image'),
                          (self._make_command_box, 'Command')]

            extra_box = _make_delay_tab(extra_list, selected_index=0)
            self.widget_extra = extra_box
        return self.widget_extra

    def _make_theme_box(self):
        if self.widget_theme is None:
            self.widget_theme = Box([
                self._make_button_theme(),
                self._make_button_reset_theme(hide_toolbar=False),
                self._make_button_reset_theme(hide_toolbar=True),
                self._make_button_clean_error_output()
            ])
        return self.widget_theme

    def _make_general_box(self):
        if self.widget_general is None:
            step_slide = IntSlider(
                value=self.step, min=-100, max=100, description='step')
            delay_text = IntSlider(
                value=self.delay, min=10, max=1000, description='delay')
            toggle_button_interpolate = ToggleButton(
                self.interpolate,
                description='Smoothing',
                tooltip='smoothing trajectory')
            link((toggle_button_interpolate, 'value'), (self, 'interpolate'))

            background_color_picker = ColorPicker(
                value='white', description='background')
            camera_type = Dropdown(
                value=self.camera,
                options=['perspective', 'orthographic'],
                description='camera')

            link((step_slide, 'value'), (self, 'step'))
            link((delay_text, 'value'), (self, 'delay'))
            link((toggle_button_interpolate, 'value'), (self, 'interpolate'))
            link((camera_type, 'value'), (self, 'camera'))
            link((background_color_picker, 'value'),
                 (self._view, 'background'))

            center_button = self._make_button_center()
            render_button = self._show_download_image()
            qtconsole_button = self._make_button_qtconsole()
            center_render_hbox = _make_autofit(
                HBox([
                    toggle_button_interpolate, center_button, render_button,
                    qtconsole_button
                ]))

            v0_left = VBox([
                step_slide,
                delay_text,
                background_color_picker,
                camera_type,
                center_render_hbox,
            ])

            v0_left = _relayout_master(v0_left, width='100%')
            self.widget_general = v0_left
        return self.widget_general

    def _make_command_box(self):
        widget_text_command = Text()

        @widget_text_command.on_submit
        def _on_submit_command(_):
            command = widget_text_command.value
            js_utils.execute(command)
            widget_text_command.value = ''

        return widget_text_command

    def _create_all_tabs(self):
        tab = self._display()
        for index, _ in enumerate(tab.children):
            # trigger ceating widgets
            tab.selected_index = index

        self.widget_extra = self._make_extra_box()
        for index, _ in enumerate(self.widget_extra.children):
            self.widget_extra.selected_index = index

    def _simplify_repr_control(self):
        for widget in self.widget_repr._saved_widgets:
            if not isinstance(widget, Tab):
                widget.layout.display = 'none'
        self.widget_repr_choices.layout.display = 'flex'
        self.widget_accordion_repr_parameters.selected_index = 0
Example #4
0
class TrajectoryPlayer(DOMWidget):
    # should set default values here different from desired defaults
    # so `observe` can be triggered
    step = Int(0).tag(sync=True)
    sync_frame = Bool(True).tag(sync=True)
    interpolate = Bool(False).tag(sync=False)
    delay = Float(0.0).tag(sync=True)
    parameters = Dict().tag(sync=True)
    iparams = Dict().tag(sync=False)
    _interpolation_t = Float().tag(sync=False)
    _iterpolation_type = CaselessStrEnum(['linear', 'spline']).tag(sync=False)
    spin = Bool(False).tag(sync=False)
    _spin_x = Int(1).tag(sync=False)
    _spin_y = Int(0).tag(sync=False)
    _spin_z = Int(0).tag(sync=False)
    _spin_speed = Float(0.005).tag(sync=False)
    camera = CaselessStrEnum(['perspective', 'orthographic'],
        default_value='perspective').tag(sync=False)
    _render_params = Dict().tag(sync=False)
    _real_time_update = Bool(False).tag(sync=False)

    widget_tab = Any(None).tag(sync=False)
    widget_repr = Any(None).tag(sync=False)
    widget_repr_parameters = Any(None).tag(sync=False)
    widget_quick_repr = Any(None).tag(sync=False)
    widget_general = Any(None).tag(sync=False)
    widget_picked = Any(None).tag(sync=False)
    widget_preference = Any(None).tag(sync=False)
    widget_extra = Any(None).tag(sync=False)
    widget_theme = Any(None).tag(sync=False)
    widget_help = Any(None).tag(sync=False)
    widget_export_image = Any(None).tag(sync=False)
    widget_component_slider = Any(None).tag(sync=False)
    widget_repr_slider = Any(None).tag(sync=False)
    widget_repr_choices = Any(None).tag(sync=False)
    widget_repr_control_buttons = Any(None).tag(sync=False)
    widget_repr_add = Any(None).tag(sync=False)
    widget_accordion_repr_parameters = Any(None).tag(sync=False)
    widget_repr_parameters_dialog = Any(None).tag(sync=False)
    widget_repr_name = Any(None).tag(sync=False)
    widget_component_dropdown = Any(None).tag(sync=False)
    widget_drag = Any(None).tag(sync=False)

    def __init__(self, view, step=1, delay=100,
                 sync_frame=False, min_delay=40):
        self._view = view
        self.step = step
        self.sync_frame = sync_frame
        self.delay = delay
        self.min_delay = min_delay
        self._interpolation_t = 0.5
        self._iterpolation_type = 'linear'
        self.iparams = dict(
            t=self._interpolation_t,
            step=1,
            type=self._iterpolation_type)
        self._render_params = dict(factor=4,
                                   antialias=True,
                                   trim=False,
                                   transparent=False)

        self._widget_names = [w for w in dir(self) if w.startswith('wiget_')]
        self.observe(self._on_widget_built, names=['widget_repr_parameters',
            'widget_repr',
            'widget_preference'])
        self._movie_maker = None

    def _on_widget_built(self, change):
        widget = change['new']
        if widget is not None:
            widget.layout.padding = '5%'

    def _update_padding(self, padding=default.DEFAULT_PADDING):
        widget_collection = [
                self.widget_general,
                self.widget_repr,
                self.widget_preference,
                self.widget_repr_parameters,
                self.widget_help,
                self.widget_extra,
                self.widget_picked
        ]
        for widget in widget_collection:
            if widget is not None:
                widget.layout.padding = padding

    def _create_all_widgets(self):
        if self.widget_tab is None:
            self.widget_tab = self._display()

        old_index = self.widget_tab.selected_index
        for index, _ in enumerate(self.widget_tab.children):
            self.widget_tab.selected_index = index

        self.widget_tab.selected_index = old_index

    def smooth(self):
        self.interpolate = True

    @observe('camera')
    def on_camera_changed(self, change):
        camera_type = change['new']
        self._view._remote_call("setParameters",
                                target='Stage',
                                kwargs=dict(cameraType=camera_type))

    @property
    def frame(self):
        return self._view.frame

    @frame.setter
    def frame(self, value):
        self._view.frame = value

    @property
    def count(self):
        return self._view.count

    @observe('sync_frame')
    def update_sync_frame(self, change):
        value = change['new']
        if value:
            self._view._set_sync_frame()
        else:
            self._view._set_unsync_frame()

    @observe("delay")
    def update_delay(self, change):
        delay = change['new']
        self._view._set_delay(delay)

    @observe('parameters')
    def update_parameters(self, change):
        params = change['new']
        self.sync_frame = params.get("sync_frame", self.sync_frame)
        self.delay = params.get("delay", self.delay)
        self.step = params.get("step", self.step)

    @observe('_interpolation_t')
    def _interpolation_t_changed(self, change):
        self.iparams['t'] = change['new']

    @observe('spin')
    def on_spin_changed(self, change):
        self.spin = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)
        else:
            # stop
            self._view._set_spin(None, None)

    @observe('_spin_x')
    def on_spin_x_changed(self, change):
        self._spin_x = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    @observe('_spin_y')
    def on_spin_y_changed(self, change):
        self._spin_y = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    @observe('_spin_z')
    def on_spin_z_changed(self, change):
        self._spin_z = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    @observe('_spin_speed')
    def on_spin_speed_changed(self, change):
        self._spin_speed = change['new']
        if self.spin:
            self._view._set_spin([self._spin_x, self._spin_y, self._spin_z],
                                 self._spin_speed)

    def _display(self):
        box_factory = [(self._make_general_box, 'General'),
                       (self._make_widget_repr, 'Representation'),
                       (self._make_widget_preference, 'Preference'),
                       (self._make_theme_box, 'Theme'),
                       (self._make_extra_box, 'Extra'),
                       (self._show_website, 'Help')]

        tab = _make_delay_tab(box_factory, selected_index=-1)
        # tab = _make_autofit(tab)
        tab.layout.align_self = 'center'
        tab.layout.align_items = 'stretch'

        self.widget_tab = tab

        return self.widget_tab

    def _make_widget_tab(self):
        return self._display()

    def _make_button_center(self):
        button = Button(description=' Center', icon='fa-bullseye')
        @button.on_click
        def on_click(button):
            self._view.center()
        return button

    def _make_button_theme(self):
        button = Button(description='Oceans16')
        @button.on_click
        def on_click(button):
            from nglview import theme
            display(theme.oceans16())
            self._view._remote_call('cleanOutput',
                                    target='Widget')
        return button

    def _make_button_reset_theme(self, hide_toolbar=False):
        from nglview import theme

        if hide_toolbar:
            button = Button(description='Simplified Default')
            @button.on_click
            def on_click(button):
                theme.reset(hide_toolbar=True)
        else:
            button = Button(description='Default')
            @button.on_click
            def on_click(button):
                theme.reset()
        return button

    def _make_button_clean_error_output(self):
        button = Button(description='Clear Error')
        @button.on_click
        def on_click(_):
            js_utils.clean_error_output()
        return button

    def _make_widget_preference(self, width='100%'):
        def make_func():
            parameters = self._view._full_stage_parameters
            def func(pan_speed=parameters.get('panSpeed', 0.8),
                     rotate_speed=parameters.get('rotateSpeed', 2),
                     zoom_speed=parameters.get('zoomSpeed', 1.2),
                     clip_dist=parameters.get('clipDist', 10),
                     camera_fov=parameters.get('cameraFov', 40),
                     clip_far=parameters.get('clipFar', 100),
                     clip_near=parameters.get('clipNear', 0),
                     fog_far=parameters.get('fogFar', 100),
                     fog_near=parameters.get('fogNear', 50),
                     impostor=parameters.get('impostor', True),
                     light_intensity=parameters.get('lightIntensity', 1),
                     quality=parameters.get('quality', 'medium'),
                     sample_level=parameters.get('sampleLevel', 1)):

                self._view.parameters = dict(
                    panSpeed=pan_speed,
                    rotateSpeed=rotate_speed,
                    zoomSpeed=zoom_speed,
                    clipDist=clip_dist,
                    clipFar=clip_far,
                    clipNear=clip_near,
                    cameraFov=camera_fov,
                    fogFar=fog_far,
                    fogNear=fog_near,
                    impostor=impostor,
                    lightIntensity=light_intensity,
                    quality=quality,
                    sampleLevel=sample_level)

            return func

        def make_widget_box():
            widget_sliders = interactive(make_func(),
                      pan_speed=(0, 10, 0.1),
                      rotate_speed=(0, 10, 1),
                      zoom_speed=(0, 10, 1),
                      clip_dist=(0, 200, 5),
                      clip_far=(0, 100, 1),
                      clip_near=(0, 100, 1),
                      camera_fov=(15, 120, 1),
                      fog_far=(0, 100, 1),
                      fog_near=(0, 100, 1),
                      light_intensity=(0, 10, 0.02),
                      quality=['low', 'medium', 'high'],
                      sample_level=(-1, 5, 1))

            for child in widget_sliders.children:
                if isinstance(child, (IntSlider, FloatSlider)):
                    child.layout.width = default.DEFAULT_SLIDER_WIDTH
            return widget_sliders

        if self.widget_preference is None:
            widget_sliders = make_widget_box()
            reset_button = Button(description='Reset')
            widget_sliders.children = [reset_button,] + list(widget_sliders.children)

            @reset_button.on_click
            def on_click(reset_button):
                self._view.parameters = self._view._original_stage_parameters
                self._view._full_stage_parameters = self._view._original_stage_parameters
                widget_sliders.children = [reset_button,] + list(make_widget_box().children)

            self.widget_preference = _relayout_master(widget_sliders, width=width)
        return self.widget_preference

    def _show_download_image(self):
        # "interactive" does not work for True/False in ipywidgets 4 yet.
        button = Button(description=' Screenshot', icon='fa-camera')
        @button.on_click
        def on_click(button):
            self._view.download_image()
        return button

    def _make_button_url(self, url, description):
        button = Button(description=description)

        @button.on_click
        def on_click(button):
            display(Javascript(js_utils.open_url_template.format(url=url)))
        return button

    def _show_website(self, ngl_base_url=default.NGL_BASE_URL):
        buttons = [self._make_button_url(url.format(ngl_base_url), description) for url, description in
            [("'http://arose.github.io/nglview/latest/'", "nglview"),
             ("'{}/index.html'", "NGL"),
             ("'{}/tutorial-selection-language.html'", "Selection"),
             ("'{}/tutorial-molecular-representations.html'", "Representation")]
        ]
        self.widget_help = _make_autofit(HBox(buttons))
        return self.widget_help

    def _make_button_qtconsole(self):
        from nglview import js_utils
        button = Button(description='qtconsole',
                tooltip='pop up qtconsole')

        @button.on_click
        def on_click(button):
            js_utils.launch_qtconsole()
        return button

    def _make_text_picked(self):
        ta = Textarea(value=json.dumps(self._view.picked), description='Picked atom')
        ta.layout.width = '300px'
        return ta

    def _refresh(self, component_slider, repr_slider):
        """update representation and component information
        """
        self._view._request_repr_parameters(component=component_slider.value,
                                            repr_index=repr_slider.value)
        self._view._remote_call('requestReprInfo', target='Widget')
        self._view._handle_repr_dict_changed(change=dict(new=self._view._repr_dict))

    def _make_button_repr_control(self, component_slider, repr_slider, repr_selection):
        button_refresh = Button(description=' Refresh', tooltip='Get representation info', icon='fa-refresh')
        button_center_selection = Button(description=' Center', tooltip='center selected atoms',
                icon='fa-bullseye')
        button_center_selection._ngl_name = 'button_center_selection'
        button_hide = Button(description=' Hide',
                icon='fa-eye-slash',
                tooltip='Hide/Show current representation')
        button_remove = Button(description=' Remove',
                icon='fa-trash',
                tooltip='Remove current representation')
        button_repr_parameter_dialog = Button(description=' Dialog',
                tooltip='Pop up representation parameters control dialog')

        @button_refresh.on_click
        def on_click_refresh(button):
            self._refresh(component_slider, repr_slider)

        @button_center_selection.on_click
        def on_click_center(center_selection):
            self._view.center_view(selection=repr_selection.value,
                                   component=component_slider.value)

        @button_hide.on_click
        def on_click_hide(button_hide):
            component=component_slider.value
            repr_index=repr_slider.value

            if button_hide.description == 'Hide':
                hide = True
                button_hide.description = 'Show'
            else:
                hide = False
                button_hide.description = 'Hide'

            self._view._remote_call('setVisibilityForRepr',
                                    target='Widget',
                                    args=[component, repr_index, not hide])

        @button_remove.on_click
        def on_click_remove(button_remove):
            self._view._remove_representation(component=component_slider.value,
                                              repr_index=repr_slider.value)
            self._view._request_repr_parameters(component=component_slider.value,
                                                repr_index=repr_slider.value)

        @button_repr_parameter_dialog.on_click
        def on_click_repr_dialog(_):
            from nglview.widget_box import DraggableBox
            if self.widget_repr_parameters is not None and self.widget_repr_choices:
                self.widget_repr_parameters_dialog = DraggableBox([self.widget_repr_choices,
                                     self.widget_repr_parameters])
                self.widget_repr_parameters_dialog._ipython_display_()
                self.widget_repr_parameters_dialog._dialog = 'on'

        bbox = _make_autofit(HBox([button_refresh, button_center_selection,
                                   button_hide, button_remove,
                                   button_repr_parameter_dialog]))
        return bbox

    def _make_widget_repr(self):
        self.widget_repr_name = Text(value='', description='representation')
        self.widget_repr_name._ngl_name = 'repr_name_text'
        repr_selection = Text(value=' ', description='selection')
        repr_selection._ngl_name = 'repr_selection'
        repr_selection.width = self.widget_repr_name.width = default.DEFAULT_TEXT_WIDTH 

        max_n_components = max(self._view.n_components-1, 0)
        self.widget_component_slider = IntSlider(value=0, max=max_n_components, min=0, description='component')
        self.widget_component_slider._ngl_name = 'component_slider'

        cvalue = ' '
        self.widget_component_dropdown = Dropdown(value=cvalue, options=[cvalue,],
                description='component')
        self.widget_component_dropdown._ngl_name = 'component_dropdown'

        self.widget_repr_slider = IntSlider(value=0, description='representation', width=default.DEFAULT_SLIDER_WIDTH)
        self.widget_repr_slider._ngl_name = 'repr_slider'
        self.widget_repr_slider.visible = True

        self.widget_component_slider.layout.width = default.DEFAULT_SLIDER_WIDTH
        self.widget_repr_slider.layout.width = default.DEFAULT_SLIDER_WIDTH
        self.widget_component_dropdown.layout.width = self.widget_component_dropdown.max_width = default.DEFAULT_TEXT_WIDTH

        # turn off for now
        self.widget_component_dropdown.layout.display = 'none'
        self.widget_component_dropdown.description = ''

        # self.widget_accordion_repr_parameters = Accordion()
        self.widget_accordion_repr_parameters = Tab()
        self.widget_repr_parameters =  self._make_widget_repr_parameters(self.widget_component_slider,
                self.widget_repr_slider,
                self.widget_repr_name)
        self.widget_accordion_repr_parameters.children = [self.widget_repr_parameters, Box()]
        self.widget_accordion_repr_parameters.set_title(0, 'Parameters')
        self.widget_accordion_repr_parameters.set_title(1, 'Hide')
        self.widget_accordion_repr_parameters.selected_index = 1
        
        checkbox_reprlist = Checkbox(value=False, description='reprlist')
        checkbox_reprlist._ngl_name = 'checkbox_reprlist'
        self.widget_repr_choices = self._make_repr_name_choices(self.widget_component_slider,
                self.widget_repr_slider)
        self.widget_repr_choices._ngl_name = 'reprlist_choices'

        self.widget_repr_add = self._make_add_widget_repr(self.widget_component_slider)

        def on_update_checkbox_reprlist(change):
            self.widget_repr_choices.visible= change['new']
        checkbox_reprlist.observe(on_update_checkbox_reprlist, names='value')

        def on_repr_name_text_value_changed(change):
            name = change['new'].strip()
            old = change['old'].strip()

            should_update = (self._real_time_update
                             and old and name
                             and name in REPRESENTATION_NAMES
                             and name != change['old'].strip())

            if should_update:
                component=self.widget_component_slider.value
                repr_index=self.widget_repr_slider.value
                self._view._remote_call('setRepresentation',
                                 target='Widget',
                                 args=[change['new'], {}, component, repr_index])
                self._view._request_repr_parameters(component, repr_index)

        def on_component_or_repr_slider_value_changed(change):
            self._view._request_repr_parameters(component=self.widget_component_slider.value,
                                                repr_index=self.widget_repr_slider.value)
            self.widget_component_dropdown.options = tuple(self._view._ngl_component_names)

            if self.widget_accordion_repr_parameters.selected_index >= 0:
                self.widget_repr_parameters.name = self.widget_repr_name.value
                self.widget_repr_parameters.repr_index = self.widget_repr_slider.value
                self.widget_repr_parameters.component_index = self.widget_component_slider.value

        def on_repr_selection_value_changed(change):
            if self._real_time_update:
                component = self.widget_component_slider.value
                repr_index = self.widget_repr_slider.value
                self._view._set_selection(change['new'],
                                          component=component,
                                          repr_index=repr_index)

        def on_change_component_dropdown(change):
            choice = change['new']
            if choice:
                 self.widget_component_slider.value = self._view._ngl_component_names.index(choice)

        self.widget_component_dropdown.observe(on_change_component_dropdown, names='value')

        self.widget_repr_slider.observe(on_component_or_repr_slider_value_changed, names='value')
        self.widget_component_slider.observe(on_component_or_repr_slider_value_changed, names='value')
        self.widget_repr_name.observe(on_repr_name_text_value_changed, names='value')
        repr_selection.observe(on_repr_selection_value_changed, names='value')

        self.widget_repr_control_buttons = self._make_button_repr_control(self.widget_component_slider,
        self.widget_repr_slider, repr_selection)

        blank_box = Box([Label("")])

        all_kids = [self.widget_repr_control_buttons,
                    blank_box,
                    self.widget_repr_add,
                    self.widget_component_dropdown,
                    self.widget_repr_name,
                    repr_selection,
                    self.widget_component_slider,
                    self.widget_repr_slider,
                    self.widget_repr_choices,
                    self.widget_accordion_repr_parameters
        ]

        vbox = VBox(all_kids)

        self._view._request_repr_parameters(component=self.widget_component_slider.value,
            repr_index=self.widget_repr_slider.value)

        self.widget_repr = _relayout_master(vbox, width='100%')

        self._refresh(self.widget_component_slider, self.widget_repr_slider)

        setattr(self.widget_repr, "_saved_widgets", [])
        for _box in self.widget_repr.children:
            if hasattr(_box, 'children'):
                for kid in _box.children:
                    self.widget_repr._saved_widgets.append(kid)

        return self.widget_repr

    def _make_widget_repr_parameters(self, component_slider, repr_slider, repr_name_text=None):
        name = repr_name_text.value if repr_name_text is not None else ' '
        widget = self._view._display_repr(component=component_slider.value,
                                          repr_index=repr_slider.value,
                                          name=name)
        widget._ngl_name = 'repr_parameters_box'
        return widget

    def _make_button_export_image(self):
        slider_factor = IntSlider(value=4, min=1, max=10, description='scale')
        checkbox_antialias = Checkbox(value=True, description='antialias')
        checkbox_trim = Checkbox(value=False, description='trim')
        checkbox_transparent = Checkbox(value=False, description='transparent')
        filename_text = Text(value='Screenshot', description='Filename')
        delay_text = FloatText(value=1, description='delay (s)', tooltip='hello')

        start_text, stop_text, step_text = (IntText(value=0, description='start'),
                                            IntText(value=self._view.count, description='stop'),
                                            IntText(value=1, description='step'))

        start_text.layout.max_width = stop_text.layout.max_width = step_text.layout.max_width \
                = filename_text.layout.max_width = delay_text.layout.max_width = default.DEFAULT_TEXT_WIDTH

        button_movie_images = Button(description='Export Images')
        def download_image(filename):
            self._view.download_image(factor=slider_factor.value,
                    antialias=checkbox_antialias.value,
                    trim=checkbox_trim.value,
                    transparent=checkbox_transparent.value,
                    filename=filename)

        @button_movie_images.on_click
        def on_click_images(button_movie_images):
            for i in range(start_text.value, stop_text.value, step_text.value):
                self._view.frame = i
                time.sleep(delay_text.value)
                download_image(filename=filename_text.value + str(i))
                time.sleep(delay_text.value)

        vbox = VBox([
            button_movie_images,
            start_text,
            stop_text,
            step_text,
            delay_text,
            filename_text,
            slider_factor,
            checkbox_antialias,
            checkbox_trim,
            checkbox_transparent,
            ])

        form_items = _relayout(vbox, make_form_item_layout())
        form = Box(form_items, layout=_make_box_layout())
        # form = _relayout_master(vbox)
        return form

    def _make_resize_notebook_slider(self):
        resize_notebook_slider = IntSlider(min=300, max=2000, description='resize notebook')
        def on_resize_notebook(change):
            width = change['new']
            self._view._remote_call('resizeNotebook',
                    target='Widget',
                    args=[width,])
        resize_notebook_slider.observe(on_resize_notebook, names='value')
        return resize_notebook_slider

    def _make_add_widget_repr(self, component_slider):
        dropdown_repr_name = Dropdown(options=REPRESENTATION_NAMES, value='cartoon')
        repr_selection = Text(value='*', description='')
        repr_button = Button(description='Add', tooltip="""Add representation.
        You can also hit Enter in selection box""")
        repr_button.layout = Layout(width='auto', flex='1 1 auto')

        dropdown_repr_name.layout.width = repr_selection.layout.width = default.DEFAULT_TEXT_WIDTH

        def on_click_or_submit(button_or_text_area):
            self._view.add_representation(selection=repr_selection.value.strip(),
                    repr_type=dropdown_repr_name.value,
                    component=component_slider.value)

        repr_button.on_click(on_click_or_submit)
        repr_selection.on_submit(on_click_or_submit)
        add_repr_box = HBox([repr_button, dropdown_repr_name, repr_selection])
        add_repr_box._ngl_name = 'add_repr_box'

        return add_repr_box

    def _make_repr_playground(self):
        vbox = VBox()
        children = []

        rep_names = REPRESENTATION_NAMES[:]
        excluded_names = ['ball+stick', 'distance']
        for name in excluded_names:
            rep_names.remove(name)

        repr_selection = Text(value='*')
        repr_selection.layout.width = default.DEFAULT_TEXT_WIDTH
        repr_selection_box  = HBox([Label('selection'), repr_selection])
        setattr(repr_selection_box, 'value', repr_selection.value)

        for index, name in enumerate(rep_names):
            button = ToggleButton(description=name)

            def make_func():
                def on_toggle_button_value_change(change, button=button):
                    selection = repr_selection.value
                    new = change['new'] # True/False
                    if new:
                        self._view.add_representation(button.description, selection=selection)
                    else:
                        self._view._remove_representations_by_name(button.description)
                return on_toggle_button_value_change

            button.observe(make_func(), names='value')
            children.append(button)

        button_clear = Button(description='clear', button_style='info',
                icon='fa-eraser')

        @button_clear.on_click
        def on_clear(button_clear):
            self._view.clear()
            for kid in children:
                # unselect
                kid.value = False

        vbox.children = children + [repr_selection, button_clear]
        _make_autofit(vbox)
        self.widget_quick_repr = vbox
        return self.widget_quick_repr

    def _make_repr_name_choices(self, component_slider, repr_slider):
        repr_choices = Dropdown(options=[" ",])

        def on_chose(change):
            repr_name = change['new']
            repr_index = repr_choices.options.index(repr_name)
            repr_slider.value = repr_index

        repr_choices.observe(on_chose, names='value')
        repr_choices.layout.width = default.DEFAULT_TEXT_WIDTH

        self.widget_repr_choices = repr_choices
        return self.widget_repr_choices

    def _make_drag_widget(self):
        button_drag = Button(description='widget drag: off', tooltip='dangerous')
        drag_nb = Button(description='notebook drag: off', tooltip='dangerous')
        button_reset_notebook = Button(description='notebook: reset', tooltip='reset?')
        button_dialog = Button(description='dialog', tooltip='make a dialog')
        button_split_half = Button(description='split screen', tooltip='try best to make a good layout')

        @button_drag.on_click
        def on_drag(button_drag):
            if button_drag.description == 'widget drag: off':
                self._view._set_draggable(True)
                button_drag.description = 'widget drag: on'
            else:
                self._view._set_draggable(False)
                button_drag.description = 'widget drag: off'

        @drag_nb.on_click
        def on_drag_nb(button_drag):
            if drag_nb.description == 'notebook drag: off':
                js_utils._set_notebook_draggable(True)
                drag_nb.description = 'notebook drag: on'
            else:
                js_utils._set_notebook_draggable(False)
                drag_nb.description = 'notebook drag: off'

        @button_reset_notebook.on_click
        def on_reset(button_reset_notebook):
            js_utils._reset_notebook()

        @button_dialog.on_click
        def on_dialog(button_dialog):
            self._view._remote_call('setDialog', target='Widget')

        @button_split_half.on_click
        def on_split_half(button_dialog):
            from nglview import js_utils
            import time
            js_utils._move_notebook_to_the_left()
            js_utils._set_notebook_width('5%')
            time.sleep(0.1)
            self._view._remote_call('setDialog', target='Widget')

        drag_box = HBox([button_drag, drag_nb, button_reset_notebook,
                        button_dialog, button_split_half])
        drag_box = _make_autofit(drag_box)
        self.widget_drag = drag_box
        return drag_box

    def _make_spin_box(self):
        checkbox_spin = Checkbox(self.spin, description='spin')
        spin_x_slide = IntSlider(
            self._spin_x,
            min=-1,
            max=1,
            description='spin_x')
        spin_y_slide = IntSlider(
            self._spin_y,
            min=-1,
            max=1,
            description='spin_y')
        spin_z_slide = IntSlider(
            self._spin_z,
            min=-1,
            max=1,
            description='spin_z')
        spin_speed_slide = FloatSlider(
            self._spin_speed,
            min=0,
            max=0.2,
            step=0.001,
            description='spin speed')
        # spin
        link((checkbox_spin, 'value'), (self, 'spin'))
        link((spin_x_slide, 'value'), (self, '_spin_x'))
        link((spin_y_slide, 'value'), (self, '_spin_y'))
        link((spin_z_slide, 'value'), (self, '_spin_z'))
        link((spin_speed_slide, 'value'), (self, '_spin_speed'))

        spin_box= VBox([checkbox_spin,
                   spin_x_slide,
                   spin_y_slide,
                   spin_z_slide,
                   spin_speed_slide])
        spin_box = _relayout_master(spin_box, width='75%')
        return spin_box

    def _make_widget_picked(self):
        self.widget_picked = self._make_text_picked()
        picked_box = HBox([self.widget_picked,])
        return _relayout_master(picked_box, width='75%')

    def _make_export_image_widget(self):
        if self.widget_export_image is None:
            self.widget_export_image = HBox([self._make_button_export_image()])
        return self.widget_export_image

    def _make_extra_box(self):
        if self.widget_extra is None:
            extra_list = [(self._make_drag_widget, 'Drag'),
                          (self._make_spin_box, 'Spin'),
                          (self._make_widget_picked, 'Picked'),
                          (self._make_repr_playground, 'Quick'),
                          (self._make_export_image_widget, 'Image'),
                          (self._make_command_box, 'Command')]

            extra_box = _make_delay_tab(extra_list, selected_index=0)
            self.widget_extra = extra_box
        return self.widget_extra

    def _make_theme_box(self):
        if self.widget_theme is None:
            self.widget_theme = Box([self._make_button_theme(),
                                     self._make_button_reset_theme(hide_toolbar=False),
                                     self._make_button_reset_theme(hide_toolbar=True),
                                     self._make_button_clean_error_output()])
        return self.widget_theme

    def _make_general_box(self):
        if self.widget_general is None:
            step_slide = IntSlider(
                value=self.step,
                min=-100,
                max=100,
                description='step')
            delay_text = IntSlider(
                value=self.delay,
                min=10,
                max=1000,
                description='delay')
            toggle_button_interpolate = ToggleButton(self.interpolate, description='Smoothing',
                                                     tooltip='smoothing trajectory')
            link((toggle_button_interpolate, 'value'), (self, 'interpolate'))

            background_color_picker = ColorPicker(value='white', description='background')
            camera_type = Dropdown(value=self.camera,
                                   options=['perspective', 'orthographic'], description='camera')

            link((step_slide, 'value'), (self, 'step'))
            link((delay_text, 'value'), (self, 'delay'))
            link((toggle_button_interpolate, 'value'), (self, 'interpolate'))
            link((camera_type, 'value'), (self, 'camera'))
            link((background_color_picker, 'value'), (self._view, 'background'))

            center_button = self._make_button_center()
            render_button = self._show_download_image()
            qtconsole_button = self._make_button_qtconsole()
            center_render_hbox = _make_autofit(HBox([toggle_button_interpolate, center_button,
                                                     render_button, qtconsole_button]))

            v0_left = VBox([step_slide,
                       delay_text,
                       background_color_picker,
                       camera_type,
                       center_render_hbox,
                       ])

            v0_left = _relayout_master(v0_left, width='100%')
            self.widget_general = v0_left
        return self.widget_general

    def _make_command_box(self):
        widget_text_command = Text()

        @widget_text_command.on_submit
        def _on_submit_command(_):
            command = widget_text_command.value
            js_utils.execute(command)
            widget_text_command.value = ''
        return widget_text_command

    def _create_all_tabs(self):
        tab = self._display()
        for index, _ in enumerate(tab.children):
            # trigger ceating widgets
            tab.selected_index = index

        self.widget_extra = self._make_extra_box()
        for index, _ in enumerate(self.widget_extra.children):
            self.widget_extra.selected_index = index

    def _simplify_repr_control(self):
        for widget in self.widget_repr._saved_widgets:
            if not isinstance(widget, Tab):
                widget.layout.display = 'none'
        self.widget_repr_choices.layout.display = 'flex'
        self.widget_accordion_repr_parameters.selected_index = 0