Open unkcpz opened 2 years ago
Hi @dou-du, I extend this as a class. I think we can have a discussion and make the decision on how to integrate _on_element_select
so can be controlled with one parameter.
class PeriodicTable(ipw.VBox):
"""Wrapper-widget for PTableWidget"""
selected_element = traitlets.Unicode(allow_none=True)
def __init__(self, cache_folder, **kwargs):
self._disabled = kwargs.get("disabled", False)
self._cache_folder = cache_folder
self.ptable = PTableWidget(states=1, selected_colors=["green"], **kwargs)
self._last_selected = None
self.ptable.observe(self._on_element_select)
# if cache empty run update: first time
if os.path.exists(os.path.join(cache_folder, _DB_FOLDER)):
self._update_db(download=False)
else:
self._update_db(download=True)
disable_elements = [
e for e in self.ptable.allElements if e not in self.elements
]
self.ptable.disabled_elements = disable_elements
db_update = ipw.Button(
description="Update Database.",
)
db_update.on_click(self._update_db)
super().__init__(
children=(
self.ptable,
db_update,
),
layout=kwargs.get("layout", {}),
)
def _on_element_select(self, event):
if event["name"] == "selected_elements" and event["type"] == "change":
if tuple(event["new"].keys()) == ("Du",):
self._last_selected = event["old"]
elif tuple(event["old"].keys()) == ("Du",):
if len(event["new"]) != 1:
# Reset to only one element only if there is more than one selected,
# to avoid infinite loops
newly_selected = set(event["new"]).difference(self._last_selected)
# If this is empty it's ok, unselect all
# If there is more than one, that's weird... to avoid problems, anyway, I pick one of the two
if newly_selected:
element = list(newly_selected)[0]
self.ptable.selected_elements = {element: 0}
self.selected_element = element
else:
self.ptable.selected_elements = {}
self.selected_element = None
# To have the correct 'last' value for next calls
self._last_selected = self.ptable.selected_elements
else:
# first time set: len(event['new']) -> 1
self.selected_element = list(event["new"])[0]
def _update_db(self, _=None, download=True):
"""update cached db fetch from remote. and update ptable"""
# download from remote
if download:
self._download(self._cache_folder)
self.elements = self._get_enabled_elements(self._cache_folder)
disable_elements = [
e for e in self.ptable.allElements if e not in self.elements
]
self.ptable.disabled_elements = disable_elements
@staticmethod
def _get_enabled_elements(cache_folder):
elements = []
for fn in os.listdir(os.path.join(cache_folder, _DB_FOLDER)):
if "band" not in fn:
elements.append(fn.split(".")[0])
return elements
@staticmethod
def _download(cache_folder):
"""
The original sssp_db folder is deleted and re-downloaded from
source and extracted.
:params cache_folder: folder where cache stored
"""
# Purge whole db folder filst
db_dir = f"{cache_folder}/sssp_db"
if os.path.exists(db_dir) and os.path.isdir(db_dir):
shutil.rmtree(db_dir)
# download DB tar file from source
tar_file = f"{cache_folder}/sssp_db.tar.gz"
request.urlretrieve(_DB_URL, tar_file)
# decompress to the db folder
tar = tarfile.open(tar_file)
os.chdir(cache_folder)
tar.extractall()
tar.close()
os.remove(tar_file)
@property
def value(self) -> dict:
"""Return value for wrapped PTableWidget"""
return not self.select_any_all.value, self.ptable.selected_elements.copy()
@property
def disabled(self) -> None:
"""Disable widget"""
return self._disabled
@disabled.setter
def disabled(self, value: bool) -> None:
"""Disable widget"""
if not isinstance(value, bool):
raise TypeError("disabled must be a boolean")
self.select_any_all.disabled = self.ptable.disabled = value
def reset(self):
"""Reset widget"""
self.select_any_all.value = False
self.ptable.selected_elements = {}
self.selected_element = None
def freeze(self):
"""Disable widget"""
self.disabled = True
def unfreeze(self):
"""Activate widget (in its current state)"""
self.disabled = False
Like in the aiidalab sssp app and commonwf-oxides app, we want the periodic table can only have one element been chosen. The following code works well for my sssp app, I assume it can be inside the widget and set by option with the periodic table instance.
The code now implemented is (copy from commonwf-oxides):