| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- """
- LFMRIWindow - main application window for the unified LF-MRI GUI.
- Single nav-bar design: tab buttons on the left.
- The native QMenuBar and QTabWidget tab-bar are both hidden; a QToolBar
- provides a unified, flat navigation strip.
- """
- from __future__ import annotations
- import os
- from PySide6.QtCore import Qt, QSize
- from src import i18n
- from PySide6.QtWidgets import (
- QApplication,
- QButtonGroup,
- QFrame,
- QLabel,
- QMainWindow,
- QPushButton,
- QSizePolicy,
- QStatusBar,
- QTabWidget,
- QToolBar,
- QWidget,
- )
- from src.tabs.fid_tab import FidTab
- from src.tabs.scanner_tab import ScannerTab
- from src.tabs.scanning_tab import ScanningTab
- from src.tabs.seq_interp_tab import SeqInterpTab
- from src.tabs.spectroscopy_tab import SpectroscopyTab
- _TAB_NAV_KEYS = [
- "tab_nav_sequence",
- "tab_nav_scanner",
- "tab_nav_fid",
- "tab_nav_scanning",
- "tab_nav_spectro",
- ]
- _NAV_BG = "#0f0f1e"
- _NAV_H = 38
- _TAB_BTN_CSS = """
- QPushButton {
- background: transparent;
- color: #7777aa;
- border: none;
- border-bottom: 2px solid transparent;
- padding: 0px 20px;
- font-size: 12px;
- min-height: 36px;
- }
- QPushButton:checked {
- color: #ffffff;
- border-bottom: 2px solid #f0c040;
- }
- QPushButton:hover:!checked {
- color: #aaaacc;
- background: #17172e;
- }
- """
- _LANG_BTN_CSS = f"""
- QPushButton {{
- background: transparent;
- color: #555577;
- border: 1px solid transparent;
- border-radius: 3px;
- padding: 0px 7px;
- font-size: 11px;
- font-weight: bold;
- min-height: 22px;
- max-height: 22px;
- }}
- QPushButton:checked {{
- color: #f0c040;
- border-color: #f0c040;
- }}
- QPushButton:hover:!checked {{
- color: #aaaacc;
- }}
- """
- _NAV_TOOLBAR_CSS = f"""
- QToolBar {{
- background: {_NAV_BG};
- border: none;
- border-bottom: 1px solid #1e1e38;
- spacing: 0px;
- padding: 0px;
- }}
- """
- class LFMRIWindow(QMainWindow):
- """Unified LF-MRI application window."""
- def __init__(
- self,
- hw_config_path: str | None = None,
- output_dir: str | None = None,
- seq_file: str | None = None,
- orchestrator_url: str = "http://localhost:1717",
- seq_interp_url: str = "http://localhost:7475",
- spectroscopy_url: str = "http://localhost:8002",
- ) -> None:
- super().__init__()
- self.setWindowTitle("LF-MRI System")
- self.setMinimumSize(960, 640)
- self._hw_config_path = hw_config_path
- self._output_dir = output_dir
- self._seq_tab = SeqInterpTab(
- hw_config_path=hw_config_path,
- output_dir=output_dir,
- seq_interp_url=seq_interp_url,
- )
- self._scanner_tab = ScannerTab(
- hw_config_path=hw_config_path,
- orchestrator_url=orchestrator_url,
- seq_interp_url=seq_interp_url,
- spectroscopy_url=spectroscopy_url,
- )
- self._fid_tab = FidTab(
- hw_config_path=hw_config_path,
- output_dir=output_dir,
- )
- self._scanning_tab = ScanningTab()
- self._scanning_tab.set_orchestrator_url(orchestrator_url)
- self._spectroscopy_tab = SpectroscopyTab(spectroscopy_url=spectroscopy_url)
- self._tabs = QTabWidget()
- self._tabs.tabBar().hide()
- self._tabs.setDocumentMode(True)
- self._tabs.addTab(self._seq_tab, i18n.tr(_TAB_NAV_KEYS[0]))
- self._tabs.addTab(self._scanner_tab, i18n.tr(_TAB_NAV_KEYS[1]))
- self._tabs.addTab(self._fid_tab, i18n.tr(_TAB_NAV_KEYS[2]))
- self._tabs.addTab(self._scanning_tab, i18n.tr(_TAB_NAV_KEYS[3]))
- self._tabs.addTab(self._spectroscopy_tab, i18n.tr(_TAB_NAV_KEYS[4]))
- self._tabs.currentChanged.connect(self._on_tab_changed)
- self.setCentralWidget(self._tabs)
- self._fid_tab.fid_seq_generated.connect(self._on_fid_generated)
- self._seq_tab.ready_for_scan.connect(self._on_ready_for_scan)
- self._scanning_tab.scan_job_started.connect(self._scanner_tab.attach_job)
- self._scanner_tab.scan_result_ready.connect(self._spectroscopy_tab.receive_scan_data)
- self.menuBar().hide()
- self._build_nav_bar()
- self._build_status_bar()
- self._size_and_center()
- if seq_file and os.path.isfile(seq_file):
- self._seq_tab.load_seq_file(os.path.abspath(seq_file))
- def _build_nav_bar(self) -> None:
- tb = QToolBar("Navigation", self)
- tb.setMovable(False)
- tb.setFloatable(False)
- tb.setIconSize(QSize(0, 0))
- tb.setStyleSheet(_NAV_TOOLBAR_CSS)
- tb.setFixedHeight(_NAV_H)
- self.addToolBar(Qt.TopToolBarArea, tb)
- lbl = QLabel(" LF-MRI ")
- lbl.setStyleSheet(
- f"color: #444466; font-weight: bold; font-size: 11px; "
- f"background: {_NAV_BG}; padding: 0 4px;"
- )
- tb.addWidget(lbl)
- sep = _VSep(tb)
- tb.addWidget(sep)
- self._nav_btn_group = QButtonGroup(self)
- self._nav_btn_group.setExclusive(True)
- self._nav_tab_buttons: list[QPushButton] = []
- for i, key in enumerate(_TAB_NAV_KEYS):
- btn = QPushButton(i18n.tr(key))
- btn.setCheckable(True)
- btn.setStyleSheet(_TAB_BTN_CSS)
- btn.setFixedHeight(_NAV_H)
- btn.setCursor(Qt.PointingHandCursor)
- self._nav_btn_group.addButton(btn, i)
- tb.addWidget(btn)
- self._nav_tab_buttons.append(btn)
- btn.clicked.connect(lambda _checked, idx=i: self._switch_tab(idx))
- self._nav_tab_buttons[0].setChecked(True)
- spacer = QWidget()
- spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
- spacer.setStyleSheet(f"background: {_NAV_BG};")
- tb.addWidget(spacer)
- # Language toggle (EN / RU)
- self._lang_btn_group = QButtonGroup(self)
- self._lang_btn_group.setExclusive(True)
- self._btn_lang_en = QPushButton("EN")
- self._btn_lang_en.setCheckable(True)
- self._btn_lang_en.setChecked(True)
- self._btn_lang_en.setStyleSheet(_LANG_BTN_CSS)
- self._btn_lang_en.setCursor(Qt.PointingHandCursor)
- self._btn_lang_ru = QPushButton("RU")
- self._btn_lang_ru.setCheckable(True)
- self._btn_lang_ru.setStyleSheet(_LANG_BTN_CSS)
- self._btn_lang_ru.setCursor(Qt.PointingHandCursor)
- self._lang_btn_group.addButton(self._btn_lang_en)
- self._lang_btn_group.addButton(self._btn_lang_ru)
- self._btn_lang_en.clicked.connect(lambda: self._on_language_change("en"))
- self._btn_lang_ru.clicked.connect(lambda: self._on_language_change("ru"))
- tb.addWidget(self._btn_lang_en)
- tb.addWidget(self._btn_lang_ru)
- def _switch_tab(self, index: int) -> None:
- self._tabs.setCurrentIndex(index)
- self._nav_tab_buttons[index].setChecked(True)
- def _build_status_bar(self) -> None:
- sb = QStatusBar()
- sb.setStyleSheet(
- "QStatusBar { background: #0c0c1a; color: #555577; font-size: 11px; }"
- )
- self.setStatusBar(sb)
- sb.showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(_TAB_NAV_KEYS[0])}")
- def _size_and_center(self) -> None:
- screen = QApplication.primaryScreen()
- if screen is not None:
- ag = screen.availableGeometry()
- w = min(1600, max(960, int(ag.width() * 0.92)))
- h = min(940, max(640, int(ag.height() * 0.90)))
- self.resize(w, h)
- self.move(
- ag.x() + (ag.width() - w) // 2,
- ag.y() + (ag.height() - h) // 2,
- )
- else:
- self.resize(1440, 860)
- def _on_tab_changed(self, index: int) -> None:
- key = _TAB_NAV_KEYS[index] if 0 <= index < len(_TAB_NAV_KEYS) else "-"
- self.statusBar().showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(key)}")
- if 0 <= index < len(self._nav_tab_buttons):
- self._nav_tab_buttons[index].setChecked(True)
- def _on_language_change(self, lang: str) -> None:
- i18n.set_language(lang)
- self.retranslate_ui()
- def retranslate_ui(self) -> None:
- for i, key in enumerate(_TAB_NAV_KEYS):
- self._nav_tab_buttons[i].setText(i18n.tr(key))
- cur = self._tabs.currentIndex()
- key = _TAB_NAV_KEYS[cur] if 0 <= cur < len(_TAB_NAV_KEYS) else "-"
- self.statusBar().showMessage(f"{i18n.tr('active_tab')}: {i18n.tr(key)}")
- for tab in (
- self._seq_tab,
- self._scanner_tab,
- self._fid_tab,
- self._scanning_tab,
- self._spectroscopy_tab,
- ):
- if hasattr(tab, "retranslate_ui"):
- tab.retranslate_ui()
- def _on_fid_generated(self, path: str) -> None:
- self._seq_tab.load_seq_file(path)
- self._switch_tab(0)
- def _on_ready_for_scan(self, info: dict) -> None:
- self._scanner_tab.apply_seq_info(info)
- self._scanning_tab.apply_seq_info(info)
- self._switch_tab(1)
- class _VSep(QFrame):
- """Thin vertical separator for the nav toolbar."""
- def __init__(self, parent=None) -> None:
- super().__init__(parent)
- self.setFrameShape(QFrame.VLine)
- self.setFixedWidth(1)
- self.setStyleSheet("background: #1e1e38; border: none;")
|