Ejemplo n.º 1
0
class FileChooser(VBox, ValueWidget):
    """FileChooser class."""

    _LBL_TEMPLATE = '<span style="margin-left:10px; color:{1};">{0}</span>'
    _LBL_NOFILE = 'No file selected'

    def __init__(
            self,
            path: str = os.getcwd(),
            filename: str = '',
            title: str = '',
            select_desc: str = 'Select',
            change_desc: str = 'Change',
            show_hidden: bool = False,
            select_default: bool = False,
            use_dir_icons: bool = False,
            show_only_dirs: bool = False,
            filter_pattern: Optional[Sequence[str]] = None,
            **kwargs):
        """Initialize FileChooser object."""
        self._default_path = path.rstrip(os.path.sep)
        self._default_filename = filename
        self._selected_path = None
        self._selected_filename = None
        self._show_hidden = show_hidden
        self._select_desc = select_desc
        self._change_desc = change_desc
        self._select_default = select_default
        self._use_dir_icons = use_dir_icons
        self._show_only_dirs = show_only_dirs
        self._filter_pattern = filter_pattern
        self._callback: Optional[Callable] = None

        # Widgets
        self._pathlist = Dropdown(
            description="",
            layout=Layout(
                width='auto',
                grid_area='pathlist'
            )
        )
        self._filename = Text(
            placeholder='output filename',
            layout=Layout(
                width='auto',
                grid_area='filename',
                display=(None, "none")[self._show_only_dirs]
            ),
            disabled=self._show_only_dirs
        )
        self._dircontent = Select(
            rows=8,
            layout=Layout(
                width='auto',
                grid_area='dircontent'
            )
        )
        self._cancel = Button(
            description='Cancel',
            layout=Layout(
                width='auto',
                display='none'
            )
        )
        self._select = Button(
            description=self._select_desc,
            layout=Layout(width='auto')
        )
        self._title = HTML(
            value=title
        )

        if title == '':
            self._title.layout.display = 'none'

        # Widget observe handlers
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')
        self._select.on_click(self._on_select_click)
        self._cancel.on_click(self._on_cancel_click)

        # Selected file label
        self._label = HTML(
            value=self._LBL_TEMPLATE.format(self._LBL_NOFILE, 'black'),
            placeholder='',
            description=''
        )

        # Layout
        self._gb = GridBox(
            children=[
                self._pathlist,
                self._filename,
                self._dircontent
            ],
            layout=Layout(
                display='none',
                width='500px',
                grid_gap='0px 0px',
                grid_template_rows='auto auto',
                grid_template_columns='60% 40%',
                grid_template_areas='''
                    'pathlist {}'
                    'dircontent dircontent'
                    '''.format(('filename', 'pathlist')[self._show_only_dirs])
            )
        )

        buttonbar = HBox(
            children=[
                self._select,
                self._cancel,
                self._label
            ],
            layout=Layout(width='auto')
        )

        # Call setter to set initial form values
        self._set_form_values(self._default_path, self._default_filename)

        # Use the defaults as the selected values
        if self._select_default:
            self._apply_selection()

        # Call VBox super class __init__
        super().__init__(
            children=[
                self._title,
                self._gb,
                buttonbar
            ],
            layout=Layout(width='auto'),
            **kwargs
        )

    def _set_form_values(self, path: str, filename: str) -> None:
        """Set the form values."""
        # Disable triggers to prevent selecting an entry in the Select
        # box from automatically triggering a new event.
        self._pathlist.unobserve(self._on_pathlist_select, names='value')
        self._dircontent.unobserve(self._on_dircontent_select, names='value')
        self._filename.unobserve(self._on_filename_change, names='value')

        # In folder only mode zero out the filename
        if self._show_only_dirs:
            filename = ''

        # Set form values
        self._pathlist.options = get_subpaths(path)
        self._pathlist.value = path
        self._filename.value = filename

        # file/folder real names
        dircontent_real_names = get_dir_contents(
            path,
            show_hidden=self._show_hidden,
            prepend_icons=False,
            show_only_dirs=self._show_only_dirs,
            filter_pattern=self._filter_pattern
        )

        # file/folder display names
        dircontent_display_names = get_dir_contents(
            path,
            show_hidden=self._show_hidden,
            prepend_icons=self._use_dir_icons,
            show_only_dirs=self._show_only_dirs,
            filter_pattern=self._filter_pattern
        )

        # Dict to map real names to display names
        self._map_name_to_disp = {
            real_name: disp_name
            for real_name, disp_name in zip(
                dircontent_real_names,
                dircontent_display_names
            )
        }

        # Dict to map display names to real names
        self._map_disp_to_name = {
            disp_name: real_name
            for real_name, disp_name in self._map_name_to_disp.items()
        }

        # Set _dircontent form value to display names
        self._dircontent.options = dircontent_display_names

        # If the value in the filename Text box equals a value in the
        # Select box and the entry is a file then select the entry.
        if ((filename in dircontent_real_names) and os.path.isfile(os.path.join(path, filename))):
            self._dircontent.value = self._map_name_to_disp[filename]
        else:
            self._dircontent.value = None

        # Reenable triggers again
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')

        # Update the state of the select button
        if self._gb.layout.display is None:
            # Disable the select button if path and filename
            # - equal an existing folder in the current view
            # - equal the already selected values
            # - don't match the provided filter pattern(s)
            check1 = filename in dircontent_real_names
            check2 = os.path.isdir(os.path.join(path, filename))
            check3 = False
            check4 = False

            # Only check selected if selected is set
            if ((self._selected_path is not None) and (self._selected_filename is not None)):
                selected = os.path.join(self._selected_path, self._selected_filename)
                check3 = os.path.join(path, filename) == selected

            # Ensure only allowed extensions are used
            if self._filter_pattern:
                check4 = not match_item(filename, self._filter_pattern)

            if (check1 and check2) or check3 or check4:
                self._select.disabled = True
            else:
                self._select.disabled = False

    def _on_pathlist_select(self, change: Mapping[str, str]) -> None:
        """Handle selecting a path entry."""
        self._set_form_values(change['new'], self._filename.value)

    def _on_dircontent_select(self, change: Mapping[str, str]) -> None:
        """Handle selecting a folder entry."""
        new_path = os.path.realpath(os.path.join(self._pathlist.value, self._map_disp_to_name[change['new']]))

        # Check if folder or file
        if os.path.isdir(new_path):
            path = new_path
            filename = self._filename.value
        else:
            path = self._pathlist.value
            filename = self._map_disp_to_name[change['new']]

        self._set_form_values(path, filename)

    def _on_filename_change(self, change: Mapping[str, str]) -> None:
        """Handle filename field changes."""
        self._set_form_values(self._pathlist.value, change['new'])

    def _on_select_click(self, _b) -> None:
        """Handle select button clicks."""
        if self._gb.layout.display == 'none':
            # If not shown, open the dialog
            self._show_dialog()
        else:
            # If shown, close the dialog and apply the selection
            self._apply_selection()

            # Execute callback function
            if self._callback is not None:
                try:
                    self._callback(self)
                except TypeError:
                    # Support previous behaviour of not passing self
                    self._callback()

    def _show_dialog(self) -> None:
        """Show the dialog."""
        # Show dialog and cancel button
        self._gb.layout.display = None
        self._cancel.layout.display = None

        # Show the form with the correct path and filename
        if ((self._selected_path is not None) and (self._selected_filename is not None)):
            path = self._selected_path
            filename = self._selected_filename
        else:
            path = self._default_path
            filename = self._default_filename

        self._set_form_values(path, filename)

    def _apply_selection(self) -> None:
        """Close the dialog and apply the selection."""
        self._selected_path = self._pathlist.value
        self._selected_filename = self._filename.value

        if ((self._selected_path is not None) and (self._selected_filename is not None)):
            selected = os.path.join(self._selected_path, self._selected_filename)
            self._gb.layout.display = 'none'
            self._cancel.layout.display = 'none'
            self._select.description = self._change_desc

            if os.path.isfile(selected):
                self._label.value = self._LBL_TEMPLATE.format(selected, 'orange')
            else:
                self._label.value = self._LBL_TEMPLATE.format(selected, 'green')

    def _on_cancel_click(self, _b) -> None:
        """Handle cancel button clicks."""
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'
        self._select.disabled = False

    def reset(self, path: Optional[str] = None, filename: Optional[str] = None) -> None:
        """Reset the form to the default path and filename."""
        self._selected_path = None
        self._selected_filename = None

        # Reset select button and label
        self._select.description = self._select_desc
        self._label.value = self._LBL_TEMPLATE.format(self._LBL_NOFILE, 'black')

        if path is not None:
            self._default_path = path.rstrip(os.path.sep)

        if filename is not None:
            self._default_filename = filename

        # Set a proper filename value
        if self._show_only_dirs:
            filename = ''
        else:
            filename = self._default_filename

        self._set_form_values(self._default_path, filename)

        # Use the defaults as the selected values
        if self._select_default:
            self._apply_selection()

    def refresh(self) -> None:
        """Re-render the form."""
        self._set_form_values(self._pathlist.value, self._filename.value)

    @property
    def show_hidden(self) -> bool:
        """Get _show_hidden value."""
        return self._show_hidden

    @show_hidden.setter
    def show_hidden(self, hidden: bool) -> None:
        """Set _show_hidden value."""
        self._show_hidden = hidden
        self.refresh()

    @property
    def use_dir_icons(self) -> bool:
        """Get _use_dir_icons value."""
        return self._use_dir_icons

    @use_dir_icons.setter
    def use_dir_icons(self, dir_icons: bool) -> None:
        """Set _use_dir_icons value."""
        self._use_dir_icons = dir_icons
        self.refresh()

    @property
    def rows(self) -> int:
        """Get current number of rows."""
        return self._dircontent.rows

    @rows.setter
    def rows(self, rows: int) -> None:
        """Set number of rows."""
        self._dircontent.rows = rows

    @property
    def title(self) -> str:
        """Get the title."""
        return self._title.value

    @title.setter
    def title(self, title: str) -> None:
        """Set the title."""
        self._title.value = title

        if title == '':
            self._title.layout.display = 'none'
        else:
            self._title.layout.display = None

    @property
    def default(self) -> str:
        """Get the default value."""
        return os.path.join(self._default_path, self._default_filename)

    @property
    def default_path(self) -> str:
        """Get the default_path value."""
        return self._default_path

    @default_path.setter
    def default_path(self, path: str) -> None:
        """Set the default_path."""
        self._default_path = path.rstrip(os.path.sep)
        self._set_form_values(self._default_path, self._filename.value)

    @property
    def default_filename(self) -> str:
        """Get the default_filename value."""
        return self._default_filename

    @default_filename.setter
    def default_filename(self, filename: str) -> None:
        """Set the default_filename."""
        self._default_filename = filename
        self._set_form_values(self._pathlist.value, self._default_filename)

    @property
    def show_only_dirs(self) -> bool:
        """Get show_only_dirs property value."""
        return self._show_only_dirs

    @show_only_dirs.setter
    def show_only_dirs(self, show_only_dirs: bool) -> None:
        """Set show_only_dirs property value."""
        self._show_only_dirs = show_only_dirs

        # Update widget layout
        self._filename.disabled = self._show_only_dirs
        self._filename.layout.display = (None, "none")[self._show_only_dirs]
        self._gb.layout.children = [
            self._pathlist,
            self._dircontent
        ]

        if not self._show_only_dirs:
            self._gb.layout.children.insert(1, self._filename)

        self._gb.layout.grid_template_areas = '''
            'pathlist {}'
            'dircontent dircontent'
            '''.format(('filename', 'pathlist')[self._show_only_dirs])

        # Reset the dialog
        self.reset()

    @property
    def filter_pattern(self) -> Optional[Sequence[str]]:
        """Get file name filter pattern."""
        return self._filter_pattern

    @filter_pattern.setter
    def filter_pattern(self, filter_pattern: Optional[Sequence[str]]) -> None:
        """Set file name filter pattern."""
        self._filter_pattern = filter_pattern
        self.refresh()

    @property
    def selected(self) -> Optional[str]:
        """Get selected value."""
        selected = None

        if ((self._selected_path is not None) and (self._selected_filename is not None)):
            selected = os.path.join(self._selected_path, self._selected_filename)

        return selected

    @property
    def selected_path(self) -> Optional[str]:
        """Get selected_path value."""
        return self._selected_path

    @property
    def selected_filename(self) -> Optional[str]:
        """Get the selected_filename."""
        return self._selected_filename

    def __repr__(self) -> str:
        """Build string representation."""
        str_ = (
            "FileChooser("
            "path='{0}', "
            "filename='{1}', "
            "title='{2}', "
            "show_hidden='{3}', "
            "use_dir_icons='{4}', "
            "show_only_dirs='{5}', "
            "select_desc='{6}', "
            "change_desc='{7}')"
        ).format(
            self._default_path,
            self._default_filename,
            self._title,
            self._show_hidden,
            self._use_dir_icons,
            self._show_only_dirs,
            self._select_desc,
            self._change_desc
        )
        return str_

    def register_callback(self, callback: Callable[[Optional['FileChooser']], None]) -> None:
        """Register a callback function."""
        self._callback = callback

    def get_interact_value(self) -> Optional[str]:
        """Return the value which should be passed to interactive functions."""
        return self.selected
class FileChooser(VBox):
    """FileChooser class."""

    _LBL_TEMPLATE = '<span style="margin-left:10px; color:{1};">{0}</span>'
    _LBL_NOFILE = 'No file selected'

    def __init__(self,
                 path=os.getcwd(),
                 filename='',
                 title='',
                 select_desc='Select',
                 change_desc='Change',
                 show_hidden=False,
                 select_default=False,
                 use_dir_icons=False,
                 **kwargs):
        """Initialize FileChooser object."""
        self._default_path = path.rstrip(os.path.sep)
        self._default_filename = filename
        self._selected_path = None
        self._selected_filename = None
        self._show_hidden = show_hidden
        self._select_desc = select_desc
        self._change_desc = change_desc
        self._callback = None
        self._use_dir_icons = use_dir_icons

        # Widgets
        self._pathlist = Dropdown(description="",
                                  layout=Layout(width='auto',
                                                grid_area='pathlist'))
        self._filename = Text(placeholder='output filename',
                              layout=Layout(width='auto',
                                            grid_area='filename'))
        self._dircontent = Select(rows=8,
                                  layout=Layout(width='auto',
                                                grid_area='dircontent'))
        self._cancel = Button(description='Cancel',
                              layout=Layout(width='auto', display='none'))
        self._select = Button(description=self._select_desc,
                              layout=Layout(width='auto'))

        self._title = HTML(value=title)

        if title == '':
            self._title.layout.display = 'none'

        # Widget observe handlers
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')
        self._select.on_click(self._on_select_click)
        self._cancel.on_click(self._on_cancel_click)

        # Selected file label
        self._label = HTML(value=self._LBL_TEMPLATE.format(
            self._LBL_NOFILE, 'black'),
                           placeholder='',
                           description='')

        # Layout
        self._gb = GridBox(
            children=[self._pathlist, self._filename, self._dircontent],
            layout=Layout(display='none',
                          width='500px',
                          grid_gap='0px 0px',
                          grid_template_rows='auto auto',
                          grid_template_columns='60% 40%',
                          grid_template_areas='''
                    'pathlist filename'
                    'dircontent dircontent'
                    '''))
        buttonbar = HBox(children=[self._select, self._cancel, self._label],
                         layout=Layout(width='auto'))

        # Call setter to set initial form values
        self._set_form_values(self._default_path, self._default_filename)

        # Use the defaults as the selected values
        if select_default:
            self._apply_selection()

        # Call VBox super class __init__
        super().__init__(children=[
            self._title,
            self._gb,
            buttonbar,
        ],
                         layout=Layout(width='auto'),
                         **kwargs)

    def _set_form_values(self, path, filename):
        """Set the form values."""
        # Disable triggers to prevent selecting an entry in the Select
        # box from automatically triggering a new event.
        self._pathlist.unobserve(self._on_pathlist_select, names='value')
        self._dircontent.unobserve(self._on_dircontent_select, names='value')
        self._filename.unobserve(self._on_filename_change, names='value')

        # Set form values
        self._pathlist.options = get_subpaths(path)
        self._pathlist.value = path
        self._filename.value = filename

        # file/folder real names
        dircontent_real_names = get_dir_contents(path,
                                                 hidden=self._show_hidden,
                                                 prepend_icons=False)

        # file/folder display names
        dircontent_display_names = get_dir_contents(
            path, hidden=self._show_hidden, prepend_icons=self._use_dir_icons)

        # Dict to map real names to display names
        self._map_name_to_disp = {
            real_name: disp_name
            for real_name, disp_name in zip(dircontent_real_names,
                                            dircontent_display_names)
        }

        # Dict to map display names to real names
        self._map_disp_to_name = dict(
            reversed(item) for item in self._map_name_to_disp.items())

        # Set _dircontent form value to display names
        self._dircontent.options = dircontent_display_names

        # If the value in the filename Text box equals a value in the
        # Select box and the entry is a file then select the entry.
        if ((filename in dircontent_real_names)
                and os.path.isfile(os.path.join(path, filename))):
            self._dircontent.value = self._map_name_to_disp[filename]
        else:
            self._dircontent.value = None

        # Reenable triggers again
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')

        # Update the state of the select button
        if self._gb.layout.display is None:
            # Disable the select button if path and filename
            # - equal an existing folder in the current view
            # - equal the already selected values
            check1 = filename in dircontent_real_names
            check2 = os.path.isdir(os.path.join(path, filename))
            check3 = False

            # Only check selected if selected is set
            if ((self._selected_path is not None)
                    and (self._selected_filename is not None)):
                selected = os.path.join(self._selected_path,
                                        self._selected_filename)
                check3 = os.path.join(path, filename) == selected

            if (check1 and check2) or check3:
                self._select.disabled = True
            else:
                self._select.disabled = False

    def _on_pathlist_select(self, change):
        """Handle selecting a path entry."""
        self._set_form_values(change['new'], self._filename.value)

    def _on_dircontent_select(self, change):
        """Handle selecting a folder entry."""
        new_path = os.path.realpath(
            os.path.join(self._pathlist.value,
                         self._map_disp_to_name[change['new']]))

        # Check if folder or file
        if os.path.isdir(new_path):
            path = new_path
            filename = self._filename.value
        elif os.path.isfile(new_path):
            path = self._pathlist.value
            filename = self._map_disp_to_name[change['new']]

        self._set_form_values(path, filename)

    def _on_filename_change(self, change):
        """Handle filename field changes."""
        self._set_form_values(self._pathlist.value, change['new'])

    def _on_select_click(self, b):
        """Handle select button clicks."""
        if self._gb.layout.display == 'none':
            # If not shown, open the dialog
            self._show_dialog()
        else:
            # If shown, close the dialog and apply the selection
            self._apply_selection()

            # Execute callback function
            if self._callback is not None:
                try:
                    self._callback(self)
                except TypeError:
                    # Support previous behaviour of not passing self
                    self._callback()

    def _show_dialog(self):
        """Show the dialog."""
        # Show dialog and cancel button
        self._gb.layout.display = None
        self._cancel.layout.display = None

        # Show the form with the correct path and filename
        if ((self._selected_path is not None)
                and (self._selected_filename is not None)):
            path = self._selected_path
            filename = self._selected_filename
        else:
            path = self._default_path
            filename = self._default_filename

        self._set_form_values(path, filename)

    def _apply_selection(self):
        """Close the dialog and apply the selection."""
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'
        self._select.description = self._change_desc
        self._selected_path = self._pathlist.value
        self._selected_filename = self._filename.value

        selected = os.path.join(self._selected_path, self._selected_filename)

        if os.path.isfile(selected):
            self._label.value = self._LBL_TEMPLATE.format(selected, 'orange')
        else:
            self._label.value = self._LBL_TEMPLATE.format(selected, 'green')

    def _on_cancel_click(self, b):
        """Handle cancel button clicks."""
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'
        self._select.disabled = False

    def reset(self, path=None, filename=None):
        """Reset the form to the default path and filename."""
        self._selected_path = ''
        self._selected_filename = ''

        self._label.value = self._LBL_TEMPLATE.format(self._LBL_NOFILE,
                                                      'black')

        if path is not None:
            self._default_path = path.rstrip(os.path.sep)

        if filename is not None:
            self._default_filename = filename

        self._set_form_values(self._default_path, self._default_filename)

    def refresh(self):
        """Re-render the form."""
        self._set_form_values(self._pathlist.value, self._filename.value)

    @property
    def show_hidden(self):
        """Get _show_hidden value."""
        return self._show_hidden

    @show_hidden.setter
    def show_hidden(self, hidden):
        """Set _show_hidden value."""
        self._show_hidden = hidden
        self.refresh()

    @property
    def use_dir_icons(self):
        """Get _use_dir_icons value."""
        return self._use_dir_icons

    @use_dir_icons.setter
    def use_dir_icons(self, dir_icons):
        """Set _use_dir_icons value."""
        self._use_dir_icons = dir_icons
        self.refresh()

    @property
    def rows(self):
        """Get current number of rows."""
        return self._dircontent.rows

    @rows.setter
    def rows(self, rows):
        """Set number of rows."""
        self._dircontent.rows = rows

    @property
    def title(self):
        """Get the title."""
        return self._title.value

    @title.setter
    def title(self, title):
        """Set the title."""
        self._title.value = title

        if title == '':
            self._title.layout.display = 'none'
        else:
            self._title.layout.display = None

    @property
    def default(self):
        """Get the default value."""
        return os.path.join(self._default_path, self._default_filename)

    @property
    def default_path(self):
        """Get the default_path value."""
        return self._default_path

    @default_path.setter
    def default_path(self, path):
        """Set the default_path."""
        self._default_path = path.rstrip(os.path.sep)
        self._set_form_values(self._default_path, self._filename.value)

    @property
    def default_filename(self):
        """Get the default_filename value."""
        return self._default_filename

    @default_filename.setter
    def default_filename(self, filename):
        """Set the default_filename."""
        self._default_filename = filename
        self._set_form_values(self._pathlist.value, self._default_filename)

    @property
    def selected(self):
        """Get selected value."""
        try:
            return os.path.join(self._selected_path, self._selected_filename)
        except TypeError:
            return None

    @property
    def selected_path(self):
        """Get selected_path value."""
        return self._selected_path

    @property
    def selected_filename(self):
        """Get the selected_filename."""
        return self._selected_filename

    def __repr__(self):
        """Build string representation."""
        str_ = ("FileChooser("
                "path='{0}', "
                "filename='{1}', "
                "show_hidden='{2}')").format(self._default_path,
                                             self._default_filename,
                                             self._show_hidden)
        return str_

    def register_callback(self, callback):
        """Register a callback function."""
        self._callback = callback
Ejemplo n.º 3
0
class FileChooser(VBox):

    _LBL_TEMPLATE = '<span style="margin-left:10px; color:{1};">{0}</span>'
    _LBL_NOFILE = 'No file selected'
    _LBL_NOFOLDER = 'No folder selected'

    def __init__(self,
                 path=os.getcwd(),
                 filename='',
                 title='',
                 select_desc='Select',
                 change_desc='Change',
                 show_hidden=False,
                 include_files=True,
                 include_folders=True,
                 **kwargs):

        self._default_path = path  # path.rstrip(os.path.sep)
        self._default_filename = filename
        self._selected_path = ''
        self._selected_filename = ''
        self._show_hidden = show_hidden
        self._select_desc = select_desc
        self._change_desc = change_desc
        self._include_files = include_files
        self._include_folders = include_folders

        if not include_folders and not include_files:
            raise ValueError("You can't exclude both files and folders")

        # Widgets
        self._pathlist = Dropdown(description="",
                                  layout=Layout(width='auto',
                                                grid_area='pathlist'))
        self._filename = Text(
            placeholder='output filename',
            layout=Layout(
                width='auto',
                grid_area='filename',
                visibility='visible' if self._include_files else 'hidden',
            ),
        )
        self._dircontent = Select(rows=8,
                                  layout=Layout(width='auto',
                                                grid_area='dircontent'))
        self._cancel = Button(description='Cancel',
                              layout=Layout(width='auto', display='none'))
        self._select = Button(description=self._select_desc,
                              layout=Layout(width='auto'))

        self._title = HTML(value=title)

        if title is '':
            self._title.layout.display = 'none'

        # Widget observe handlers
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')
        self._select.on_click(self._on_select_click)
        self._cancel.on_click(self._on_cancel_click)

        # Selected file label
        self._label = HTML(value=self._LBL_TEMPLATE.format(
            self._LBL_NOFILE if self._include_files else
            self._LBL_NOFOLDER if self._include_folders else "", 'black'),
                           placeholder='',
                           description='')

        # Layout
        self._gb = GridBox(
            children=[self._pathlist, self._filename, self._dircontent],
            layout=Layout(display='none',
                          width='500px',
                          grid_gap='0px 0px',
                          grid_template_rows='auto auto',
                          grid_template_columns='60% 40%',
                          grid_template_areas='''
                    'pathlist filename'
                    'dircontent dircontent'
                    '''))
        buttonbar = HBox(children=[self._select, self._cancel, self._label],
                         layout=Layout(width='auto'))

        # Call setter to set initial form values
        self._set_form_values(self._default_path, self._default_filename)

        # Call VBox super class __init__
        kwargs.setdefault('layout', Layout(width='auto'))
        super().__init__(children=[
            self._title,
            self._gb,
            buttonbar,
        ],
                         **kwargs)

    def _set_form_values(self, path, filename):
        """Set the form values"""

        # Disable triggers to prevent selecting an entry in the Select
        # box from automatically triggering a new event.
        self._pathlist.unobserve(self._on_pathlist_select, names='value')
        self._dircontent.unobserve(self._on_dircontent_select, names='value')
        self._filename.unobserve(self._on_filename_change, names='value')

        # Set form values
        self._pathlist.options = get_subpaths(path)
        self._pathlist.value = path
        self._filename.value = filename
        self._dircontent.options = get_dir_contents(
            path,
            hidden=self._show_hidden,
            include_files=self._include_files,
            include_folders=self._include_folders)

        # If the value in the filename Text box equals a value in the
        # Select box and the entry is a file then select the entry.
        if ((filename in self._dircontent.options)
                and os.path.isfile(os.path.join(path, filename))):
            self._dircontent.value = filename
        else:
            self._dircontent.value = None

        # Reenable triggers again
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')

        # Set the state of the select Button
        if self._gb.layout.display is None:
            selected = os.path.join(self._selected_path,
                                    self._selected_filename)

            # filename value is empty or equals the selected value
            if ((filename == '') or
                (os.path.join(path, filename) == selected)) \
                    and self._include_files:
                self._select.disabled = True
            else:
                self._select.disabled = False

    def _on_pathlist_select(self, change):
        """Handler for when a new path is selected"""
        self._set_form_values(change['new'], self._filename.value)

    def _on_dircontent_select(self, change):
        """Handler for when a folder entry is selected"""
        new_path = update_path(self._pathlist.value, change['new'])

        # Check if folder or file
        if os.path.isdir(new_path):
            path = new_path
            filename = self._filename.value
        elif os.path.isfile(new_path):
            path = self._pathlist.value
            filename = change['new']

        self._set_form_values(path, filename)

    def _on_filename_change(self, change):
        """Handler for when the filename field changes"""
        self._set_form_values(self._pathlist.value, change['new'])

    def _on_select_click(self, b):
        """Handler for when the select button is clicked"""
        if self._gb.layout.display is 'none':
            self._gb.layout.display = None
            self._cancel.layout.display = None

            # Show the form with the correct path and filename
            if (self._selected_path and self._selected_filename) or \
                    (self._selected_path and not self._include_files):
                path = self._selected_path
                filename = self._selected_filename
            else:
                path = self._default_path
                filename = self._default_filename

            self._set_form_values(path, filename)

        else:
            self._gb.layout.display = 'none'
            self._cancel.layout.display = 'none'
            self._select.description = self._change_desc
            self._selected_path = self._pathlist.value
            self._selected_filename = self._filename.value
            # self._default_path = self._selected_path
            # self._default_filename = self._selected_filename

            selected = os.path.join(self._selected_path,
                                    self._selected_filename)

            if not self._include_files and os.path.isdir(selected):
                selected = selected.rstrip(os.path.sep)

            if os.path.isfile(selected):
                self._label.value = self._LBL_TEMPLATE.format(
                    selected, 'orange')
            else:
                self._label.value = self._LBL_TEMPLATE.format(
                    selected, 'green')

    def _on_cancel_click(self, b):
        """Handler for when the cancel button is clicked"""
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'
        self._select.disabled = False

    def reset(self, path=None, filename=None):
        """Reset the form to the default path and filename"""
        self._selected_path = ''
        self._selected_filename = ''

        self._label.value = self._LBL_TEMPLATE.format(
            self._LBL_NOFILE if self._include_files else
            self._LBL_NOFOLDER if self._include_folders else "", 'black')

        if path is not None:
            self._default_path = path.rstrip(os.path.sep)

        if filename is not None:
            self._default_filename = filename

        self._set_form_values(self._default_path, self._default_filename)

    def refresh(self):
        """Re-render the form"""
        self._set_form_values(self._pathlist.value, self._filename.value)

    @property
    def show_hidden(self):
        """Get current number of rows"""
        return self._show_hidden

    @show_hidden.setter
    def show_hidden(self, hidden):
        """Set number of rows"""
        self._show_hidden = hidden
        self.refresh()

    @property
    def rows(self):
        """Get current number of rows"""
        return self._dircontent.rows

    @rows.setter
    def rows(self, rows):
        """Set number of rows"""
        self._dircontent.rows = rows

    @property
    def title(self):
        """Get the title"""
        return self._title.value

    @title.setter
    def title(self, title):
        """Set the title"""
        self._title.value = title

        if title is '':
            self._title.layout.display = 'none'
        else:
            self._title.layout.display = None

    @property
    def default(self):
        """Get the default value"""
        return os.path.join(self._default_path, self._default_filename)

    @property
    def default_path(self):
        """Get the default_path value"""
        return self._default_path

    @default_path.setter
    def default_path(self, path):
        """Set the default_path"""
        self._default_path = path.rstrip(os.path.sep)
        self._default = os.path.join(self._default_path, self._filename.value)
        self._set_form_values(self._default_path, self._filename.value)

    @property
    def default_filename(self):
        """Get the default_filename value"""
        return self._default_filename

    @default_filename.setter
    def default_filename(self, filename):
        """Set the default_filename"""
        self._default_filename = filename
        self._default = os.path.join(self._pathlist.value,
                                     self._default_filename)
        self._set_form_values(self._pathlist.value, self._default_filename)

    @property
    def selected(self):
        """Get selected value"""
        return os.path.join(self._selected_path, self._selected_filename)

    @property
    def selected_path(self):
        """Get selected_path value"""
        return self._selected_path

    @property
    def selected_filename(self):
        """Get the selected_filename"""
        return self._selected_filename

    def __repr__(self):
        str_ = ("FileChooser("
                "path='{0}', "
                "filename='{1}', "
                "show_hidden='{2}')").format(self._default_path,
                                             self._default_filename,
                                             self._show_hidden)
        return str_
Ejemplo n.º 4
0
class FileChooser(VBox, ValueWidget):
    """FileChooser class."""

    _LBL_TEMPLATE = '<span style="color:{1};">{0}</span>'
    _LBL_NOFILE = 'No selection'

    def __init__(self,
                 path: str = os.getcwd(),
                 filename: str = '',
                 title: str = '',
                 select_desc: str = 'Select',
                 change_desc: str = 'Change',
                 show_hidden: bool = False,
                 select_default: bool = False,
                 dir_icon: Optional[str] = '\U0001F4C1 ',
                 dir_icon_append: bool = False,
                 show_only_dirs: bool = False,
                 filter_pattern: Optional[Sequence[str]] = None,
                 sandbox_path: Optional[str] = None,
                 layout: Layout = Layout(width='500px'),
                 **kwargs):
        """Initialize FileChooser object."""
        # Check if path and sandbox_path align
        if sandbox_path and not has_parent_path(normalize_path(path),
                                                normalize_path(sandbox_path)):
            raise ParentPathError(path, sandbox_path)

        # Verify the filename is valid
        if not is_valid_filename(filename):
            raise InvalidFileNameError(filename)

        self._default_path = normalize_path(path)
        self._default_filename = filename
        self._selected_path: Optional[str] = None
        self._selected_filename: Optional[str] = None
        self._show_hidden = show_hidden
        self._select_desc = select_desc
        self._change_desc = change_desc
        self._select_default = select_default
        self._dir_icon = dir_icon
        self._dir_icon_append = dir_icon_append
        self._show_only_dirs = show_only_dirs
        self._filter_pattern = filter_pattern
        self._sandbox_path = normalize_path(
            sandbox_path) if sandbox_path is not None else None
        self._callback: Optional[Callable] = None

        # Widgets
        self._pathlist = Dropdown(description="",
                                  layout=Layout(width='auto',
                                                grid_area='pathlist'))
        self._filename = Text(
            placeholder='output filename',
            layout=Layout(width='auto',
                          grid_area='filename',
                          display=(None, "none")[self._show_only_dirs]),
            disabled=self._show_only_dirs)
        self._dircontent = Select(rows=8,
                                  layout=Layout(width='auto',
                                                grid_area='dircontent'))
        self._cancel = Button(description='Cancel',
                              layout=Layout(min_width='6em',
                                            width='6em',
                                            display='none'))
        self._select = Button(description=self._select_desc,
                              layout=Layout(min_width='6em', width='6em'))
        self._title = HTML(value=title)

        if title == '':
            self._title.layout.display = 'none'

        # Widget observe handlers
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')
        self._select.on_click(self._on_select_click)
        self._cancel.on_click(self._on_cancel_click)

        # Selected file label
        self._label = HTML(value=self._LBL_TEMPLATE.format(
            self._LBL_NOFILE, 'black'),
                           placeholder='',
                           description='',
                           layout=Layout(margin='0 0 0 1em'))

        # Layout
        self._gb = GridBox(
            children=[self._pathlist, self._filename, self._dircontent],
            layout=Layout(display='none',
                          width='auto',
                          grid_gap='0px 0px',
                          grid_template_rows='auto auto',
                          grid_template_columns='60% 40%',
                          grid_template_areas='''
                    'pathlist {}'
                    'dircontent dircontent'
                    '''.format(
                              ('filename', 'pathlist')[self._show_only_dirs])))

        buttonbar = HBox(children=[
            self._select, self._cancel,
            Box([self._label], layout=Layout(overflow='auto'))
        ],
                         layout=Layout(width='auto'))

        # Call setter to set initial form values
        self._set_form_values(self._default_path, self._default_filename)

        # Use the defaults as the selected values
        if self._select_default:
            self._apply_selection()

        # Call VBox super class __init__
        super().__init__(children=[self._title, self._gb, buttonbar],
                         layout=layout,
                         **kwargs)

    def _set_form_values(self, path: str, filename: str) -> None:
        """Set the form values."""
        # Check if the path falls inside the configured sandbox path
        if self._sandbox_path and not has_parent_path(path,
                                                      self._sandbox_path):
            raise ParentPathError(path, self._sandbox_path)

        # Disable triggers to prevent selecting an entry in the Select
        # box from automatically triggering a new event.
        self._pathlist.unobserve(self._on_pathlist_select, names='value')
        self._dircontent.unobserve(self._on_dircontent_select, names='value')
        self._filename.unobserve(self._on_filename_change, names='value')

        try:
            # Fail early if the folder can not be read
            _ = os.listdir(path)

            # In folder only mode zero out the filename
            if self._show_only_dirs:
                filename = ''

            # Set form values
            restricted_path = self._restrict_path(path)
            subpaths = get_subpaths(restricted_path)

            if os.path.splitdrive(subpaths[-1])[0]:
                # Add missing Windows drive letters
                drives = get_drive_letters()
                subpaths.extend(list(set(drives) - set(subpaths)))

            self._pathlist.options = subpaths
            self._pathlist.value = restricted_path
            self._filename.value = filename

            # file/folder real names
            dircontent_real_names = get_dir_contents(
                path,
                show_hidden=self._show_hidden,
                show_only_dirs=self._show_only_dirs,
                dir_icon=None,
                filter_pattern=self._filter_pattern,
                top_path=self._sandbox_path)

            # file/folder display names
            dircontent_display_names = get_dir_contents(
                path,
                show_hidden=self._show_hidden,
                show_only_dirs=self._show_only_dirs,
                dir_icon=self._dir_icon,
                dir_icon_append=self._dir_icon_append,
                filter_pattern=self._filter_pattern,
                top_path=self._sandbox_path)

            # Dict to map real names to display names
            self._map_name_to_disp = {
                real_name: disp_name
                for real_name, disp_name in zip(dircontent_real_names,
                                                dircontent_display_names)
            }

            # Dict to map display names to real names
            self._map_disp_to_name = {
                disp_name: real_name
                for real_name, disp_name in self._map_name_to_disp.items()
            }

            # Set _dircontent form value to display names
            self._dircontent.options = dircontent_display_names

            # If the value in the filename Text box equals a value in the
            # Select box and the entry is a file then select the entry.
            if ((filename in dircontent_real_names)
                    and os.path.isfile(os.path.join(path, filename))):
                self._dircontent.value = self._map_name_to_disp[filename]
            else:
                self._dircontent.value = None

            # Update the state of the select button
            if self._gb.layout.display is None:
                # Disable the select button if path and filename
                # - equal an existing folder in the current view
                # - contains an invalid character sequence
                # - equal the already selected values
                # - don't match the provided filter pattern(s)
                check1 = filename in dircontent_real_names
                check2 = os.path.isdir(os.path.join(path, filename))
                check3 = not is_valid_filename(filename)
                check4 = False
                check5 = False

                # Only check selected if selected is set
                if ((self._selected_path is not None)
                        and (self._selected_filename is not None)):
                    selected = os.path.join(self._selected_path,
                                            self._selected_filename)
                    check4 = os.path.join(path, filename) == selected

                # Ensure only allowed extensions are used
                if self._filter_pattern:
                    check5 = not match_item(filename, self._filter_pattern)

                if (check1 and check2) or check3 or check4 or check5:
                    self._select.disabled = True
                else:
                    self._select.disabled = False
        except PermissionError:
            # Deselect the unreadable folder and generate a warning
            self._dircontent.value = None
            warnings.warn(f'Permission denied for {path}', RuntimeWarning)

        # Reenable triggers
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')

    def _on_pathlist_select(self, change: Mapping[str, str]) -> None:
        """Handle selecting a path entry."""
        self._set_form_values(self._expand_path(change['new']),
                              self._filename.value)

    def _on_dircontent_select(self, change: Mapping[str, str]) -> None:
        """Handle selecting a folder entry."""
        new_path = os.path.realpath(
            os.path.join(self._expand_path(self._pathlist.value),
                         self._map_disp_to_name[change['new']]))

        # Check if folder or file
        if os.path.isdir(new_path):
            path = new_path
            filename = self._filename.value
        else:
            path = self._expand_path(self._pathlist.value)
            filename = self._map_disp_to_name[change['new']]

        self._set_form_values(path, filename)

    def _on_filename_change(self, change: Mapping[str, str]) -> None:
        """Handle filename field changes."""
        self._set_form_values(self._expand_path(self._pathlist.value),
                              change['new'])

    def _on_select_click(self, _b) -> None:
        """Handle select button clicks."""
        if self._gb.layout.display == 'none':
            # If not shown, open the dialog
            self._show_dialog()
        else:
            # If shown, close the dialog and apply the selection
            self._apply_selection()

            # Execute callback function
            if self._callback is not None:
                try:
                    self._callback(self)
                except TypeError:
                    # Support previous behaviour of not passing self
                    self._callback()

    def _show_dialog(self) -> None:
        """Show the dialog."""
        # Show dialog and cancel button
        self._gb.layout.display = None
        self._cancel.layout.display = None

        # Show the form with the correct path and filename
        if ((self._selected_path is not None)
                and (self._selected_filename is not None)):
            path = self._selected_path
            filename = self._selected_filename
        else:
            path = self._default_path
            filename = self._default_filename

        self._set_form_values(path, filename)

    def _apply_selection(self) -> None:
        """Close the dialog and apply the selection."""
        self._selected_path = self._expand_path(self._pathlist.value)
        self._selected_filename = self._filename.value

        if ((self._selected_path is not None)
                and (self._selected_filename is not None)):
            selected = os.path.join(self._selected_path,
                                    self._selected_filename)
            self._gb.layout.display = 'none'
            self._cancel.layout.display = 'none'
            self._select.description = self._change_desc
            self._select.disabled = False

            if os.path.isfile(selected):
                self._label.value = self._LBL_TEMPLATE.format(
                    self._restrict_path(selected), 'orange')
            else:
                self._label.value = self._LBL_TEMPLATE.format(
                    self._restrict_path(selected), 'green')

    def _on_cancel_click(self, _b) -> None:
        """Handle cancel button clicks."""
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'
        self._select.disabled = False

    def _expand_path(self, path) -> str:
        """Calculate the full path using the sandbox path."""
        if self._sandbox_path:
            path = os.path.join(self._sandbox_path, path.lstrip(os.sep))

        return path

    def _restrict_path(self, path) -> str:
        """Calculate the sandboxed path using the sandbox path."""
        if self._sandbox_path == os.sep:
            pass
        elif self._sandbox_path == path:
            path = os.sep
        elif self._sandbox_path:
            if os.path.splitdrive(self._sandbox_path)[0] and len(
                    self._sandbox_path) == 3:
                # If the value is 'c:\\', strip 'c:' so we retain the leading os.sep char
                path = strip_parent_path(
                    path,
                    os.path.splitdrive(self._sandbox_path)[0])
            else:
                path = strip_parent_path(path, self._sandbox_path)

        return path

    def reset(self,
              path: Optional[str] = None,
              filename: Optional[str] = None) -> None:
        """Reset the form to the default path and filename."""
        # Check if path and sandbox_path align
        if path is not None and self._sandbox_path and not has_parent_path(
                normalize_path(path), self._sandbox_path):
            raise ParentPathError(path, self._sandbox_path)

        # Verify the filename is valid
        if filename is not None and not is_valid_filename(filename):
            raise InvalidFileNameError(filename)

        # Remove selection
        self._selected_path = None
        self._selected_filename = None

        # Hide dialog and cancel button
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'

        # Reset select button and label
        self._select.description = self._select_desc
        self._select.disabled = False
        self._label.value = self._LBL_TEMPLATE.format(self._LBL_NOFILE,
                                                      'black')

        if path is not None:
            self._default_path = normalize_path(path)

        if filename is not None:
            self._default_filename = filename

        self._set_form_values(self._default_path, self._default_filename)

        # Use the defaults as the selected values
        if self._select_default:
            self._apply_selection()

    def refresh(self) -> None:
        """Re-render the form."""
        self._set_form_values(self._expand_path(self._pathlist.value),
                              self._filename.value)

    @property
    def show_hidden(self) -> bool:
        """Get _show_hidden value."""
        return self._show_hidden

    @show_hidden.setter
    def show_hidden(self, hidden: bool) -> None:
        """Set _show_hidden value."""
        self._show_hidden = hidden
        self.refresh()

    @property
    def dir_icon(self) -> Optional[str]:
        """Get dir icon value."""
        return self._dir_icon

    @dir_icon.setter
    def dir_icon(self, dir_icon: Optional[str]) -> None:
        """Set dir icon value."""
        self._dir_icon = dir_icon
        self.refresh()

    @property
    def dir_icon_append(self) -> bool:
        """Get dir icon value."""
        return self._dir_icon_append

    @dir_icon_append.setter
    def dir_icon_append(self, dir_icon_append: bool) -> None:
        """Prepend or append the dir icon."""
        self._dir_icon_append = dir_icon_append
        self.refresh()

    @property
    def rows(self) -> int:
        """Get current number of rows."""
        return self._dircontent.rows

    @rows.setter
    def rows(self, rows: int) -> None:
        """Set number of rows."""
        self._dircontent.rows = rows

    @property
    def title(self) -> str:
        """Get the title."""
        return self._title.value

    @title.setter
    def title(self, title: str) -> None:
        """Set the title."""
        self._title.value = title

        if title == '':
            self._title.layout.display = 'none'
        else:
            self._title.layout.display = None

    @property
    def default(self) -> str:
        """Get the default value."""
        return os.path.join(self._default_path, self._default_filename)

    @property
    def default_path(self) -> str:
        """Get the default_path value."""
        return self._default_path

    @default_path.setter
    def default_path(self, path: str) -> None:
        """Set the default_path."""
        # Check if path and sandbox_path align
        if self._sandbox_path and not has_parent_path(normalize_path(path),
                                                      self._sandbox_path):
            raise ParentPathError(path, self._sandbox_path)

        self._default_path = normalize_path(path)
        self._set_form_values(self._default_path, self._filename.value)

    @property
    def default_filename(self) -> str:
        """Get the default_filename value."""
        return self._default_filename

    @default_filename.setter
    def default_filename(self, filename: str) -> None:
        """Set the default_filename."""
        # Verify the filename is valid
        if not is_valid_filename(filename):
            raise InvalidFileNameError(filename)

        self._default_filename = filename
        self._set_form_values(self._expand_path(self._pathlist.value),
                              self._default_filename)

    @property
    def sandbox_path(self) -> Optional[str]:
        """Get the sandbox_path."""
        return self._sandbox_path

    @sandbox_path.setter
    def sandbox_path(self, sandbox_path: str) -> None:
        """Set the sandbox_path."""
        # Check if path and sandbox_path align
        if sandbox_path and not has_parent_path(self._default_path,
                                                normalize_path(sandbox_path)):
            raise ParentPathError(self._default_path, sandbox_path)

        self._sandbox_path = normalize_path(
            sandbox_path) if sandbox_path is not None else None

        # Reset the dialog
        self.reset()

    @property
    def show_only_dirs(self) -> bool:
        """Get show_only_dirs property value."""
        return self._show_only_dirs

    @show_only_dirs.setter
    def show_only_dirs(self, show_only_dirs: bool) -> None:
        """Set show_only_dirs property value."""
        self._show_only_dirs = show_only_dirs

        # Update widget layout
        self._filename.disabled = self._show_only_dirs
        self._filename.layout.display = (None, "none")[self._show_only_dirs]
        self._gb.layout.children = [self._pathlist, self._dircontent]

        if not self._show_only_dirs:
            self._gb.layout.children.insert(1, self._filename)

        self._gb.layout.grid_template_areas = '''
            'pathlist {}'
            'dircontent dircontent'
            '''.format(('filename', 'pathlist')[self._show_only_dirs])

        # Reset the dialog
        self.reset()

    @property
    def filter_pattern(self) -> Optional[Sequence[str]]:
        """Get file name filter pattern."""
        return self._filter_pattern

    @filter_pattern.setter
    def filter_pattern(self, filter_pattern: Optional[Sequence[str]]) -> None:
        """Set file name filter pattern."""
        self._filter_pattern = filter_pattern
        self.refresh()

    @property
    def value(self) -> Optional[str]:
        """Get selected value."""
        return self.selected

    @property
    def selected(self) -> Optional[str]:
        """Get selected value."""
        selected = None

        if ((self._selected_path is not None)
                and (self._selected_filename is not None)):
            selected = os.path.join(self._selected_path,
                                    self._selected_filename)

        return selected

    @property
    def selected_path(self) -> Optional[str]:
        """Get selected_path value."""
        return self._selected_path

    @property
    def selected_filename(self) -> Optional[str]:
        """Get the selected_filename."""
        return self._selected_filename

    def __repr__(self) -> str:
        """Build string representation."""
        properties = f"path='{self._default_path}'"
        properties += f", filename='{self._default_filename}'"
        properties += f", title='{self._title.value}'"
        properties += f", show_hidden={self._show_hidden}"
        properties += f", select_desc='{self._select_desc}'"
        properties += f", change_desc='{self._change_desc}'"
        properties += f", select_default={self._select_default}"
        properties += f", show_only_dirs={self._show_only_dirs}"
        properties += f", dir_icon_append={self._dir_icon_append}"

        if self._sandbox_path is not None:
            properties += f", sandbox_path='{self._sandbox_path}'"

        if self._dir_icon:
            properties += f", dir_icon='{self._dir_icon}'"

        if self._filter_pattern:
            if isinstance(self._filter_pattern, str):
                properties += f", filter_pattern='{self._filter_pattern}'"
            else:
                properties += f", filter_pattern={self._filter_pattern}"

        return f"{self.__class__.__name__}({properties})"

    def register_callback(
            self, callback: Callable[[Optional['FileChooser']], None]) -> None:
        """Register a callback function."""
        self._callback = callback

    def get_interact_value(self) -> Optional[str]:
        """Return the value which should be passed to interactive functions."""
        return self.selected
Ejemplo n.º 5
0
class FileChooser(VBox, ValueWidget):
    """FileChooser class."""

    _LBL_TEMPLATE = '<span style="margin-left:10px; color:{1};">{0}</span>'
    _LBL_NOFILE = 'No file selected'

    file_path = Unicode(allow_none=True)

    def __init__(
            self,
            path=os.getcwd(),
            filename='',
            show_hidden=False,
            use_dir_icons=False,
            filter_pattern=None,
            **kwargs):
        """Initialize FileChooser object."""
        self._default_path = path.rstrip(os.path.sep)
        self._default_filename = filename
        self._show_hidden = show_hidden
        self._use_dir_icons = use_dir_icons
        self._filter_pattern = filter_pattern

        # Widgets

        self._pathlist = Dropdown(
            description="",
            layout=Layout(
                width='auto',
                grid_area='pathlist'
            )
        )

        self._dircontent = Select(
            rows=8,
            layout=Layout(
                width='auto',
                grid_area='dircontent'
            )
        )

        # Widget observe handlers
        self._pathlist.observe(
            self._on_pathlist_select,
            names='value'
        )
        self._dircontent.observe(
            self._on_dircontent_select,
            names='value'
        )

        # Layout
        self._gb = GridBox(
            children=[
                self._pathlist,
                self._dircontent
            ],
            layout=Layout(
                width='500px',
                grid_gap='0px 0px',
                grid_template_rows='auto auto',
                grid_template_columns='60% 40%',
                grid_template_areas='''
                    'pathlist pathlist'
                    'dircontent dircontent'
                    '''
            )
        )

        # Call setter to set initial form values
        self._set_form_values(
            self._default_path,
            self._default_filename
        )

        self._initialize_form_values()

        # Call VBox super class __init__
        super().__init__(
            children=[self._gb],
            layout=Layout(width='auto'),
            **kwargs
        )

    def _set_form_values(self, path, filename):
        """Set the form values."""
        # Disable triggers to prevent selecting an entry in the Select
        # box from automatically triggering a new event.
        self._pathlist.unobserve(
            self._on_pathlist_select,
            names='value'
        )
        self._dircontent.unobserve(
            self._on_dircontent_select,
            names='value'
        )

        # Set form values
        self._pathlist.options = get_subpaths(path)
        self._pathlist.value = path

        # file/folder real names
        dircontent_real_names = get_dir_contents(
            path,
            show_hidden=self._show_hidden,
            prepend_icons=False,
            filter_pattern=self._filter_pattern
        )

        # file/folder display names
        dircontent_display_names = get_dir_contents(
            path,
            show_hidden=self._show_hidden,
            prepend_icons=self._use_dir_icons,
            filter_pattern=self._filter_pattern
        )

        # Dict to map real names to display names
        self._map_name_to_disp = {
            real_name: disp_name
            for real_name, disp_name in zip(
                dircontent_real_names,
                dircontent_display_names
            )
        }

        # Dict to map display names to real names
        self._map_disp_to_name = dict(
            reversed(item) for item in self._map_name_to_disp.items()
        )

        # Set _dircontent form value to display names
        self._dircontent.options = dircontent_display_names

        # If the value in the filename Text box equals a value in the
        # Select box and the entry is a file then select the entry.
        if ((filename in dircontent_real_names) and
                os.path.isfile(os.path.join(path, filename))):
            self._dircontent.value = self._map_name_to_disp[filename]
        else:
            self._dircontent.value = None

        # Re-enable triggers again
        self._pathlist.observe(
            self._on_pathlist_select,
            names='value'
        )
        self._dircontent.observe(
            self._on_dircontent_select,
            names='value'
        )

        self._update_file_path()

    def _on_pathlist_select(self, change):
        """Handle selecting a path entry."""
        self._set_form_values(
            change['new'],
            self._selected_filename
        )

    def _on_dircontent_select(self, change):
        """Handle selecting a folder entry."""
        new_path = os.path.realpath(
            os.path.join(
                self._selected_path,
                self._map_disp_to_name[change['new']]
            )
        )

        # Check if folder or file
        if os.path.isdir(new_path):
            path = new_path
            filename = None
        elif os.path.isfile(new_path):
            path = self._selected_path
            filename = self._map_disp_to_name[change['new']]

        self._set_form_values(
            path,
            filename
        )

    def _initialize_form_values(self):
        """Show the dialog."""

        # Show the form with the correct path and filename
        if ((self._selected_path is not None) and
                (self._selected_filename is not None)):
            path = self._selected_path
            filename = self._selected_filename
        else:
            path = self._default_path
            filename = self._default_filename

        self._set_form_values(path, filename)

    @property
    def _selected_path(self):
        return self._pathlist.value

    @property
    def _selected_filename(self):
        if self._dircontent.value is None:
            return None
        else:
            return self._map_disp_to_name[self._dircontent.value]

    def _update_file_path(self):
        if self._selected_filename is None or self._selected_path is None:
            self.file_path = None
        else:
            self.file_path = os.path.join(self._selected_path,
                                          self._selected_filename)

    def refresh(self):
        """Re-render the form."""
        self._set_form_values(
            self._selected_path,
            self._selected_filename
        )

    @property
    def show_hidden(self):
        """Get _show_hidden value."""
        return self._show_hidden

    @show_hidden.setter
    def show_hidden(self, hidden):
        """Set _show_hidden value."""
        self._show_hidden = hidden
        self.refresh()

    @property
    def use_dir_icons(self):
        """Get _use_dir_icons value."""
        return self._use_dir_icons

    @use_dir_icons.setter
    def use_dir_icons(self, dir_icons):
        """Set _use_dir_icons value."""
        self._use_dir_icons = dir_icons
        self.refresh()

    @property
    def rows(self):
        """Get current number of rows."""
        return self._dircontent.rows

    @rows.setter
    def rows(self, rows):
        """Set number of rows."""
        self._dircontent.rows = rows

    @property
    def default_path(self):
        """Get the default_path value."""
        return self._default_path

    @default_path.setter
    def default_path(self, path):
        """Set the default_path."""
        self._default_path = path.rstrip(os.path.sep)
        self._set_form_values(
            self._default_path,
            self._selected_filename
        )

    @property
    def default_filename(self):
        """Get the default_filename value."""
        return self._default_filename

    @default_filename.setter
    def default_filename(self, filename):
        """Set the default_filename."""
        self._default_filename = filename
        self._set_form_values(
            self._selected_path,
            self._default_filename
        )

    @property
    def filter_pattern(self):
        """Get file name filter pattern."""
        return self._filter_pattern

    @filter_pattern.setter
    def filter_pattern(self, filter_pattern):
        """Set file name filter pattern."""
        self._filter_pattern = filter_pattern
        self.refresh()

    @property
    def selected(self):
        """Get selected value."""
        try:
            return os.path.join(
                self._selected_path,
                self._selected_filename
            )
        except TypeError:
            return None
Ejemplo n.º 6
0
class FileChooser(VBox):
    """
# FileChooser code is from
# https://github.com/crahan/ipyfilechooser
    """

    _LBL_TEMPLATE = '<span style="margin-left:10px; color:{1};">{0}</span>'
    _LBL_NOFILE = 'No file selected'

    def __init__(self,
                 path=os.getcwd(),
                 filename='',
                 show_hidden=False,
                 **kwargs):

        self._default_path = path.rstrip(os.path.sep)
        self._default_filename = filename
        self._selected_path = ''
        self._selected_filename = ''
        self._show_hidden = show_hidden

        # Widgets
        self._pathlist = Dropdown(description="",
                                  layout=Layout(width='auto',
                                                grid_area='pathlist'))
        self._filename = Text(placeholder='output filename',
                              layout=Layout(width='auto',
                                            grid_area='filename'))
        self._dircontent = Select(rows=8,
                                  layout=Layout(width='auto',
                                                grid_area='dircontent'))
        self._cancel = Button(
            description='Cancel',
            button_style=
            'warning',  # 'success', 'info', 'warning', 'danger' or ''
            layout=Layout(width='auto', display='none'))
        self._select = Button(
            description='Select',
            button_style=
            'success',  # 'success', 'info', 'warning', 'danger' or ''
            layout=Layout(width='auto'))

        # Widget observe handlers
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')
        self._select.on_click(self._on_select_click)
        self._cancel.on_click(self._on_cancel_click)

        # Selected file label
        self._label = HTML(value=self._LBL_TEMPLATE.format(
            self._LBL_NOFILE, 'black'),
                           placeholder='',
                           description='')

        # Layout
        self._gb = GridBox(
            children=[self._pathlist, self._filename, self._dircontent],
            layout=Layout(display='none',
                          width='500px',
                          grid_gap='0px 0px',
                          grid_template_rows='auto auto',
                          grid_template_columns='60% 40%',
                          grid_template_areas='''
                    'pathlist filename'
                    'dircontent dircontent'
                    '''))
        buttonbar = HBox(children=[self._select, self._cancel, self._label],
                         layout=Layout(width='auto'))

        # Call setter to set initial form values
        self._set_form_values(self._default_path, self._default_filename)

        # Call VBox super class __init__
        super().__init__(children=[
            self._gb,
            buttonbar,
        ],
                         layout=Layout(width='auto'),
                         **kwargs)

    def _set_form_values(self, path, filename):
        '''Set the form values'''

        # Disable triggers to prevent selecting an entry in the Select
        # box from automatically triggering a new event.
        self._pathlist.unobserve(self._on_pathlist_select, names='value')
        self._dircontent.unobserve(self._on_dircontent_select, names='value')
        self._filename.unobserve(self._on_filename_change, names='value')

        # Set form values
        self._pathlist.options = get_subpaths(path)
        self._pathlist.value = path
        self._filename.value = filename
        self._dircontent.options = get_dir_contents(path,
                                                    hidden=self._show_hidden)

        # If the value in the filename Text box equals a value in the
        # Select box and the entry is a file then select the entry.
        if ((filename in self._dircontent.options)
                and os.path.isfile(os.path.join(path, filename))):
            self._dircontent.value = filename
        else:
            self._dircontent.value = None

        # Reenable triggers again
        self._pathlist.observe(self._on_pathlist_select, names='value')
        self._dircontent.observe(self._on_dircontent_select, names='value')
        self._filename.observe(self._on_filename_change, names='value')

        # Set the state of the select Button
        if self._gb.layout.display is None:
            selected = os.path.join(self._selected_path,
                                    self._selected_filename)

            # filename value is empty or equals the selected value
            if (filename == '') or (os.path.join(path, filename) == selected):
                self._select.disabled = True
            else:
                self._select.disabled = False

    def _on_pathlist_select(self, change):
        '''Handler for when a new path is selected'''
        self._set_form_values(change['new'], self._filename.value)

    def _on_dircontent_select(self, change):
        '''Handler for when a folder entry is selected'''
        new_path = update_path(self._pathlist.value, change['new'])

        # Check if folder or file
        if os.path.isdir(new_path):
            path = new_path
            filename = self._filename.value
        elif os.path.isfile(new_path):
            path = self._pathlist.value
            filename = change['new']

        self._set_form_values(path, filename)

    def _on_filename_change(self, change):
        '''Handler for when the filename field changes'''
        self._set_form_values(self._pathlist.value, change['new'])

    def _on_select_click(self, b):
        '''Handler for when the select button is clicked'''
        if self._gb.layout.display == 'none':
            self._gb.layout.display = None
            self._cancel.layout.display = None

            # Show the form with the correct path and filename
            if self._selected_path and self._selected_filename:
                path = self._selected_path
                filename = self._selected_filename
            else:
                path = self._default_path
                filename = self._default_filename

            self._set_form_values(path, filename)

        else:
            self._gb.layout.display = 'none'
            self._cancel.layout.display = 'none'
            self._select.description = 'Change'
            self._selected_path = self._pathlist.value
            self._selected_filename = self._filename.value
            # self._default_path = self._selected_path
            # self._default_filename = self._selected_filename

            selected = os.path.join(self._selected_path,
                                    self._selected_filename)

            if os.path.isfile(selected):
                self._label.value = self._LBL_TEMPLATE.format(
                    selected, 'orange')
            else:
                self._label.value = self._LBL_TEMPLATE.format(
                    selected, 'green')

    def _on_cancel_click(self, b):
        '''Handler for when the cancel button is clicked'''
        self._gb.layout.display = 'none'
        self._cancel.layout.display = 'none'
        self._select.disabled = False

    def reset(self, path=None, filename=None):
        '''Reset the form to the default path and filename'''
        self._selected_path = ''
        self._selected_filename = ''

        self._label.value = self._LBL_TEMPLATE.format(self._LBL_NOFILE,
                                                      'black')

        if path is not None:
            self._default_path = path.rstrip(os.path.sep)

        if filename is not None:
            self._default_filename = filename

        self._set_form_values(self._default_path, self._default_filename)

    def refresh(self):
        '''Re-render the form'''
        self._set_form_values(self._pathlist.value, self._filename.value)

    @property
    def show_hidden(self):
        '''Get current number of rows'''
        return self._show_hidden

    @show_hidden.setter
    def show_hidden(self, hidden):
        '''Set number of rows'''
        self._show_hidden = hidden
        self.refresh()

    @property
    def rows(self):
        '''Get current number of rows'''
        return self._dircontent.rows

    @rows.setter
    def rows(self, rows):
        '''Set number of rows'''
        self._dircontent.rows = rows

    @property
    def default(self):
        '''Get the default value'''
        return os.path.join(self._default_path, self._default_filename)

    @property
    def default_path(self):
        '''Get the default_path value'''
        return self._default_path

    @default_path.setter
    def default_path(self, path):
        '''Set the default_path'''
        self._default_path = path.rstrip(os.path.sep)
        self._default = os.path.join(self._default_path, self._filename.value)
        self._set_form_values(self._default_path, self._filename.value)

    @property
    def default_filename(self):
        '''Get the default_filename value'''
        return self._default_filename

    @default_filename.setter
    def default_filename(self, filename):
        '''Set the default_filename'''
        self._default_filename = filename
        self._default = os.path.join(self._pathlist.value,
                                     self._default_filename)
        self._set_form_values(self._pathlist.value, self._default_filename)

    @property
    def selected(self):
        '''Get selected value'''
        return os.path.join(self._selected_path, self._selected_filename)

    @property
    def selected_path(self):
        '''Get selected_path value'''
        return self._selected_path

    @property
    def selected_filename(self):
        '''Get the selected_filename'''
        return self._selected_filename

    @property
    def nmrname(self):  # added by MAD
        return os.path.join(
            os.path.basename(os.path.dirname(self._selected_path)),
            os.path.basename(self._selected_path))

    def __repr__(self):
        str_ = ("FileChooser("
                "path='{0}', "
                "filename='{1}', "
                "show_hidden='{2}')").format(self._default_path,
                                             self._default_filename,
                                             self._show_hidden)
        return str_
Ejemplo n.º 7
0
class PapayaConfigWidget(VBox):
    """A widget that displays widgets to adjust NLPapayaViewer image parameters."""

    lut_options = [
        "Grayscale",
        "Red Overlay",
        "Green Overlay",
        "Blue Overlay",
        "Gold",
        "Spectrum",
        "Overlay (Positives)",
        "Overlay (Negatives)",
    ]

    def __init__(self, viewer, *args, **kwargs):
        """
        Parameters
        ----------
        viewer: NlPapayaViewer
            associated viewer.
        """
        super().__init__(*args, **kwargs)

        self._viewer = viewer
        self._init_widgets()

        self.children = [
            VBox([
                VBox(
                    [self._hist],
                    layout=Layout(
                        height="auto",
                        margin="0px 0px 0px 0px",
                        padding="5px 5px 5px 5px",
                    ),
                ),
                VBox(
                    [
                        self._alpha,
                        self._lut,
                        self._nlut,
                        self._min,
                        self._minp,
                        self._max,
                        self._maxp,
                        self._sym,
                    ],
                    layout=Layout(width="230px"),
                ),
            ])
        ]

    def _init_widgets(self):
        """Initializes all configuration widgets. Possible image config parameters are:"""
        layout = Layout(width="200px", max_width="200px")

        self._alpha = FloatSlider(
            value=1,
            min=0,
            max=1.0,
            step=0.1,
            description="alpha:",
            description_tooltip="Overlay image alpha level (0 to 1).",
            disabled=False,
            continuous_update=True,
            orientation="horizontal",
            readout=True,
            readout_format=".1f",
            layout=layout,
        )

        self._lut = Dropdown(
            options=PapayaConfigWidget.lut_options,
            value="Red Overlay",
            description="lut:",
            description_tooltip="The color table name.",
            layout=layout,
        )

        self._nlut = Dropdown(
            options=PapayaConfigWidget.lut_options,
            value="Red Overlay",
            description="negative-lut:",
            description_tooltip=
            "The color table name used by the negative side of the parametric pair.",
            layout=layout,
        )

        self._min = FloatText(
            value=None,
            description="min:",
            description_tooltip="The display range minimum.",
            step=0.01,
            continuous_update=True,
            disabled=False,
            layout=layout,
        )

        self._minp = BoundedFloatText(
            value=None,
            min=0,
            max=100,
            step=1,
            continuous_update=True,
            description="min %:",
            description_tooltip=
            "The display range minimum as a percentage of image max.",
            disabled=False,
            layout=layout,
        )

        self._max = FloatText(
            value=None,
            description="max:",
            description_tooltip="The display range maximum.",
            step=0.01,
            continuous_update=True,
            disabled=False,
            layout=layout,
        )

        self._maxp = BoundedFloatText(
            value=None,
            min=0,
            max=100,
            step=1,
            continuous_update=True,
            description="max %:",
            description_tooltip=
            "The display range minimum as a percentage of image max.",
            disabled=False,
            layout=layout,
        )

        self._sym = Checkbox(
            value=False,
            description="symmetric",
            description_tooltip=
            "When selected, sets the negative range of a parametric pair to the same size as the positive range.",
            disabled=False,
            layout=layout,
        )

        # figure to display histogram of image data
        fig = Figure()
        fig.update_layout(
            height=300,
            margin=dict(l=15, t=15, b=15, r=15, pad=4),
            showlegend=True,
            legend_orientation="h",
        )

        self._hist = FigureWidget(fig)
        self._hist.add_trace(
            Histogram(x=[], name="All image data", visible="legendonly"))
        self._hist.add_trace(Histogram(x=[], name="Image data without 0s"))

        self._handlers = defaultdict()

    def _set_values(self, config, range, data):
        """Sets config values from the specified `config` and creates histogram for `data`.

        Parameters
        ----------
        config : dict
            configuration parameters for the image. Possible keywords are:
            alpha : int
                the overlay image alpha level (0 to 1).
            lut : str
                the color table name.
            negative_lut : str
                the color table name used by the negative side of the parametric pair.
            max : int
                the display range maximum.
            maxPercent : int
                the display range maximum as a percentage of image max.
            min : int
                the display range minimum.
            minPercent : int
                the display range minimum as a percentage of image min.
           symmetric : bool
                if true, sets the negative range of a parametric pair to the same size as the positive range.
        range: float
            range of image values.
        data: []
           flattened image data.
        """
        self._alpha.value = config.get("alpha", 1)
        self._lut.value = config.get("lut", PapayaConfigWidget.lut_options[1])
        self._nlut.value = config.get("negative_lut",
                                      PapayaConfigWidget.lut_options[1])
        self._min.value = config.get("min", 0)
        self._minp.value = self._get_per_from_value(range,
                                                    config.get("min", 0))
        self._max.value = config.get("max", 0.1)
        self._maxp.value = self._get_per_from_value(range,
                                                    config.get("max", 0.1))
        self._sym.value = config.get("symmetric", "false") == "true"

        # set histogram data
        self._hist.data[0].x = data
        # leave out 0 values
        self._hist.data[1].x = [] if (data == []
                                      or data is None) else data[data != 0]

    def _add_handlers(self, image):
        """Add config widget event handlers to change the config values for the specified `image`.

        Parameters
        ----------
        image: neurolang_ipywidgets.PapayaImage
            image whose config values will be viewed/modified using this config widget.
        """

        # Dropdown does not support resetting event handlers after Dropdown.unobserve_all is called
        # So handlers are stored to be removed individually
        # github issue https://github.com/jupyter-widgets/ipywidgets/issues/1868

        self._handlers["alpha"] = partial(self._config_changed,
                                          image=image,
                                          name="alpha")
        self._handlers["lut"] = partial(self._config_changed,
                                        image=image,
                                        name="lut")
        self._handlers["nlut"] = partial(self._config_changed,
                                         image=image,
                                         name="negative_lut")
        self._handlers["min"] = partial(self._config_changed,
                                        image=image,
                                        name="min")
        self._handlers["minp"] = partial(self._set_min_max,
                                         image=image,
                                         name="minPercent")
        self._handlers["max"] = partial(self._config_changed,
                                        image=image,
                                        name="max")
        self._handlers["maxp"] = partial(self._set_min_max,
                                         image=image,
                                         name="maxPercent")
        self._handlers["sym"] = partial(self._config_bool_changed,
                                        image=image,
                                        name="symmetric")

        self._alpha.observe(self._handlers["alpha"], names="value")

        self._lut.observe(self._handlers["lut"], names="value")

        self._nlut.observe(self._handlers["nlut"], names="value")

        self._min.observe(self._handlers["min"], names="value")

        self._minp.observe(self._handlers["minp"], names="value")

        self._max.observe(self._handlers["max"], names="value")

        self._maxp.observe(self._handlers["maxp"], names="value")

        self._sym.observe(self._handlers["sym"], names="value")

    def _remove_handlers(self):
        """Removes all event handlers set for the config widgets."""
        if len(self._handlers):
            self._alpha.unobserve(self._handlers["alpha"], names="value")
            self._lut.unobserve(self._handlers["lut"], names="value")
            self._nlut.unobserve(self._handlers["nlut"], names="value")
            self._min.unobserve(self._handlers["min"], names="value")
            self._minp.unobserve(self._handlers["minp"], names="value")
            self._max.unobserve(self._handlers["max"], names="value")
            self._maxp.unobserve(self._handlers["maxp"], names="value")
            self._sym.unobserve(self._handlers["sym"], names="value")

            self._handlers = defaultdict()

    @debounce(0.5)
    def _config_changed(self, change, image, name):
        if name == "min":
            self._minp.unobserve(self._handlers["minp"], names="value")
            self._minp.value = self._get_per_from_value(
                image.range, change.new)
            self._minp.observe(self._handlers["minp"], names="value")
        elif name == "max":
            self._maxp.unobserve(self._handlers["maxp"], names="value")
            self._maxp.value = self._get_per_from_value(
                image.range, change.new)
            self._maxp.observe(self._handlers["maxp"], names="value")

        self._set_config(image, name, change.new)

    @debounce(0.5)
    def _set_min_max(self, change, image, name):
        if name == "minPercent":
            self._min.unobserve(self._handlers["min"], names="value")
            self._min.value = self._get_value_from_per(image.range, change.new)
            self._set_config(image, "min", self._min.value)
            self._min.observe(self._handlers["min"], names="value")
        elif name == "maxPercent":
            self._max.unobserve(self._handlers["max"], names="value")
            self._max.value = self._get_value_from_per(image.range, change.new)
            self._set_config(image, "max", self._max.value)
            self._max.observe(self._handlers["max"], names="value")

    def _config_bool_changed(self, change, image, name):
        value = "false"
        if change.new:
            value = "true"
        self._set_config(image, name, value)

    def _set_config(self, image, key, value):
        image.config[key] = value
        self._viewer.set_images()

    def _get_per_from_value(self, range, value):
        return round(value * 100 / range, 0)

    def _get_value_from_per(self, range, per):
        return round(per * range / 100, 2)

    def set_image(self, image):
        """Sets the image whose config values will be viewed/modified using this config widget.
        If image is `None`, all config values are reset.

        Parameters
        ----------
        image: neurolang_ipywidgets.PapayaImage
            image whose config values will be viewed/modified using this config widget.
        """
        if image:
            self._remove_handlers()
            self._set_values(image.config, image.range,
                             image.image.get_fdata().flatten())
            self._add_handlers(image)
        else:
            self.reset()

    def reset(self):
        """Resets values for all config widgets."""
        self._remove_handlers()
        self._set_values({}, 100, [])
        self.layout.visibility = "hidden"