# --------------------------------------------------------------------------------------
# Modified from intake.gui
#
# Copyright (c) 2012 - 2018, Anaconda, Inc. and Intake contributors
#
# BSD 2-Clause "Simplified" License
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# --------------------------------------------------------------------------------------
"""Widgets for the Jupyter notebook and Jupyter lab."""
from contextlib import contextmanager
import IPython
from IPython.core.interactiveshell import InteractiveShell
from spectrochempy.application import warning_
from spectrochempy.utils.decorators import deprecated
from spectrochempy.utils.file import pathclean
from spectrochempy.utils.optional import import_optional_dependency
ipywidgets = import_optional_dependency("ipywidgets", errors="ignore")
def _ipywidgets_is_not_available():
return ipywidgets is None
if not _ipywidgets_is_not_available():
from ipywidgets import widgets
__all__ = ["FileSelector"]
@contextmanager
def ignore(ob):
try:
ob.ignore = True
yield
finally:
ob.ignore = False
class Base:
done_callback = None
def stop(self, ok=False):
done = self.done_callback is not None
if ok and done:
self.done_callback(ok)
elif done:
self.done_callback(None)
def __repr__(self):
return (
"To get widget to display, you must "
"install ipy/jupyter-widgets, run in a notebook and, in "
"the case of jupyter-lab, install the jlab extension."
)
def _ipython_display_(self, **kwargs):
# from IPython.Widget._ipython_display_
if InteractiveShell.initialized() and self.widget._view_name is not None:
plaintext = repr(self)
data = {
"text/plain": plaintext,
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": self.widget._model_id,
},
}
IPython.display.display(data, raw=True)
self.widget._handle_displayed(**kwargs)
[docs]
class FileSelector(Base):
"""
IPyWidgets interface for picking files.
The current path is stored in ` .path` and the current selection is
stored in ` .value` .
Parameters
----------
done_callback : function
Called when the tick or cross buttons are clicked. Expects signature
func(path, ok=True|False).
filters : list of str or None
Only show files ending in one of these strings. Normally used for
picking file extensions. None is an
alias for [''], passes all files.
"""
@deprecated(replace="(None)", removed="0.8.0")
def __init__(self, done_callback=None, path=None, filters=None):
warning_(
"This widget is not supported anymore and will be removed in a future version.",
)
if _ipywidgets_is_not_available():
raise ImportError('This widget requires "ipywidgets" to be installed.')
path = pathclean(path)
self.startpath = path
self.startname = path.name
self.done_callback = done_callback
if filters:
if not isinstance(filters, list | tuple):
filters = [filters]
self.filters = list(filters)
else:
self.filters = [""]
if not path or not path.exists():
self.path = path.cwd()
else:
self.path = path
self.main = widgets.Select(rows=7)
self.button = widgets.Button(
icon="chevron-left",
tooltip="Parent",
layout=widgets.Layout(flex="1 1 auto", width="auto"),
)
self.button.on_click(self.up)
self.label = widgets.Label(
layout=widgets.Layout(flex="100 1 auto", width="auto"),
)
self.x = widgets.Button(
icon="close",
tooltip="Close Selector",
layout=widgets.Layout(width="auto"),
)
self.x.on_click(lambda ev: self.stop())
self.ok = widgets.Button(
icon="check",
tooltip="OK",
layout=widgets.Layout(width="auto"),
)
self.ok.on_click(lambda ev: self._ok())
self.make_options()
self.main.observe(self.changed, "value")
self.upper = widgets.Box(children=[self.button, self.label])
self.right = widgets.VBox(children=[self.x, self.ok])
self.lower = widgets.HBox(children=[self.main, self.right])
self.widget = widgets.VBox(children=[self.upper, self.lower])
self.ignore = False
def _ok(self):
fn = self.path / self.main.value
if fn.is_dir():
self.stop()
else:
self.stop(fn)
def make_options(self):
self.ignore = True
self.label.value = (
str(self.path).replace(str(self.startpath.parent), "..")
if str(self.startpath) in str(self.path)
else str(self.path)
)
out = []
for f in sorted(self.path.glob("[a-zA-Z0-9]*")): # os.listdir()):
if f.is_dir() or any(
ext in f.suffix
for ext in self.filters + list(map(str.upper, self.filters))
):
out.append(f.name)
self.main.value = self.value = None
self.fullpath = self.path
self.main.options = out
self.ignore = False
def up(self, *args):
self.path = (
self.path.parent
) # os.path.dirname(self.path.rstrip('/')).rstrip('/') + '/'
self.make_options()
def changed(self, ev):
if self.ignore:
self.value = None
self.fullpath = None
return
with ignore(self):
fn = self.path / ev["new"]
if fn.is_dir():
self.path = fn
self.make_options()
self.value = self.main.value
self.fullpath = self.path / self.value