Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies = [
"kaleido", # required for exporting PNG to disk
"asteval",
"muscle3",
"panel-jstree",
]
dynamic = ["version"]

Expand Down
6 changes: 6 additions & 0 deletions waveform_editor/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def exception_handler(ex):
"tabulator",
"terminal",
"katex",
"tree", # panel_jstree registers under "tree", not "jstree"
js_files={
# jQuery must be in the page <head> before Bokeh renders panel_jstree
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js",
"jstree": "https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.12/jstree.min.js",
},
notifications=True,
exception_handler=exception_handler,
)
Expand Down
159 changes: 83 additions & 76 deletions waveform_editor/gui/selector/options_button_row.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,80 +7,66 @@

if TYPE_CHECKING:
from waveform_editor.gui.main import WaveformEditorGui
from waveform_editor.gui.selector.selection_group import SelectionGroup
from waveform_editor.gui.selector.selector import WaveformSelector


class OptionsButtonRow(Viewer):
"""Row of options buttons for the SelectionGroup"""
"""Row of options buttons for waveform/group management.

def __init__(
self, main_gui: "WaveformEditorGui", selection_group: "SelectionGroup"
):
Operates on the currently active group tracked by selector.active_group_path.
"""

def __init__(self, main_gui: "WaveformEditorGui", selector: "WaveformSelector"):
super().__init__()
self.main_gui = main_gui
self.config = main_gui.config
self.selection_group = selection_group
self.selector = selection_group.selector
self.path = selection_group.path
has_waveforms = selection_group.has_waveforms
self.selector = selector

not_root = pn.bind(lambda path: bool(path), selector.param.active_group_path)

# 'Select all' Button
self.select_all_button = pn.widgets.ButtonIcon(
icon="select-all",
size="20px",
active_icon="check",
description="Select all waveforms in this group",
on_click=selection_group.select_all,
disabled=self.selector.param.multiselect.rx.not_(),
visible=has_waveforms,
description="Select all waveforms in active group",
on_click=self._select_all,
disabled=selector.param.multiselect.rx.not_(),
)

# 'Deselect all' Button
self.deselect_all_button = pn.widgets.ButtonIcon(
icon="deselect",
size="20px",
active_icon="check",
description="Deselect all waveforms in this group",
on_click=selection_group.deselect_all,
visible=has_waveforms,
description="Deselect all waveforms in active group",
on_click=self._deselect_all,
)

# 'Add new waveform' button
self.new_waveform_button = pn.widgets.ButtonIcon(
icon="plus",
size="20px",
active_icon="check",
description="Add new waveform",
description="Add new waveform to active group",
on_click=self._on_add_waveform_button_click,
visible=not self.selection_group.is_root,
visible=not_root,
)
self.new_waveform_panel = TextInputForm(
"Enter name of new waveform",
is_visible=False,
on_click=self._add_new_waveform,
)

# 'Remove waveform' button
self.remove_waveform_button = pn.widgets.ButtonIcon(
icon="minus",
size="20px",
active_icon="check",
description="Remove selected waveforms in this group",
description="Remove selected waveforms from active group",
on_click=self._show_remove_waveform_modal,
visible=has_waveforms,
visible=not_root,
)

# 'Rename waveform' button
self.rename_waveform_button = pn.widgets.ButtonIcon(
icon="cursor-text",
size="20px",
active_icon="check",
description="Rename waveform",
description="Rename selected waveform",
on_click=self._on_rename_waveform_button_click,
visible=has_waveforms,
)

# 'Add new group' button
self.new_group_button = pn.widgets.ButtonIcon(
icon="library-plus",
size="20px",
Expand All @@ -93,18 +79,15 @@ def __init__(
is_visible=False,
on_click=self._add_new_group,
)

# 'Remove group' button
self.remove_group_button = pn.widgets.ButtonIcon(
icon="trash",
size="20px",
active_icon="trash-filled",
description="Remove this group",
description="Remove active group",
on_click=self._show_remove_group_modal,
visible=not self.selection_group.is_root,
visible=not_root,
)

# Combine all into a button row
option_buttons = pn.Row(
self.new_waveform_button,
self.remove_waveform_button,
Expand All @@ -120,90 +103,114 @@ def __init__(
self.new_group_panel,
)

def _get_active_group(self):
group = self.config
for part in self.selector.active_group_path:
group = group.groups[part]
return group

def _get_active_group_selection(self):
"""Selected waveforms that belong to the active group."""
path = self.selector.active_group_path
if not path:
return []
group = self._get_active_group()
return [w for w in self.selector.selection if w in group.waveforms]

def _select_all(self, event=None):
path = self.selector.active_group_path
if not path:
pn.state.notifications.warning("Click a group in the tree first.")
return
all_wf = list(self._get_active_group().waveforms.keys())
new_sel = list(dict.fromkeys(self.selector.selection + all_wf))
self.selector.set_selection(new_sel)

def _deselect_all(self, event=None):
path = self.selector.active_group_path
if not path:
return
grp_wf = set(self._get_active_group().waveforms.keys())
new_sel = [w for w in self.selector.selection if w not in grp_wf]
self.selector.set_selection(new_sel)

def _show_remove_waveform_modal(self, event):
if not self.selection_group.get_selection():
selection = self._get_active_group_selection()
if not selection:
pn.state.notifications.error("No waveforms selected for removal.")
return
path = self.selector.active_group_path
self.main_gui.confirm_modal.show(
"Are you sure you want to delete the selected waveform(s) from the "
f"**{self.path[-1]}** group?",
f"Are you sure you want to delete the selected waveform(s) from the "
f"**{path[-1]}** group?",
on_confirm=self._remove_waveforms,
)

def _remove_waveforms(self):
"""Remove all selected waveforms in this SelectionGroup."""
for waveform_name in self.selection_group.get_selection():
for waveform_name in self._get_active_group_selection():
self.config.remove_waveform(waveform_name)
with self.selector.is_removing_waveform: # Signal we're removing waveforms
self.selection_group.sync_waveforms()
with self.selector.is_removing_waveform:
new_sel = [
v for v in self.selector.selection if v in self.config.waveform_map
]
self.selector._rebuild_tree()
self.selector.set_selection(new_sel)

def _show_remove_group_modal(self, event):
path = self.selector.active_group_path
if not path:
return
self.main_gui.confirm_modal.show(
f"Are you sure you want to delete the **{self.path[-1]}** group? \n"
f"Are you sure you want to delete the **{path[-1]}** group? \n"
"This will also remove all waveforms and subgroups in this group!",
on_confirm=self._remove_group,
)

def _remove_group(self):
"""Remove the group."""
# Remove from config
self.config.remove_group(self.path)
# Remove from GUI
self.selector.remove_group(self.path)
path = self.selector.active_group_path
self.config.remove_group(path)
self.selector.remove_group(path)

def _on_add_waveform_button_click(self, event):
"""Show the text input form to add a new waveform."""
self.new_waveform_panel.is_visible(True)

def _add_new_waveform(self, event):
"""Add the new waveform to CheckButtonGroup and update the
WaveformConfiguration."""
name = self.new_waveform_panel.input.value_input

# Add empty waveform to YAML
path = self.selector.active_group_path
new_waveform = self.config.parse_waveform(f"{name}:\n- {{}}")
self.config.add_waveform(new_waveform, self.path)
self.selection_group.sync_waveforms()
self.config.add_waveform(new_waveform, path)
self.selector._rebuild_tree()
self.new_waveform_panel.cancel()

def _on_add_group_button_click(self, event):
"""Show the text input form to add a new group."""
self.new_group_panel.is_visible(True)

def _on_rename_waveform_button_click(self, event):
"""Rename a waveform and update the configuration and GUI."""
selection = self.selection_group.get_selection()
selection = self.selector.selection
if len(selection) != 1:
pn.state.notifications.error(
"You must select only a single waveform to rename."
)
return
old_name = selection[0]

self.main_gui.rename_modal.show(
current_name=old_name, on_accept=self._rename_waveform
current_name=selection[0], on_accept=self._rename_waveform
)

def _rename_waveform(self, new_name):
"""Rename a waveform and update the GUI."""
old_name = self.selection_group.get_selection()[0]
old_name = self.selector.selection[0]
if new_name == old_name:
return
self.config.rename_waveform(old_name, new_name)
self.selection_group.sync_waveforms()
self.selection_group.set_selection([new_name])
self.selector._rebuild_tree()
self.selector.set_selection([new_name])

def _add_new_group(self, event):
"""Add the new group as a panel accordion and update the YAML."""
name = self.new_group_panel.input.value_input

# Create new group in configuration
new_group = self.config.add_group(name, self.path)

# Update UI
self.selection_group.add_group(new_group)
path = self.selector.active_group_path
new_group = self.config.add_group(name, path)
self.selector.active_group_path = path + [new_group.name]
self.selector._rebuild_tree()
self.new_group_panel.cancel()

def __panel__(self):
"""Returns the panel UI element."""
return self.panel
Loading
Loading