diff --git a/src/aare/gui/main_window.py b/src/aare/gui/main_window.py index 306f2eed..dcc40ce0 100644 --- a/src/aare/gui/main_window.py +++ b/src/aare/gui/main_window.py @@ -9,8 +9,8 @@ from PySide6.QtWidgets import ( QHBoxLayout, QVBoxLayout, QMessageBox, - QApplication, - QDockWidget, QTabWidget) + QDockWidget, + QTabWidget, QFrame, QSizePolicy, QLabel) from aare.common.coordinate import Coordinate, SmargonCoordinate from aare.common.diffraction_geometry import DiffractionGeometry @@ -41,6 +41,7 @@ from aare.gui.threads.prediction_subscriber import PredictionSubscriber from aare.gui.threads.daq_worker import DAQWorker from aare.gui.threads.jfjoch_viewer import JFJochDBusClient from aare.gui.tutorials.tutorial_registration import register_tutorials +from aare.gui.widgets.alert_banner import AlertBanner from aare.gui.widgets.camera_image import SampleCameraImageLabel from aare.gui.widgets.no_wheel_scroll_area import NoWheelScrollArea from aare.gui.widgets.status_bar import StatusBar @@ -98,15 +99,23 @@ class MainWindow(QMainWindow): self.setStyleSheet("background-color: rgb(216, 228, 253);") - top_widget = QWidget(parent=self) + root_widget = QWidget(parent=self) + root_layout = QVBoxLayout(root_widget) + root_layout.setContentsMargins(0, 0, 0, 0) + root_layout.setSpacing(0) + + self.alert_banner = AlertBanner(parent=root_widget) + root_layout.addWidget(self.alert_banner) + + top_widget = QWidget(parent=root_widget) top_widget_layout = QHBoxLayout(top_widget) top_widget.setLayout(top_widget_layout) diffraction = DiffractionGeometry( energy_keV=12.4, dtz_mm=100, - detector_size_pxl=(1553,1630), - pixel_size_mm=0.150, #PILATUS 4 + detector_size_pxl=(1553, 1630), + pixel_size_mm=0.150, # PILATUS 4 beam_center_pxl=(750, 750), detector_description="PILATUS 4", detector_serial_number="1", @@ -114,7 +123,7 @@ class MainWindow(QMainWindow): poni_rot2_rad=-0.003839724 ) - geom = SampleGeometryModel(beam_location_pxl=Coordinate(x=1000,y=1000), + geom = SampleGeometryModel(beam_location_pxl=Coordinate(x=1000, y=1000), pixel_in_mm=0.001, aerotech=Coordinate(), smargon=SmargonCoordinate(sh_mm=Coordinate(), phi_deg=0, chi_deg=0), @@ -133,7 +142,6 @@ class MainWindow(QMainWindow): raster_mgr=self.raster, diffraction=diffraction) - top_widget_layout.addWidget(collection_controls_scroll) collection_controls_scroll.setWidget(self.data_collection) collection_controls_scroll.setHorizontalScrollBarPolicy( @@ -141,14 +149,14 @@ class MainWindow(QMainWindow): ) collection_controls_scroll.setFixedWidth(self.data_collection.set_width + 10) - self.video_tab = QTabWidget(parent=top_widget) - self.sample_camera = SampleCameraImageLabel(geom=geom, raster=self.raster, parent=top_widget, default_image=default_image) + self.sample_camera = SampleCameraImageLabel(geom=geom, raster=self.raster, parent=top_widget, + default_image=default_image) self.beamline_view_container = QWidget(parent=top_widget) self.beamline_view_layout = QVBoxLayout(self.beamline_view_container) - self.beamline_view_layout.setContentsMargins(0,0,0,0) + self.beamline_view_layout.setContentsMargins(0, 0, 0, 0) self.beamline_view_layout.setSpacing(6) self.beamline_view_1_combined = VideoGraphicsView() @@ -165,7 +173,7 @@ class MainWindow(QMainWindow): self.beamline_view_layout.addWidget(self.beamline_view) self.gonio_view = VideoGraphicsView() - #TODO add option to change cameras for gonio_camera_thread + # TODO add option to change cameras for gonio_camera_thread if gonio_cam_addr and gonio_cam_id: self.gonio_camera_thread = VideoThread(ip=gonio_cam_addr, camera=gonio_cam_id) self.gonio_camera_thread.frame_ready.connect(self.gonio_view.update_frame) @@ -190,8 +198,8 @@ class MainWindow(QMainWindow): ) beamline_controls_scroll.setFixedWidth(self.beamline.set_width + 10) - self.tell_samples = TellSamplePanel(samples=SampleShortInfoList(s = [])) - self.ref_tools_panel = ReferenceToolsPanel(samples=SampleShortInfoList(s = [])) + self.tell_samples = TellSamplePanel(samples=SampleShortInfoList(s=[])) + self.ref_tools_panel = ReferenceToolsPanel(samples=SampleShortInfoList(s=[])) self.job_list_panel = SampleQueuePanel() self.tell_samples_dock = QDockWidget("Sample List", self) @@ -227,7 +235,8 @@ class MainWindow(QMainWindow): self.face_panel_dock = QDockWidget("Face detection", self) self.face_panel_dock.setObjectName("face_panel_dock") self.face_panel_dock.setWidget(self.face_panel) - self.face_panel_dock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea | Qt.DockWidgetArea.LeftDockWidgetArea) + self.face_panel_dock.setAllowedAreas( + Qt.DockWidgetArea.RightDockWidgetArea | Qt.DockWidgetArea.LeftDockWidgetArea) self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.face_panel_dock) self.face_panel_dock.hide() @@ -235,7 +244,8 @@ class MainWindow(QMainWindow): self.fluor_panel_dock = QDockWidget("Fluorescence", self) self.fluor_panel_dock.setObjectName("fluor_panel_dock") self.fluor_panel_dock.setWidget(self.fluor_panel) - self.fluor_panel_dock.setAllowedAreas(Qt.DockWidgetArea.TopDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea) + self.fluor_panel_dock.setAllowedAreas( + Qt.DockWidgetArea.TopDockWidgetArea | Qt.DockWidgetArea.BottomDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.fluor_panel_dock) self.fluor_panel_dock.hide() @@ -260,7 +270,8 @@ class MainWindow(QMainWindow): self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.smargon_trace_dock) self.smargon_trace_dock.hide() - self.setCentralWidget(top_widget) + root_layout.addWidget(top_widget) + self.setCentralWidget(root_widget) self.setWindowTitle("AareGUI") self.create_menu_bar() @@ -343,7 +354,7 @@ class MainWindow(QMainWindow): self._samcam_source_timer.start() # -# self.data_collection.helical.helical_scan.connect(self.worker.helical_scan) + # self.data_collection.helical.helical_scan.connect(self.worker.helical_scan) # self.data_collection.helical.update_bookmarks.connect( # self.camera_image.update_bookmarks # ) @@ -447,6 +458,7 @@ class MainWindow(QMainWindow): self.daq.fluorimeter_spectrum_update.connect(lambda: self.fluor_panel_dock.setVisible(True)) self.daq.status_message.connect(self.status_bar.show_connection_message) + self.daq.status_message.connect(self.alert_banner.show_message) register_tutorials(self, self.tutorial_manager) diff --git a/src/aare/gui/widgets/alert_banner.py b/src/aare/gui/widgets/alert_banner.py new file mode 100644 index 00000000..fe10f4c7 --- /dev/null +++ b/src/aare/gui/widgets/alert_banner.py @@ -0,0 +1,84 @@ +from PySide6.QtCore import QTimer, Slot, Qt +from PySide6.QtGui import QColor +from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QSizePolicy, QGraphicsDropShadowEffect + +from aare.common.logger_config import setup_logger + +logger = setup_logger("aareGUI") + + +class AlertBanner(QFrame): + def __init__(self, parent=None): + super().__init__(parent) + + self._clear_timer = QTimer(self) + self._clear_timer.setSingleShot(True) + self._clear_timer.timeout.connect(self.clear_message) + + self._label = QLabel("", self) + self._label.setWordWrap(True) + self._label.setAlignment(Qt.AlignmentFlag.AlignCenter) + + layout = QHBoxLayout(self) + layout.setContentsMargins(24, 12, 24, 12) + layout.addWidget(self._label) + + shadow = QGraphicsDropShadowEffect(self) + shadow.setBlurRadius(18) + shadow.setOffset(0, 3) + shadow.setColor(QColor(0, 0, 0, 55)) + self.setGraphicsEffect(shadow) + + self.setVisible(False) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + + @Slot(str, bool) + def show_message(self, msg: str, is_error: bool = True): + self._clear_timer.stop() + + if not msg: + self.clear_message() + return + + if is_error: + decorated = f"🛑 {msg} 🛑" + self.setStyleSheet( + "QFrame {" + " background-color: #fbe4e6;" + " border: 2px solid #d97a84;" + " border-radius: 12px;" + " margin: 8px 12px 8px 12px;" + "}" + "QLabel {" + " color: #8f1d2c;" + " font-weight: 700;" + " font-size: 20px;" + " padding: 2px 6px 2px 6px;" + "}" + ) + else: + decorated = f"✅ {msg} ✅" + self.setStyleSheet( + "QFrame {" + " background-color: #e7f6ea;" + " border: 2px solid #7bbf8e;" + " border-radius: 12px;" + " margin: 8px 12px 8px 12px;" + "}" + "QLabel {" + " color: #1f6a3a;" + " font-weight: 700;" + " font-size: 20px;" + " padding: 2px 6px 2px 6px;" + "}" + ) + self._clear_timer.start(5000) + + self._label.setText(decorated) + self.setVisible(True) + + @Slot() + def clear_message(self): + self._clear_timer.stop() + self._label.clear() + self.setVisible(False)