Skip to content
Merged
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
7 changes: 6 additions & 1 deletion datashuttle/tui/custom_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from datashuttle.tui.app import TuiApp
from datashuttle.tui.interface import Interface
from datashuttle.tui.screens.datatypes import BaseDatatypeCheckboxes

from dataclasses import dataclass
from pathlib import Path
Expand Down Expand Up @@ -446,10 +447,14 @@ def get_sub_ses_names_and_datatype(
"""See `handle_fill_input_from_directorytree` for parameters."""
sub_names = self.query_one(sub_input_key).as_names_list()
ses_names = self.query_one(ses_input_key).as_names_list()
datatype = self.query_one("DatatypeCheckboxes").selected_datatypes()
datatype = self.get_datatype_checkbox_widget().selected_datatypes()

return sub_names, ses_names, datatype

def get_datatype_checkbox_widget(self) -> BaseDatatypeCheckboxes:
"""Get the Transfer or Create DatatypeCheckboxes widget."""
raise NotImplementedError


class TopLevelFolderSelect(Select):
"""A Select widget for display and updating of top-level-folders.
Expand Down
133 changes: 106 additions & 27 deletions datashuttle/tui/screens/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,22 +178,23 @@ def on_selection_list_selection_toggled(


# --------------------------------------------------------------------------------------
# DatatypeCheckboxes
# BaseDatatypeCheckboxes
# --------------------------------------------------------------------------------------


class DatatypeCheckboxes(Static):
class BaseDatatypeCheckboxes(Static):
"""Dynamically-populated checkbox widget for convenient datatype selection.

Parameters
----------
settings_key
'create' if datatype checkboxes for the create tab,
'transfer' for the transfer tab. Transfer tab includes
additional datatype options (e.g. "all").
Base class for a widget that allows the user to select datatypes.
The checkbox names are generated in the persistent_settings
configuration file, which stores all checkbox visible and
checked status across datashuttle sessions.

Attributes
----------
tab_name
Set by the subclass, indicates if the settings and checkbox names
should include "create" or "transfer"
datatype_config
A Dictionary containing datatype as key (e.g. "ephys", "behav")
and values are `bool` indicating whether the checkbox is on / off.
Expand All @@ -210,32 +211,29 @@ class DatatypeCheckboxes(Static):

"""

tab_name: Literal["create", "transfer"] # must be set by subclass

def __init__(
self,
interface: Interface,
create_or_transfer: Literal["create", "transfer"] = "create",
id: Optional[str] = None,
) -> None:
"""Initialise the DatatypeCheckboxes.
"""Initialise the BaseDatatypeCheckboxes.

Parameters
----------
interface
Datashuttle Interface object.

create_or_transfer
Whether we are on the "create" or "transfer" tab.

id
Textual ID for the DatatypeCheckboxes widget.
Textual ID for the BaseDatatypeCheckboxes widget.

"""
super(DatatypeCheckboxes, self).__init__(id=id)
super(BaseDatatypeCheckboxes, self).__init__(id=id)

self.interface = interface
self.create_or_transfer = create_or_transfer

self.settings_key = get_tui_settings_key_name(self.create_or_transfer)
self.settings_key = get_tui_settings_key_name(self.tab_name)

# `datatype_config` is basically just a convenience wrapper
# around interface.get_tui_settings
Expand All @@ -244,17 +242,17 @@ def __init__(
]

def compose(self) -> ComposeResult:
"""Add widgets to the DatatypeCheckboxes."""
"""Add widgets to the BaseDatatypeCheckboxes."""
for datatype, setting in self.datatype_config.items():
if setting["displayed"]:
yield Checkbox(
datatype.replace("_", " "),
id=get_checkbox_name(self.create_or_transfer, datatype),
id=get_checkbox_name(self.tab_name, datatype),
value=setting["on"],
)

@on(Checkbox.Changed)
def on_checkbox_changed(self) -> None:
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
"""Handle a datatype checkbox change.

When a checkbox is changed, update the `self.datatype_config`
Expand All @@ -264,19 +262,19 @@ def on_checkbox_changed(self) -> None:
for datatype in self.datatype_config.keys():
if self.datatype_config[datatype]["displayed"]:
self.datatype_config[datatype]["on"] = self.query_one(
f"#{get_checkbox_name(self.create_or_transfer, datatype)}"
f"#{get_checkbox_name(self.tab_name, datatype)}"
).value

self.interface.save_tui_settings(
self.datatype_config, self.settings_key
)

def on_mount(self) -> None:
"""Add widgets to the DatatypeCheckboxes."""
"""Add widgets to the BaseDatatypeCheckboxes."""
for datatype in self.datatype_config.keys():
if self.datatype_config[datatype]["displayed"]:
self.query_one(
f"#{get_checkbox_name(self.create_or_transfer, datatype)}"
f"#{get_checkbox_name(self.tab_name, datatype)}"
).tooltip = tooltips[datatype]

def selected_datatypes(self) -> List[str]:
Expand All @@ -289,22 +287,103 @@ def selected_datatypes(self) -> List[str]:
return selected_datatypes


class CreateDatatypeCheckboxes(BaseDatatypeCheckboxes):
"""Subclass of the data-type checkboxes for the "create" tab."""

tab_name: Literal["create", "transfer"] = "create"


class TransferDatatypeCheckboxes(BaseDatatypeCheckboxes):
"""Subclass of the data type checkboxes class for the transfer tab.

This subclass extends `on_checkbox_changed` by adding logic to
dynamically turn off mutually exclusive checkboxes when one is
selected, before delegating to the base implementation.

"""

tab_name: Literal["create", "transfer"] = "transfer"

def __init__(self, interface: Interface, id: Optional[str]):
"""Initialise TransferDatatypeCheckboxes.

Parameters
----------
interface
Datashuttle Interface object.

id
Textual ID for the DatatypeCheckboxes widget.

"""
super().__init__(interface, id)

@on(Checkbox.Changed)
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
Comment thread
JoeZiminski marked this conversation as resolved.
"""Dynamically turn off checkboxes depending on the activated checkbox then save.

In the transfer tab, we have a few different checkboxes that
are mutually exclusive. For example, `all` and `all_datatype`
are redundant. This function turns off checkboxes redundant
with the selected checkbox, before calling the super class
which saves the state of the currently selected checkboxes.
"""
checkbox = event.control
checkbox_name = get_datatype_from_checkbox_name(str(checkbox.id))

if checkbox.value:
all_datatypes_list = [
dtype
for dtype in self.datatype_config.keys()
if dtype not in ["all", "all_datatype", "all_non_datatype"]
]

if checkbox_name == "all":
to_turn_off = [
"all_datatype",
"all_non_datatype",
] + all_datatypes_list

elif checkbox_name == "all_datatype":
to_turn_off = ["all"] + all_datatypes_list

elif checkbox_name == "all_non_datatype":
to_turn_off = ["all"]

else:
to_turn_off = ["all", "all_datatype"]

for datatype in to_turn_off:
if self.datatype_config[datatype]["displayed"]:
self.query_one(
f"#{get_checkbox_name(self.tab_name, datatype)}"
).value = False

super().on_checkbox_changed(event)


# Helpers
# --------------------------------------------------------------------------------------


def get_checkbox_name(
create_or_transfer: Literal["create", "transfer"], datatype
tab_name: Literal["create", "transfer"], datatype
) -> str:
"""Return a canonical formatted checkbox name."""
return f"{create_or_transfer}_{datatype}_checkbox"
return f"{tab_name}_{datatype}_checkbox"


def get_datatype_from_checkbox_name(checkbox_name: str) -> str:
"""Get the datatype from the output of `get_checkbox_name()`."""
split_datatype = checkbox_name.split("_")[1:-1]
return "_".join(split_datatype)


def get_tui_settings_key_name(
create_or_transfer: Literal["create", "transfer"],
tab_name: Literal["create", "transfer"],
) -> str:
"""Return the canonical tui settings key."""
if create_or_transfer == "create":
if tab_name == "create":
settings_key = "create_checkboxes_on"
else:
settings_key = "transfer_checkboxes_on"
Expand Down
14 changes: 9 additions & 5 deletions datashuttle/tui/tabs/create_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
CreateFoldersSettingsScreen,
)
from datashuttle.tui.screens.datatypes import (
DatatypeCheckboxes,
CreateDatatypeCheckboxes,
DisplayedDatatypesScreen,
)
from datashuttle.tui.screens.modal_dialogs import (
Expand Down Expand Up @@ -95,7 +95,7 @@ def compose(self) -> ComposeResult:
)
yield Label("Datatype(s)", id="create_folders_datatype_label")
yield Container(
self.get_datatype_checkboxes_widget(),
self.create_datatype_checkboxes_widget(),
id="create_folders_datatype_container", #
)
yield Horizontal(
Expand Down Expand Up @@ -167,7 +167,7 @@ async def refresh_after_datatypes_changed(self, ignore) -> None:
"#create_folders_datatype_checkboxes"
).remove()

await container.mount(self.get_datatype_checkboxes_widget())
await container.mount(self.create_datatype_checkboxes_widget())

@require_double_click
def on_clickable_input_clicked(
Expand Down Expand Up @@ -543,8 +543,12 @@ def update_directorytree_root(self, new_root_path: Path) -> None:
"""Refresh the tree through the reactive attribute `path`."""
self.query_one("#create_folders_directorytree").path = new_root_path

def get_datatype_checkboxes_widget(self):
def create_datatype_checkboxes_widget(self):
"""Create the datatype checkboxes, centralised as used in multiple places."""
return DatatypeCheckboxes(
return CreateDatatypeCheckboxes(
self.interface, id="create_folders_datatype_checkboxes"
)

def get_datatype_checkbox_widget(self) -> CreateDatatypeCheckboxes:
"""Get the datatype checkboxes widget for the `create` tab."""
return self.query_one("CreateDatatypeCheckboxes")
18 changes: 10 additions & 8 deletions datashuttle/tui/tabs/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from rich.text import Text
from textual import work
from textual.containers import Container, Horizontal, Vertical
from textual.css.query import NoMatches
from textual.widgets import (
Button,
Checkbox,
Expand All @@ -32,8 +33,8 @@
TreeAndInputTab,
)
from datashuttle.tui.screens.datatypes import (
DatatypeCheckboxes,
DisplayedDatatypesScreen,
TransferDatatypeCheckboxes,
)
from datashuttle.tui.screens.modal_dialogs import (
ConfirmAndAwaitTransferPopup,
Expand Down Expand Up @@ -141,7 +142,7 @@ def compose(self) -> ComposeResult:
),
# These are almost identical to create tab
Label("Datatype(s)", id="transfer_datatype_label"),
self.get_datatypes_checkboxes_widget(),
self.create_datatype_checkboxes_widget(),
self.get_displayed_datatypes_button(),
]

Expand Down Expand Up @@ -217,8 +218,6 @@ def on_mount(self) -> None:
"#transfer_tab_overwrite_select",
"#transfer_tab_dry_run_checkbox",
]:
from textual.css.query import NoMatches

try:
# if checkbox is removed by user, hard to predict, skip.
self.query_one(id).tooltip = get_tooltip(id)
Expand Down Expand Up @@ -338,7 +337,7 @@ async def refresh_after_datatype_changed(self, ignore):
id="transfer_tab_displayed_datatypes_button",
),
)
await container.mount(self.get_datatypes_checkboxes_widget())
await container.mount(self.create_datatype_checkboxes_widget())
await container.mount(self.get_displayed_datatypes_button())

def on_custom_directory_tree_directory_tree_special_key_press(
Expand Down Expand Up @@ -422,11 +421,10 @@ def transfer_data(self) -> Worker[InterfaceOutput]:

return success, output

def get_datatypes_checkboxes_widget(self):
def create_datatype_checkboxes_widget(self):
"""Create the datatype checkboxes, centralised as used in multiple places."""
return DatatypeCheckboxes(
return TransferDatatypeCheckboxes(
self.interface,
create_or_transfer="transfer",
id="transfer_custom_datatype_checkboxes",
)

Expand All @@ -436,3 +434,7 @@ def get_displayed_datatypes_button(self):
"Displayed Datatypes",
id="transfer_tab_displayed_datatypes_button",
)

def get_datatype_checkbox_widget(self) -> TransferDatatypeCheckboxes:
"""Get the datatype checkboxes widget for the transfer tab."""
return self.query_one("TransferDatatypeCheckboxes")
Loading
Loading