reworked with tkinter, UI improements needed

This commit is contained in:
Vic Sergeev 2026-05-09 19:44:16 +03:00
parent d3878857af
commit 3ae07ca289
26 changed files with 1488 additions and 1751 deletions

View file

@ -1,281 +1,180 @@
"""
CalibrationDialog - главный диалог калибровки
С выбором камеры, папки и типа кадров
CalibrationDialog - главный диалог калибровки (tkinter)
"""
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
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from pathlib import Path
class CalibrationDialog(QDialog):
class CalibrationDialog(tk.Toplevel):
"""Главное окно калибровки"""
def __init__(self, parent, config_service: ConfigService):
def __init__(self, parent, config_service):
super().__init__(parent)
self.parent = parent
self.config_service = config_service
self._blink_active = False
self._blink_after_id = None
self.setWindowTitle("🌑 Калибровочные кадры")
self.setMinimumSize(600, 450)
self.resize(650, 500)
self.title("Calibration Frames")
self.geometry("650x500")
self.minsize(600, 450)
self.transient(parent)
self.grab_set()
self._create_ui()
self._load_saved_settings()
# Таймер для мигания кнопки "Обзор"
self._browse_blink_timer = None
self._check_folder_path()
self._center_window()
def _create_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(25, 25, 25, 25)
# Main frame
main_frame = ttk.Frame(self, padding="25")
main_frame.pack(fill='both', expand=True)
# Заголовок
title_label = QLabel("🌑 Калибровочные кадры")
title_font = QFont()
title_font.setPointSize(18)
title_font.setBold(True)
title_label.setFont(title_font)
layout.addWidget(title_label)
# Title
title_label = ttk.Label(main_frame, text="Calibration Frames", font=('Segoe UI', 18, 'bold'))
title_label.pack(pady=(0, 15))
# Основная сетка
grid = QGridLayout()
grid.setVerticalSpacing(15)
grid.setHorizontalSpacing(15)
# Separator
ttk.Separator(main_frame, orient='horizontal').pack(fill='x', pady=(0, 15))
# Строка 0: Камера
camera_label = QLabel("📷 Камера:")
camera_label.setFont(QFont("", 10, QFont.Bold))
grid.addWidget(camera_label, 0, 0)
# Camera selection
camera_frame = ttk.Frame(main_frame)
camera_frame.pack(fill='x', pady=5)
self.camera_combo = QComboBox()
self.camera_combo.setEditable(True)
self.camera_combo.setMinimumWidth(250)
grid.addWidget(self.camera_combo, 0, 1)
ttk.Label(camera_frame, text="Camera:", font=('Segoe UI', 10, 'bold')).pack(side='left', padx=(0, 10))
# Строка 1: Папка
folder_label = QLabel("📁 Папка:")
folder_label.setFont(QFont("", 10, QFont.Bold))
grid.addWidget(folder_label, 1, 0)
self.camera_combo = ttk.Combobox(camera_frame, width=30)
self.camera_combo.pack(side='left', fill='x', expand=True)
folder_widget = QWidget()
folder_layout = QHBoxLayout(folder_widget)
folder_layout.setContentsMargins(0, 0, 0, 0)
folder_layout.setSpacing(10)
# Folder selection
folder_frame = ttk.Frame(main_frame)
folder_frame.pack(fill='x', pady=5)
self.folder_entry = QLineEdit()
self.folder_entry.setPlaceholderText("Выберите папку для сохранения калибровочных кадров")
folder_layout.addWidget(self.folder_entry)
ttk.Label(folder_frame, text="Folder:", font=('Segoe UI', 10, 'bold')).pack(side='left', padx=(0, 10))
self.browse_button = QPushButton("✨ Обзор")
self.browse_button.setFixedWidth(100)
self.browse_button.clicked.connect(self._browse_folder)
folder_layout.addWidget(self.browse_button)
folder_input_frame = ttk.Frame(folder_frame)
folder_input_frame.pack(side='left', fill='x', expand=True)
grid.addWidget(folder_widget, 1, 1)
self.folder_entry = ttk.Entry(folder_input_frame)
self.folder_entry.pack(side='left', fill='x', expand=True, padx=(0, 10))
layout.addLayout(grid)
self.browse_btn = tk.Button(folder_input_frame, text="Browse...", width=10,
bg='#3c3c3c', fg='#e0e0e0', activebackground='#4c4c4c',
relief='raised', borderwidth=1)
self.browse_btn.config(command=self._browse_folder)
self.browse_btn.pack(side='right')
# Разделитель
separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setStyleSheet("background-color: #333333; max-height: 1px;")
layout.addWidget(separator)
# Separator
ttk.Separator(main_frame, orient='horizontal').pack(fill='x', pady=15)
# Кнопки типов кадров
types_layout = QHBoxLayout()
types_layout.setSpacing(20)
types_layout.setAlignment(Qt.AlignCenter)
# Type buttons
types_frame = ttk.Frame(main_frame)
types_frame.pack(pady=10)
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.bias_btn = tk.Button(types_frame, text="BIAS", font=('Segoe UI', 12, 'bold'),
bg='#2196F3', fg='white', activebackground='#1976D2',
width=12, height=2,
command=lambda: self._open_calibration_type('bias'))
self.bias_btn.pack(side='left', padx=10)
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.dark_btn = tk.Button(types_frame, text="DARK", font=('Segoe UI', 12, 'bold'),
bg='#9C27B0', fg='white', activebackground='#7B1FA2',
width=12, height=2,
command=lambda: self._open_calibration_type('dark'))
self.dark_btn.pack(side='left', padx=10)
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'))
self.flat_btn = tk.Button(types_frame, text="FLAT", font=('Segoe UI', 12, 'bold'),
bg='#4CAF50', fg='white', activebackground='#388E3C',
width=12, height=2,
command=lambda: self._open_calibration_type('flat'))
self.flat_btn.pack(side='left', padx=10)
types_layout.addWidget(self.bias_btn)
types_layout.addWidget(self.dark_btn)
types_layout.addWidget(self.flat_btn)
# Tips frame
tips_frame = tk.Frame(main_frame, bg='#2d2d2d', relief='groove', bd=1)
tips_frame.pack(fill='x', pady=15, padx=10)
layout.addLayout(types_layout)
tk.Label(tips_frame, text="Tips:", font=('Segoe UI', 10, 'bold'),
bg='#2d2d2d', fg='#FFD700').pack(anchor='w', padx=10, pady=(10, 5))
# Совет
tips_frame = QFrame()
tips_frame.setStyleSheet("""
QFrame {
background-color: #2d2d2d;
border-radius: 8px;
padding: 10px;
}
""")
tips_layout = QVBoxLayout(tips_frame)
self.tips_label = tk.Label(tips_frame,
text="• BIAS can be taken once a month (at home)\n• DARK must be taken on site at the same temperature\n• FLAT must be taken after session without changing focus",
bg='#2d2d2d', fg='#e0e0e0', justify='left', font=('Segoe UI', 9))
self.tips_label.pack(anchor='w', padx=10, pady=(0, 10))
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)
# Cancel button
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(pady=10)
ttk.Button(btn_frame, text="Cancel", command=self.destroy).pack()
def _load_saved_settings(self):
"""Загружает сохранённые камеры"""
cameras = self.config_service.get_cameras()
if cameras:
self.camera_combo.addItems(cameras)
self.camera_combo['values'] = cameras
last_camera = self.config_service.get_last_camera()
if last_camera and last_camera in cameras:
self.camera_combo.setCurrentText(last_camera)
self.camera_combo.set(last_camera)
def _browse_folder(self):
"""Выбор папки для калибровочных кадров"""
folder = QFileDialog.getExistingDirectory(self, "Выберите папку для калибровочных кадров")
folder = filedialog.askdirectory(title="Select folder for calibration frames")
if folder:
self.folder_entry.setText(folder)
self._stop_browse_blinking()
self.folder_entry.delete(0, tk.END)
self.folder_entry.insert(0, folder)
self._stop_blinking()
self.browse_btn.config(bg='#3c3c3c', fg='#e0e0e0')
def _check_folder_path(self):
"""Проверяет, заполнено ли поле пути и запускает мигание если нет"""
if not self.folder_entry.text():
self._start_browse_blinking()
if not self.folder_entry.get():
self._start_blinking()
else:
self._stop_browse_blinking()
self._stop_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 _start_blinking(self):
self._blink_active = True
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 blink():
if not self._blink_active:
return
if self.browse_btn.cget('bg') == '#3c3c3c':
self.browse_btn.config(bg='#f44336', fg='white')
else:
self.browse_btn.config(bg='#3c3c3c', fg='#e0e0e0')
self._blink_after_id = self.after(1500, blink)
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;
}
""")
blink()
def _open_calibration_type(self, cal_type: str):
"""Открывает дочернее окно для выбранного типа калибровки"""
if not self.folder_entry.text():
QMessageBox.warning(self, "Внимание", "Сначала выберите папку для сохранения!")
self._start_browse_blinking()
def _stop_blinking(self):
self._blink_active = False
if self._blink_after_id:
self.after_cancel(self._blink_after_id)
self._blink_after_id = None
self.browse_btn.config(bg='#3c3c3c', fg='#e0e0e0')
def _center_window(self):
self.update_idletasks()
x = self.parent.winfo_x() + (self.parent.winfo_width() // 2) - (self.winfo_width() // 2)
y = self.parent.winfo_y() + (self.parent.winfo_height() // 2) - (self.winfo_height() // 2)
self.geometry(f'+{x}+{y}')
def _open_calibration_type(self, cal_type):
if not self.folder_entry.get():
messagebox.showwarning("Warning", "Please select a folder to save calibration frames!", parent=self)
self._start_blinking()
return
camera_name = self.camera_combo.currentText()
camera_name = self.camera_combo.get()
if not camera_name:
QMessageBox.warning(self, "Внимание", "Введите или выберите название камеры!")
messagebox.showwarning("Warning", "Please enter or select a camera name!", parent=self)
return
from ui.dialogs.calibration_type_dialog import CalibrationTypeDialog
dialog = CalibrationTypeDialog(
self,
cal_type,
self.folder_entry.text(),
self.folder_entry.get(),
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()
self.wait_window(dialog)