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
2 changes: 1 addition & 1 deletion village/devices/led_strip.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import traceback
from typing import Any

from pi5neo import Pi5Neo, EPixelType
from pi5neo import EPixelType, Pi5Neo

from village.classes.null_classes import NullLEDStrip
from village.scripts.log import log
Expand Down
10 changes: 8 additions & 2 deletions village/gui/data_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,10 @@ def mouseDoubleClickEvent(self, event) -> None:
)
if reply == QMessageBox.No:
return
super().mouseDoubleClickEvent(event)
try:
super().mouseDoubleClickEvent(event)
except RuntimeError:
return
self.save_changes_in_df()
elif flags & Qt.ItemIsEditable:
text = "Wait until the box is empty or synchronization is complete"
Expand Down Expand Up @@ -224,7 +227,10 @@ def mouseDoubleClickEvent(self, event) -> None:
self.clearSelection()

def save_changes_in_df(self, update: bool = True) -> None:
model = self._model()
try:
model = self._model()
except RuntimeError:
return
if update:
model.complete_df.loc[model.df.index] = model.df
if manager.table == DataTable.SUBJECTS:
Expand Down
24 changes: 24 additions & 0 deletions village/gui/settings_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ def _flush_to_pending(self) -> None:
]
with suppress(AttributeError, RuntimeError):
self._pending["SOUND_DEVICE"] = self.sound_device_combobox.currentText()
with suppress(AttributeError, RuntimeError):
txt = self.favourite_task_combobox.currentText()
self._pending["FAVOURITE_TASK"] = txt
with suppress(AttributeError, RuntimeError):
self._pending["PROJECT_DIRECTORY"] = (
self.project_directory_combobox.currentText()
Expand Down Expand Up @@ -760,6 +763,11 @@ def save(self, changing_project: bool, exiting: bool = False) -> None:
except Exception:
pass

try:
settings.set("FAVOURITE_TASK", self.favourite_task_combobox.currentText())
except Exception:
pass

cam_corridor.change = True
cam_box.change = True

Expand Down Expand Up @@ -871,6 +879,22 @@ def create_label_and_value(
)
self.sound_device_combobox.setProperty("type", type)

elif s.key == "FAVOURITE_TASK":
possible_values = ["None"] + list(manager.tasks.keys())
value = self._get(s.key)
index = possible_values.index(value) if value in possible_values else 0
self.favourite_task_combobox = self.create_and_add_combo_box(
s.key,
row,
column + width,
size2,
2,
possible_values,
index,
self.settings_changed,
)
self.favourite_task_combobox.setProperty("type", type)

elif s.value_type in (str, int, float):
project_dir = self._get("PROJECT_DIRECTORY")
value = str(self._get(s.key))
Expand Down
117 changes: 84 additions & 33 deletions village/gui/tasks_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,27 @@ def draw(self) -> None:
self.line_edits: dict[str, LineEdit] = {}
self.tasks_button.setDisabled(True)

# Centered between menu end (col 25) and right panel (col 89): (89-25-20)//2=22
_btn_col = C_COL + (SETTINGS_COL - C_COL - 20) // 2 # = 47
self.run_task_button = self.create_and_add_button(
"RUN TASK",
C_ROW,
_btn_col,
20,
2,
C_COL + 24,
34,
3,
self.run_task_button_clicked,
"Run the selected task",
"powderblue",
)

self._draw_menu()
self._draw_content_area()
self._draw_subject_selector()
self.check_buttons()

favourite = settings.get("FAVOURITE_TASK")
items = self._menu_items()
if favourite and favourite != "None" and favourite in items:
self.menu_list.setCurrentRow(items.index(favourite))

# ── Left menu ──────────────────────────────────────────────────────────────

def _draw_menu(self) -> None:
Expand Down Expand Up @@ -116,8 +120,10 @@ def _draw_menu(self) -> None:
)
self.menu_list.addItem(training_item)

favourite = settings.get("FAVOURITE_TASK")
for key in manager.tasks:
item = QListWidgetItem(key)
label = f"★ {key}" if key == favourite else key
item = QListWidgetItem(label)
item.setToolTip(f"Select the task {key}")
self.menu_list.addItem(item)

Expand All @@ -138,6 +144,60 @@ def _on_menu_changed(self, row: int) -> None:
cls = manager.tasks[name]
self.select_task(cls, name)

# ── Subject selector ─────────────────────────────────────────────────────
def _draw_subject_selector(self) -> None:
mylist = ["None"] + manager.subjects.df["name"].tolist()
self.possible_subjects = [
x
for x in mylist
if not pd.isna(x) and not (isinstance(x, str) and x.strip() == "")
]
last_subject = settings.get("LAST_SUBJECT")
if last_subject in self.possible_subjects:
self.subject_index = self.possible_subjects.index(last_subject)
else:
self.subject_index = 0

self.subject_label = self.create_and_add_label(
"Subject", C_ROW, C_COL + 4, 14, 1, "black"
)
self.subject_combo = self.create_and_add_combo_box(
"subject",
C_ROW + 1,
C_COL + 4,
16,
2,
self.possible_subjects,
self.subject_index,
self.select_subject,
)
self.subject_up_button = self.create_and_add_button(
"▲",
C_ROW + 1,
C_COL + 20,
2,
1,
lambda: self._step_subject(-1),
"Previous subject",
)
self.subject_down_button = self.create_and_add_button(
"▼",
C_ROW + 2,
C_COL + 20,
2,
1,
lambda: self._step_subject(1),
"Next subject",
)

def _step_subject(self, delta: int) -> None:
if not self.possible_subjects:
return
n = len(self.possible_subjects)
self.subject_combo.setCurrentIndex(
(self.subject_combo.currentIndex() + delta) % n
)

# ── Content area ───────────────────────────────────────────────────────────

def _draw_content_area(self) -> None:
Expand Down Expand Up @@ -186,6 +246,12 @@ def restart_tab_panel(self) -> None:
# ── Button state management ────────────────────────────────────────────────

def check_buttons(self) -> None:
subject_enabled = not manager.state.task_is_running() and (
self.testing_training or self.selected != ""
)
self.subject_combo.setEnabled(subject_enabled)
self.subject_up_button.setEnabled(subject_enabled)
self.subject_down_button.setEnabled(subject_enabled)
if manager.state.task_is_running():
self.run_task_button.setEnabled(False)
self.menu_list.setEnabled(False)
Expand All @@ -212,7 +278,6 @@ def check_buttons(self) -> None:

def select_task(self, cls: Type, name: str) -> None:
if issubclass(cls, TaskBase):
self.subject_index = 0
self.testing_training = False
self.selected = name
self.central_sub_layout.delete_optional_widgets("optional")
Expand Down Expand Up @@ -243,10 +308,9 @@ def select_task(self, cls: Type, name: str) -> None:
row_h = self.central_sub_layout.row_height
self.info_scroll.setFixedSize(60 * col_w, 30 * row_h)
self.central_sub_layout.addWidget(self.info_scroll, 2, 2, 30, 60)
self.create_gui_properties(testing_training=False)
self._apply_current_subject(testing_training=False)

def training_button_clicked(self) -> None:
self.subject_index = 0
self.testing_training = True
self.selected = ""
self.central_sub_layout.delete_optional_widgets("optional")
Expand All @@ -255,7 +319,15 @@ def training_button_clicked(self) -> None:
self.right_layout_general.delete_optional_widgets("optional2")
self.check_buttons()
manager.reset_subject_task_training()
self.create_gui_properties(testing_training=True)
self._apply_current_subject(testing_training=True)

def _apply_current_subject(self, testing_training: bool) -> None:
"""Loads the selector's subject, or builds defaults when None."""
subject = self.subject_combo.currentText()
if subject != "None":
self.select_subject(subject, "subject")
else:
self.create_gui_properties(testing_training=testing_training)

# ── GUI properties panel ───────────────────────────────────────────────────

Expand All @@ -265,29 +337,6 @@ def create_gui_properties(self, testing_training: bool) -> None:
self.right_layout_general.delete_optional_widgets("optional2")
self.restart_tab_panel()

self.subject_label = self.right_layout_general.create_and_add_label(
"Subject", 0, 2, 20, 2, "black"
)
self.subject_label.setProperty("type", "optional")

mylist = ["None"] + manager.subjects.df["name"].tolist()
self.possible_subjects = [
x
for x in mylist
if not pd.isna(x) and not (isinstance(x, str) and x.strip() == "")
]
self.subject_combo = self.right_layout_general.create_and_add_combo_box(
"subject",
0,
32,
30,
2,
self.possible_subjects,
self.subject_index,
self.select_subject,
)
self.subject_combo.setProperty("type", "optional")

remove_names = [
"next_task",
"maximum_duration",
Expand Down Expand Up @@ -351,6 +400,8 @@ def find_tab_by_label(self, label: str) -> Union[QWidget, None]:

def select_subject(self, value: str, key: str) -> None:
self.subject_index = self.subject_combo.currentIndex()
settings.set("LAST_SUBJECT", value)
settings.sync()
current_value = ""
if value != "None":
manager.subject.subject_series = manager.subjects.get_last_entry(
Expand Down
9 changes: 9 additions & 0 deletions village/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
"""Enables the Operant Box PCB. This setting allows the Raspberry Pi to control
some box components, such as LED stimuli, visible/infrared lighting, and motors.""",
),
Setting(
"FAVOURITE_TASK",
"None",
str,
"""A favourite (★) task that is preselected when opening the TASKS
tab, so that the user can start a session immediately.
Set to None to disable preselection.""",
),
]

sound_settings = [
Expand Down Expand Up @@ -770,6 +778,7 @@

hidden_settings = [
Setting("FIRST_LAUNCH", "OFF", Active, "First launch of the system."),
Setting("LAST_SUBJECT", "None", str, "The last subject selected in the TASKS tab."),
Setting(
"GITHUB_REPOSITORIES_DOWNLOADED",
"OFF",
Expand Down
Loading