""" CalibrationDialog - главный диалог калибровки С выбором камеры, папки и типа кадров """ from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QComboBox, QLineEdit, QPushButton, QFrame, QMessageBox, QFileDialog, QWidget ) from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QFont from services.config_service import ConfigService class CalibrationDialog(QDialog): """Главное окно калибровки""" def __init__(self, parent, config_service: ConfigService): super().__init__(parent) self.config_service = config_service self.setWindowTitle("🌑 Калибровочные кадры") self.setMinimumSize(600, 450) self.resize(650, 500) self._create_ui() self._load_saved_settings() # Таймер для мигания кнопки "Обзор" self._browse_blink_timer = None self._check_folder_path() def _create_ui(self): layout = QVBoxLayout(self) layout.setSpacing(20) layout.setContentsMargins(25, 25, 25, 25) # Заголовок title_label = QLabel("🌑 Калибровочные кадры") title_font = QFont() title_font.setPointSize(18) title_font.setBold(True) title_label.setFont(title_font) layout.addWidget(title_label) # Основная сетка grid = QGridLayout() grid.setVerticalSpacing(15) grid.setHorizontalSpacing(15) # Строка 0: Камера camera_label = QLabel("📷 Камера:") camera_label.setFont(QFont("", 10, QFont.Bold)) grid.addWidget(camera_label, 0, 0) self.camera_combo = QComboBox() self.camera_combo.setEditable(True) self.camera_combo.setMinimumWidth(250) grid.addWidget(self.camera_combo, 0, 1) # Строка 1: Папка folder_label = QLabel("📁 Папка:") folder_label.setFont(QFont("", 10, QFont.Bold)) grid.addWidget(folder_label, 1, 0) folder_widget = QWidget() folder_layout = QHBoxLayout(folder_widget) folder_layout.setContentsMargins(0, 0, 0, 0) folder_layout.setSpacing(10) self.folder_entry = QLineEdit() self.folder_entry.setPlaceholderText("Выберите папку для сохранения калибровочных кадров") folder_layout.addWidget(self.folder_entry) self.browse_button = QPushButton("✨ Обзор") self.browse_button.setFixedWidth(100) self.browse_button.clicked.connect(self._browse_folder) folder_layout.addWidget(self.browse_button) grid.addWidget(folder_widget, 1, 1) layout.addLayout(grid) # Разделитель separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setStyleSheet("background-color: #333333; max-height: 1px;") layout.addWidget(separator) # Кнопки типов кадров types_layout = QHBoxLayout() types_layout.setSpacing(20) types_layout.setAlignment(Qt.AlignCenter) self.bias_btn = QPushButton("⚪ BIAS") self.bias_btn.setFixedSize(120, 50) self.bias_btn.setStyleSheet(""" QPushButton { background-color: #2196F3; color: white; font-weight: bold; font-size: 14px; border-radius: 6px; } QPushButton:hover { background-color: #1976D2; } """) self.bias_btn.clicked.connect(lambda: self._open_calibration_type('bias')) self.dark_btn = QPushButton("🌑 DARK") self.dark_btn.setFixedSize(120, 50) self.dark_btn.setStyleSheet(""" QPushButton { background-color: #9C27B0; color: white; font-weight: bold; font-size: 14px; border-radius: 6px; } QPushButton:hover { background-color: #7B1FA2; } """) self.dark_btn.clicked.connect(lambda: self._open_calibration_type('dark')) self.flat_btn = QPushButton("📖 FLAT") self.flat_btn.setFixedSize(120, 50) self.flat_btn.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; font-weight: bold; font-size: 14px; border-radius: 6px; } QPushButton:hover { background-color: #388E3C; } """) self.flat_btn.clicked.connect(lambda: self._open_calibration_type('flat')) types_layout.addWidget(self.bias_btn) types_layout.addWidget(self.dark_btn) types_layout.addWidget(self.flat_btn) layout.addLayout(types_layout) # Совет tips_frame = QFrame() tips_frame.setStyleSheet(""" QFrame { background-color: #2d2d2d; border-radius: 8px; padding: 10px; } """) tips_layout = QVBoxLayout(tips_frame) tips_title = QLabel("💡 Совет") tips_title.setFont(QFont("", 11, QFont.Bold)) tips_layout.addWidget(tips_title) self.tips_label = QLabel( "• BIAS снимаются один раз на месяц (можно дома)\n" "• DARK снимаются на месте съёмки при той же температуре\n" "• FLAT снимаются после сессии без изменения фокуса" ) self.tips_label.setWordWrap(True) tips_layout.addWidget(self.tips_label) layout.addWidget(tips_frame) # Кнопки отмена/закрыть buttons_layout = QHBoxLayout() buttons_layout.addStretch() cancel_btn = QPushButton("❌ Отмена") cancel_btn.clicked.connect(self.reject) buttons_layout.addWidget(cancel_btn) layout.addLayout(buttons_layout) def _load_saved_settings(self): """Загружает сохранённые камеры""" cameras = self.config_service.get_cameras() if cameras: self.camera_combo.addItems(cameras) last_camera = self.config_service.get_last_camera() if last_camera and last_camera in cameras: self.camera_combo.setCurrentText(last_camera) def _browse_folder(self): """Выбор папки для калибровочных кадров""" folder = QFileDialog.getExistingDirectory(self, "Выберите папку для калибровочных кадров") if folder: self.folder_entry.setText(folder) self._stop_browse_blinking() def _check_folder_path(self): """Проверяет, заполнено ли поле пути и запускает мигание если нет""" if not self.folder_entry.text(): self._start_browse_blinking() else: self._stop_browse_blinking() def _start_browse_blinking(self): """Запускает мигание кнопки 'Обзор' зелёным цветом""" self._browse_blink_timer = QTimer() self._browse_blink_timer.timeout.connect(self._do_browse_blink) self._browse_blink_timer.start(500) def _do_browse_blink(self): """Мигание кнопки""" current_style = self.browse_button.styleSheet() if "background-color: #4CAF50" in current_style: self.browse_button.setStyleSheet(""" QPushButton { background-color: #2196F3; color: white; font-weight: bold; border-radius: 4px; } """) else: self.browse_button.setStyleSheet(""" QPushButton { background-color: #4CAF50; color: white; font-weight: bold; border-radius: 4px; } """) def _stop_browse_blinking(self): """Останавливает мигание кнопки""" if self._browse_blink_timer: self._browse_blink_timer.stop() self._browse_blink_timer = None self.browse_button.setStyleSheet(""" QPushButton { background-color: #2196F3; color: white; font-weight: bold; border-radius: 4px; } """) def _open_calibration_type(self, cal_type: str): """Открывает дочернее окно для выбранного типа калибровки""" if not self.folder_entry.text(): QMessageBox.warning(self, "Внимание", "Сначала выберите папку для сохранения!") self._start_browse_blinking() return camera_name = self.camera_combo.currentText() if not camera_name: QMessageBox.warning(self, "Внимание", "Введите или выберите название камеры!") return from ui.dialogs.calibration_type_dialog import CalibrationTypeDialog dialog = CalibrationTypeDialog( self, cal_type, self.folder_entry.text(), camera_name, self.config_service ) if dialog.exec(): # После успешной съёмки QMessageBox.information(self, "Успех", f"Съёмка {cal_type.upper()} завершена!") def reject(self): """Закрытие диалога""" if hasattr(self, '_browse_blink_timer') and self._browse_blink_timer: self._browse_blink_timer.stop() super().reject()