comapct automation panel updates

This commit is contained in:
appleb_m
2026-06-09 11:59:51 +02:00
parent 46d76a5e0e
commit 87c4f5b241
5 changed files with 342 additions and 99 deletions
+119 -18
View File
@@ -10,7 +10,9 @@ from PySide6.QtWidgets import (
QVBoxLayout,
QMessageBox,
QDockWidget,
QTabWidget)
QTabWidget,
QStackedWidget
)
#Common imports
from aare.common.auth_models import BatonStatus
@@ -166,6 +168,9 @@ class MainWindow(QMainWindow):
self.alert_banner_secondary = AlertBanner(parent=root_widget)
root_layout.addWidget(self.alert_banner_secondary)
self.content_stack = QStackedWidget(parent=root_widget)
root_layout.addWidget(self.content_stack, 1)
top_widget = QWidget(parent=root_widget)
top_widget_layout = QHBoxLayout(top_widget)
top_widget.setLayout(top_widget_layout)
@@ -280,11 +285,24 @@ class MainWindow(QMainWindow):
self.compact_sample_camera = SampleCameraImageLabel(
geom=geom,
raster=self.raster,
parent=top_widget,
parent=root_widget,
default_image=default_image,
)
self.compact_automation_panel = CompactAutomationPanel(self.compact_sample_camera, parent=top_widget)
self.video_tab.addTab(self.compact_automation_panel, "Automation")
self.compact_automation_panel = CompactAutomationPanel(self.compact_sample_camera, parent=root_widget)
self.compact_automation_page = QWidget(parent=root_widget)
self.compact_automation_page.setObjectName("compactAutomationPage")
self.compact_automation_page.setStyleSheet(
"""
QWidget#compactAutomationPage {
background-color: #e8eefc;
}
"""
)
self.compact_automation_page_layout = QVBoxLayout(self.compact_automation_page)
self.compact_automation_page_layout.setContentsMargins(18, 18, 18, 18)
self.compact_automation_page_layout.setSpacing(0)
self.compact_automation_page_layout.addWidget(self.compact_automation_panel)
top_widget_layout.addWidget(self.video_tab)
self._start_axis_camera_threads()
@@ -301,7 +319,13 @@ class MainWindow(QMainWindow):
self.tell_samples = TellSamplePanel(samples=SampleShortInfoList(s=[]))
self.ref_tools_panel = ReferenceToolsPanel(samples=SampleShortInfoList(s=[]))
self.job_list_panel = SampleQueuePanel()
self.job_list_panel = SampleQueuePanel(show_user=self.__decoded_token.staff)
self.compact_automation_panel.play_pause_clicked.connect(self.job_list_panel.run)
self.compact_automation_panel.skip_clicked.connect(self.job_list_panel.skip_current_sample)
self.compact_automation_panel.step_through_toggled.connect(self.job_list_panel.set_step_through)
self.compact_automation_panel.show_full_view_requested.connect(self._return_from_compact_automation_view)
self.compact_automation_panel.annotation_selected.connect(self._handle_compact_annotation)
self.tell_samples_dock = QDockWidget("Sample List", self)
self.tell_samples_dock.setObjectName("tell_samples_dock")
@@ -373,17 +397,15 @@ class MainWindow(QMainWindow):
self.tabifyDockWidget(self.manual_sample_dock, self.automation_progress_dock)
self.tabifyDockWidget(self.automation_progress_dock, self.log_dock)
self.compact_automation_panel.play_pause_clicked.connect(self.job_list_panel.run)
self.compact_automation_panel.skip_clicked.connect(self.job_list_panel.skip_current_sample)
self.compact_automation_panel.step_through_toggled.connect(self.job_list_panel.set_step_through)
self.compact_automation_panel.show_full_view_requested.connect(self.restore_default_view)
self.job_list_panel.samples_in_queue_changed.connect(
self.automation_progress_panel.set_samples_in_queue
)
self.job_list_panel.automation_running_changed.connect(
self.automation_progress_panel.set_running
)
self.job_list_panel.automation_running_changed.connect(
self._on_automation_running_changed
)
self.job_list_panel.automation_running_changed.connect(
self.compact_automation_panel.set_running
)
@@ -393,10 +415,14 @@ class MainWindow(QMainWindow):
self.job_list_panel.samples_in_queue_changed.connect(
self._refresh_compact_queue_preview
)
self.automation_progress_panel.set_samples_in_queue(
len(self.job_list_panel.table_model.samples)
)
self.automation_progress_panel.set_running(self.job_list_panel.is_running())
self.compact_automation_panel.set_running(self.job_list_panel.is_running())
self.compact_automation_panel.set_step_through(self.job_list_panel.is_step_through())
self._refresh_compact_queue_preview()
# smargon trace panel
self.smargon_trace_panel = SmargonTracePanel()
@@ -437,7 +463,11 @@ class MainWindow(QMainWindow):
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.prediction_metrics_dock)
self.prediction_metrics_dock.hide()
root_layout.addWidget(top_widget)
self.content_stack.addWidget(top_widget)
self.content_stack.addWidget(self.compact_automation_page)
self.content_stack.setCurrentWidget(top_widget)
self._standard_main_page = top_widget
self.setCentralWidget(root_widget)
self.setWindowTitle("AareGUI")
@@ -873,7 +903,7 @@ class MainWindow(QMainWindow):
self.gonio_camera_thread.start()
def _all_tell_samples_in_default_order(self) -> list:
samples = list(self.tell_samples.table_model.samples)
samples = list(getattr(self.tell_samples.table_model, "samples", []))
return sorted(
[sample for sample in samples if sample.location is not None],
key=lambda sample: sample.loc_str_sort(),
@@ -885,7 +915,30 @@ class MainWindow(QMainWindow):
self._all_tell_samples_in_default_order()
)
self._refresh_compact_queue_preview()
self.video_tab.setCurrentWidget(self.compact_automation_panel)
self.tell_samples_dock.setVisible(False)
self.job_list_dock.setVisible(False)
self.manual_sample_dock.setVisible(False)
self.automation_progress_dock.setVisible(False)
self.face_panel_dock.setVisible(False)
self.fluor_panel_dock.setVisible(False)
self.smargon_trace_dock.setVisible(False)
self.target_stability_dock.setVisible(False)
self.prediction_metrics_dock.setVisible(False)
self.log_dock.setVisible(False)
if self.__decoded_token.staff:
self.ref_tools_dock.setVisible(False)
self.content_stack.setCurrentWidget(self.compact_automation_page)
@Slot()
def _return_from_compact_automation_view(self) -> None:
self.content_stack.setCurrentWidget(self._standard_main_page)
self.restore_default_view()
self.job_list_dock.setVisible(True)
self.tell_samples_dock.setVisible(True)
self.job_list_dock.raise_()
@Slot()
def _refresh_compact_queue_preview(self) -> None:
@@ -896,6 +949,52 @@ class MainWindow(QMainWindow):
next_next_sample,
)
@staticmethod
def _annotation_token(annotation: str) -> str:
mapping = {
"Heart": "❤️",
"Thumbs Up": "👍",
"Thumbs Down": "👎",
"Eyes": "👀",
"Scan Again": "scan again",
}
return mapping.get(annotation, str(annotation).strip())
@staticmethod
def _append_annotation_to_comment(existing_comment: str | None, annotation: str) -> str:
token = MainWindow._annotation_token(annotation)
current = str(existing_comment or "").strip()
if not current:
return token
current_tokens = [part.strip() for part in current.split(" | ") if part.strip()]
if token in current_tokens:
return current
return f"{current} | {token}"
@Slot(str)
def _handle_compact_annotation(self, annotation: str) -> None:
current_sample, _, _ = self.job_list_panel.queue_preview()
if current_sample is None:
self.status_bar.show_connection_message("No sample selected for annotation.", True)
return
updated_comment = self._append_annotation_to_comment(
getattr(current_sample, "comment", None),
annotation,
)
self.job_list_panel.annotate_sample_comment(current_sample.db_id, updated_comment)
self.tell_samples.annotate_sample_comment(current_sample.db_id, updated_comment)
self._refresh_compact_queue_preview()
self.status_bar.show_connection_message(
f"Annotation added: {self._annotation_token(annotation)}{current_sample.sample_name}",
False,
)
@Slot()
def refresh_axis_cameras(self) -> None:
logger.info("Refreshing Axis camera threads")
@@ -1032,7 +1131,7 @@ class MainWindow(QMainWindow):
beamline_combined_tab_action.triggered.connect(lambda: self.video_tab.setCurrentIndex(3))
view_menu.addAction(beamline_combined_tab_action)
compact_automation_action = QAction("Compact Automation View", self)
compact_automation_action = QAction("Automation View", self)
compact_automation_action.setShortcut(QKeySequence("Ctrl+5"))
compact_automation_action.triggered.connect(self.enter_compact_automation_view)
view_menu.addAction(compact_automation_action)
@@ -1087,6 +1186,8 @@ class MainWindow(QMainWindow):
if self._default_window_state is not None:
self.restoreState(self._default_window_state)
self.content_stack.setCurrentWidget(self._standard_main_page)
self.tell_samples_dock.setVisible(True)
self.job_list_dock.setVisible(True)
self.manual_sample_dock.setVisible(True)
@@ -1389,6 +1490,8 @@ class MainWindow(QMainWindow):
s.geom.beam_location_pxl.y,
)
self._refresh_compact_queue_preview()
current_session = int(getattr(self.__decoded_token, "session", -1))
for gui in getattr(s, "open_guis", []) or []:
try:
@@ -1405,14 +1508,12 @@ class MainWindow(QMainWindow):
if self._remote_close_deadline_ts is not None:
self._clear_remote_close_request()
self._refresh_compact_queue_preview()
if not self.__mounting and s.state == BeamlineStateEnum.RobotSampleExchange:
self.__mounting = True
self.video_tab.setCurrentIndex(3)
self.video_tab.setCurrentWidget(self.beamline_combined_panel)
elif self.__mounting and s.state != BeamlineStateEnum.RobotSampleExchange:
self.__mounting = False
self.video_tab.setCurrentIndex(0)
self.video_tab.setCurrentWidget(self.sample_camera)
# ========== BATON DIALOG HANDLING ==========
+40 -16
View File
@@ -4,31 +4,44 @@ from PySide6.QtGui import QBrush, QColor
from aare.common.models import SampleShortInfo, SampleShortInfoList
def get_entry(sample: SampleShortInfo, column: int):
if column == 0:
return sample.user
elif column == 1:
return sample.puck_name
elif column == 2:
return sample.sample_name
def get_entry(sample: SampleShortInfo, column: int, *, show_user: bool = False):
if show_user:
if column == 0:
return sample.user
elif column == 1:
return sample.dewar_name
elif column == 2:
return sample.puck_name
elif column == 3:
return sample.sample_name
else:
if column == 0:
return sample.dewar_name
elif column == 1:
return sample.puck_name
elif column == 2:
return sample.sample_name
return ""
class SampleQueueSpreadsheet(QAbstractTableModel):
def __init__(
self,
parent=None,
samples: list[SampleShortInfo] | None = None
samples: list[SampleShortInfo] | None = None,
show_user: bool = False,
):
super().__init__(parent)
if samples is None:
samples = []
self.__running = False
self._show_user = bool(show_user)
self.samples: list[SampleShortInfo] = samples
self.header = [
"User",
"Puck",
"Sample",
]
self.header = (
["User", "Dewar", "Puck", "Sample"]
if self._show_user
else ["Dewar", "Puck", "Sample"]
)
def flags(self, index):
default_flags = super().flags(index)
@@ -42,7 +55,7 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
def data(self, index, role=None):
if role == Qt.ItemDataRole.DisplayRole:
return get_entry(self.samples[index.row()], index.column())
return get_entry(self.samples[index.row()], index.column(), show_user=self._show_user)
elif role == Qt.ItemDataRole.TextAlignmentRole:
return Qt.AlignmentFlag.AlignCenter
elif role == Qt.ItemDataRole.BackgroundRole:
@@ -119,16 +132,27 @@ class SampleQueueSpreadsheet(QAbstractTableModel):
self.samples = []
self.endResetModel()
def annotate_sample(self, db_id: int, comment: str) -> None:
self.beginResetModel()
updated_samples: list[SampleShortInfo] = []
for sample in self.samples:
if sample.db_id == db_id:
updated_samples.append(sample.model_copy(update={"comment": comment}))
else:
updated_samples.append(sample)
self.samples = updated_samples
self.endResetModel()
def to_state(self) -> dict:
return {
"samples": [s.to_dict() for s in self.samples],
"samples": [s.model_dump() for s in self.samples],
}
def from_state(self, state: dict):
self.beginResetModel()
self.samples = [
SampleShortInfo.from_dict(d)
SampleShortInfo.model_validate(d)
for d in state.get("samples", [])
]
+143 -18
View File
@@ -30,41 +30,162 @@ class CompactAutomationPanel(QFrame):
self._next_sample: SampleShortInfo | None = None
self._next_next_sample: SampleShortInfo | None = None
self.setFrameShape(QFrame.Shape.StyledPanel)
self.setFrameShape(QFrame.Shape.NoFrame)
self.setObjectName("compactAutomationPanel")
self.setStyleSheet(
"""
QFrame#compactAutomationPanel {
background: #eef4ff;
border: none;
border-radius: 16px;
}
QFrame#compactAutomationHeader {
background: #1f3b63;
border-radius: 14px;
}
QLabel#compactAutomationTitle {
color: white;
font-size: 20px;
font-weight: 700;
}
QLabel#compactAutomationSubtitle {
color: #d7e6ff;
font-size: 12px;
}
QToolButton#compactMenuButton {
background: rgba(255, 255, 255, 0.14);
color: white;
border: 1px solid rgba(255, 255, 255, 0.22);
border-radius: 18px;
padding: 8px 12px;
font-size: 18px;
font-weight: 700;
}
QToolButton#compactMenuButton:hover {
background: rgba(255, 255, 255, 0.24);
}
QFrame#compactInfoPanel {
background: white;
border: 1px solid #d5e0f2;
border-radius: 14px;
}
QLabel#compactSectionTitle {
color: #1f3b63;
font-size: 14px;
font-weight: 700;
}
QLabel#compactInfoLabel {
background: #f6f9ff;
border: 1px solid #dde7f6;
border-radius: 10px;
color: #24364d;
padding: 10px 12px;
font-size: 13px;
}
QPushButton#compactPrimaryButton {
background: #2f6fed;
color: white;
border: none;
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
font-weight: 700;
}
QPushButton#compactPrimaryButton:hover {
background: #255fd0;
}
QPushButton#compactSecondaryButton {
background: white;
color: #1f3b63;
border: 1px solid #c8d7ef;
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
font-weight: 700;
}
QPushButton#compactSecondaryButton:hover,
QToolButton#compactSecondaryButton:hover {
background: #f4f8ff;
}
QToolButton#compactSecondaryButton {
background: white;
color: #1f3b63;
border: 1px solid #c8d7ef;
border-radius: 12px;
padding: 12px 16px;
font-size: 16px;
font-weight: 700;
}
"""
)
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(12, 12, 12, 12)
main_layout.setSpacing(10)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(14)
top_bar = QHBoxLayout()
top_bar.setSpacing(8)
header = QFrame(self)
header.setObjectName("compactAutomationHeader")
header_layout = QHBoxLayout(header)
header_layout.setContentsMargins(14, 12, 14, 12)
header_layout.setSpacing(10)
self.menu_button = QToolButton(self)
self.menu_button.setObjectName("compactMenuButton")
self.menu_button.setText("")
self.menu_button.setToolTip("Return to full view")
self.menu_button.clicked.connect(self.show_full_view_requested.emit)
self.title_label = QLabel("Automation mode", self)
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
title_container = QWidget(self)
title_layout = QVBoxLayout(title_container)
title_layout.setContentsMargins(0, 0, 0, 0)
title_layout.setSpacing(2)
top_bar.addWidget(self.menu_button)
top_bar.addWidget(self.title_label, 1)
self.title_label = QLabel("Automation View", self)
self.title_label.setObjectName("compactAutomationTitle")
main_layout.addLayout(top_bar)
self.subtitle_label = QLabel("Focused queue control and sample camera", self)
self.subtitle_label.setObjectName("compactAutomationSubtitle")
title_layout.addWidget(self.title_label)
title_layout.addWidget(self.subtitle_label)
header_layout.addWidget(self.menu_button, 0, Qt.AlignmentFlag.AlignTop)
header_layout.addWidget(title_container, 1)
main_layout.addWidget(header)
main_layout.addWidget(camera_widget, 1)
info_panel = QFrame(self)
info_panel.setObjectName("compactInfoPanel")
info_layout = QVBoxLayout(info_panel)
info_layout.setContentsMargins(10, 10, 10, 10)
info_layout.setSpacing(6)
info_layout.setContentsMargins(14, 14, 14, 14)
info_layout.setSpacing(8)
info_title = QLabel("Queue preview", self)
info_title.setObjectName("compactSectionTitle")
self.current_label = QLabel("Current: —", self)
self.next_label = QLabel("Next: —", self)
self.next_next_label = QLabel("Next next: —", self)
self.next_next_label = QLabel("Then: —", self)
for label in (self.current_label, self.next_label, self.next_next_label):
label.setObjectName("compactInfoLabel")
label.setWordWrap(True)
info_layout.addWidget(info_title)
info_layout.addWidget(self.current_label)
info_layout.addWidget(self.next_label)
info_layout.addWidget(self.next_next_label)
@@ -75,17 +196,21 @@ class CompactAutomationPanel(QFrame):
button_row.setSpacing(10)
self.play_pause_button = QPushButton("▶ Play", self)
self.play_pause_button.setObjectName("compactPrimaryButton")
self.play_pause_button.clicked.connect(self.play_pause_clicked.emit)
self.skip_button = QPushButton("⏭ Skip", self)
self.skip_button.setObjectName("compactSecondaryButton")
self.skip_button.clicked.connect(self.skip_clicked.emit)
self.step_button = QPushButton("Step Through", self)
self.step_button.setObjectName("compactSecondaryButton")
self.step_button.setCheckable(True)
self.step_button.toggled.connect(self._on_step_toggled)
self.annotation_button = QToolButton(self)
self.annotation_button.setText("+")
self.annotation_button.setObjectName("compactSecondaryButton")
self.annotation_button.setText("Annotate")
self.annotation_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
annotation_menu = QMenu(self.annotation_button)
@@ -96,10 +221,10 @@ class CompactAutomationPanel(QFrame):
)
self.annotation_button.setMenu(annotation_menu)
button_row.addWidget(self.play_pause_button, 1)
button_row.addWidget(self.play_pause_button, 2)
button_row.addWidget(self.skip_button, 1)
button_row.addWidget(self.step_button, 1)
button_row.addWidget(self.annotation_button)
button_row.addWidget(self.annotation_button, 1)
main_layout.addLayout(button_row)
@@ -128,7 +253,7 @@ class CompactAutomationPanel(QFrame):
self.current_label.setText(f"Current: {self._format_sample(current_sample)}")
self.next_label.setText(f"Next: {self._format_sample(next_sample)}")
self.next_next_label.setText(f"Next next: {self._format_sample(next_next_sample)}")
self.next_next_label.setText(f"Then: {self._format_sample(next_next_sample)}")
def _on_step_toggled(self, checked: bool) -> None:
self._step_through = checked
@@ -138,4 +263,4 @@ class CompactAutomationPanel(QFrame):
def _format_sample(sample: SampleShortInfo | None) -> str:
if sample is None:
return ""
return f"{sample.sample_name} ({sample.loc_str()})"
return f"{sample.sample_name} ({sample.loc_str()})"
+25 -37
View File
@@ -25,13 +25,14 @@ class SampleQueuePanel(QFrame):
automation_running_changed = Signal(bool)
step_through_changed = Signal(bool)
def __init__(self, parent=None, samples: SampleShortInfoList | None = None):
def __init__(self, parent=None, samples: SampleShortInfoList | None = None, show_user: bool = False):
super().__init__(parent)
self.__baton_holder = None
self.__busy = None
self.__beamline_state = None
self.__pause = True
self.__set_to_pause = False
self.__warning_msg_box = None
self._current_db_id: int | None = None
self._step_through = False
self.ring_current = None
@@ -49,26 +50,18 @@ class SampleQueuePanel(QFrame):
self.setLayout(layout)
layout.addWidget(TitleLabel("Sample queue", self))
# Create the table
self.table_view = QTableView(self)
# Create the custom table model
self.table_model = SampleQueueSpreadsheet()
# Create the QTableView and set the model
self.table_model = SampleQueueSpreadsheet(show_user=show_user)
self.table_view.setModel(self.table_model)
# Enable automatic column resizing
header = self.table_view.horizontalHeader()
header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch) # Columns stretch to fill the table
header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
# Set the table's size policy to expand
self.table_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.table_view.setAcceptDrops(True)
self.table_view.setDropIndicatorShown(True)
# Enable selection of multiple rows
self.table_view.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
# Set up keyboard shortcut for delete
self.delete_shortcut = QShortcut(QKeySequence.StandardKey.Delete, self.table_view)
self.delete_shortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut)
self.delete_shortcut.activated.connect(self.remove_selected_samples)
@@ -77,9 +70,8 @@ class SampleQueuePanel(QFrame):
self.run_pause_shortcut.setContext(Qt.ShortcutContext.WidgetWithChildrenShortcut)
self.run_pause_shortcut.activated.connect(self.run)
layout.addWidget(self.table_view) # Add table to the layout
layout.addWidget(self.table_view)
# Create a horizontal layout for the buttons
button_layout = QHBoxLayout()
self.play_button = QPushButton("▶ Run", self)
self.play_button.clicked.connect(self.run)
@@ -98,7 +90,6 @@ class SampleQueuePanel(QFrame):
button_layout.addWidget(self.clear_button)
button_layout.addWidget(self.park_and_dry_when_cleared)
# Add the button layout to the main layout
layout.addLayout(button_layout)
self._emit_samples_in_queue_changed()
self.automation_running_changed.emit(False)
@@ -149,6 +140,9 @@ class SampleQueuePanel(QFrame):
)
self.queue_samples(ordered, replace=True)
def annotate_sample_comment(self, db_id: int, comment: str) -> None:
self.table_model.annotate_sample(db_id, comment)
def skip_current_sample(self) -> None:
if not self.table_model.samples:
return
@@ -171,16 +165,12 @@ class SampleQueuePanel(QFrame):
self._emit_samples_in_queue_changed()
def remove_selected_samples(self):
"""Remove selected samples from the queue."""
selected_indexes = self.table_view.selectionModel().selectedRows()
if not selected_indexes:
return
# Get the row numbers and sort them in descending order
# This ensures we remove from bottom to top to avoid index shifting
rows = sorted([index.row() for index in selected_indexes], reverse=True)
# Remove samples by their database IDs
for row in rows:
if 0 <= row < len(self.table_model.samples):
sample = self.table_model.samples[row]
@@ -191,7 +181,7 @@ class SampleQueuePanel(QFrame):
def ring_current_check(self):
if not ring_current_low_check(self, self.ring_current):
self.table_model.set_running(False)
self.set_to_pause = True
self.__set_to_pause = True
self.__pause = True
self.play_button.setText("▶ Run")
self._emit_samples_in_queue_changed()
@@ -199,7 +189,7 @@ class SampleQueuePanel(QFrame):
return True
def resume_automation(self):
if hasattr(self, '__warning_msg_box') and self.__warning_msg_box:
if self.__warning_msg_box:
self.__warning_msg_box.done(0)
self.__warning_msg_box = None
@@ -218,14 +208,15 @@ class SampleQueuePanel(QFrame):
self.__warning_msg_box.setText("TELL reported a warning. Please see console for details.")
self.__warning_msg_box.setInformativeText(
"Automation paused for 10 minutes to allow sufficient dry time."
"\nClick 'Continue Now' to resume immediately, or wait for auto-resume.")
"\nClick 'Continue Now' to resume immediately, or wait for auto-resume."
)
continue_btn = self.__warning_msg_box.addButton("Continue Now", QMessageBox.ButtonRole.AcceptRole)
self.__warning_msg_box.setWindowModality(Qt.WindowModality.NonModal)
continue_btn.clicked.connect(self.manual_resume_from_warning)
self.__warning_msg_box.show()
def show_error_dialog(self, title:str, msg:str, info:str = None):
def show_error_dialog(self, title: str, msg: str, info: str | None = None):
self.__warning_msg_box = QMessageBox(self)
self.__warning_msg_box.setIcon(QMessageBox.Icon.Warning)
self.__warning_msg_box.setWindowTitle(title)
@@ -250,24 +241,25 @@ class SampleQueuePanel(QFrame):
def run(self):
self.__recovery_timer.stop()
if CHECK_ENABLED:
if hasattr(self, '__warning_msg_box') and self.__warning_msg_box:
if self.__warning_msg_box:
self.__warning_msg_box.done(0)
self.__warning_msg_box = None
if self.__beamline_state != BeamlineStateEnum.SampleAlignment:
self.show_error_dialog(title="wrong beamline state",
msg="Cannot run automation while beamline is not in sample alignment state",
info="Please change the state at the bottom of the menu")
self.show_error_dialog(
title="wrong beamline state",
msg="Cannot run automation while beamline is not in sample alignment state",
info="Please change the state at the bottom of the menu",
)
return
if self.__pause:
if self.__busy:
self.show_error_dialog(
title="Beamline is busy",
msg="Cannot run automation while beamline is busy",
info="Please wait until beamline is idle "
"or contact your local contact for support"
"or contact your local contact for support",
)
return
@@ -276,7 +268,7 @@ class SampleQueuePanel(QFrame):
title="Session is vacant",
msg="Starting automation while session is vacant is not currently implemented",
info="Please grab the baton before continuing "
"or contact your local contact for support"
"or contact your local contact for support",
)
return
@@ -286,7 +278,7 @@ class SampleQueuePanel(QFrame):
msg="You do not hold the baton.",
info="Please request the baton if it is your shift."
"If your baton request is denied and it should be the start of your shift,"
"please contact your local contact for support"
"please contact your local contact for support",
)
return
@@ -323,12 +315,13 @@ class SampleQueuePanel(QFrame):
self.show_error_dialog(
title="No Samples in Queue",
msg="Cannot run automation as there are no samples in the queue.",
info="Please add samples to the queue."
info="Please add samples to the queue.",
)
return
else:
self.__set_to_pause = True
self.__pause = True
self.table_model.set_running(False)
self.play_button.setText("▶ Run")
self._emit_samples_in_queue_changed()
self.automation_running_changed.emit(False)
@@ -356,16 +349,13 @@ class SampleQueuePanel(QFrame):
self.automation_running_changed.emit(False)
@Slot(int, bool)
def automated_scan_done(self, db_id: int, success: bool, reply:str):
def automated_scan_done(self, db_id: int, success: bool, reply: str):
if self._current_db_id is not None and db_id != self._current_db_id:
return
if CHECK_ENABLED and (self.ring_current is None or (self.ring_current < LOW_CURRENT_THRESHOLD)):
# Pause UI state
self.pause_automation(set_id_to_None=False)
# This dialog should auto-accept when current_ok() returns True.
# No user button press is required to continue.
def current_ok() -> bool:
return (self.ring_current is not None) and (self.ring_current >= LOW_CURRENT_THRESHOLD)
@@ -403,7 +393,6 @@ class SampleQueuePanel(QFrame):
self._current_db_id = None
self.pause_automation()
logger.warning("TELL Warning: Pausing for 10 minutes.")
# recovery timer of ten minutes to allow Tell to dry. However user should be able to interupt
self.__recovery_timer.start()
self.show_warning_recovery_dialog()
@@ -425,7 +414,6 @@ class SampleQueuePanel(QFrame):
else:
self.table_model.remove_sample(db_id)
self._current_db_id = None
# Missing or other errors -> continue to next sample
if not self.__pause and len(self.table_model.samples) > 0:
next_item = self.table_model.samples[0]
if self._step_through:
+15 -10
View File
@@ -52,15 +52,12 @@ class TellSamplePanel(QFrame):
grid_layout.setColumnStretch(0, 1)
grid_layout.setColumnStretch(1, 0)
# Create the custom table model
self.table_model = UserSampleSpreadsheet(samples=samples.s)
# Create the QTableView and set the model
self.table_view.setModel(self.table_model)
self.table_view.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Stretch
)
# Enable the custom context menu
self.table_view.setSortingEnabled(True)
self.table_view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.table_view.customContextMenuRequested.connect(self.context_menu)
@@ -88,26 +85,34 @@ class TellSamplePanel(QFrame):
def new_sample_list(self, samples: SampleShortInfoList):
self.table_model.updateData(samples=samples.s)
def annotate_sample_comment(self, db_id: int, comment: str) -> None:
samples = list(self.table_model.samples)
updated_samples: list[SampleShortInfo] = []
for sample in samples:
if sample.db_id == db_id:
updated_samples.append(sample.model_copy(update={"comment": comment}))
else:
updated_samples.append(sample)
self.table_model.updateData(samples=updated_samples)
def context_menu(self, position):
# Determine the row where the user clicked
index = self.table_view.indexAt(position)
if not index.isValid():
return # Do nothing if user didn't click on a valid item
return
row = index.row()
sample = self.table_model.get_id(row)
if sample.location is None:
return # Do nothing if sample not mounted
return
# Create the context menu
menu = QMenu()
mount_action = menu.addAction("Mount") # Add "Mount" to the menu
mount_action = menu.addAction("Mount")
# Display the menu at the cursor position
action = menu.exec_(self.table_view.viewport().mapToGlobal(position))
# Handle the result of the selected action
if action == mount_action:
self.mount.emit(sample)