reworked with tkinter, UI improements needed
This commit is contained in:
parent
d3878857af
commit
3ae07ca289
26 changed files with 1488 additions and 1751 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,160 +1,153 @@
|
|||
"""
|
||||
CelestialDialog - диалог управления небесными телами
|
||||
Аналог CelestialBodiesDialogController из JavaFX версии
|
||||
CelestialDialog - диалог управления небесными телами (tkinter)
|
||||
"""
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QListWidget,
|
||||
QPushButton, QLineEdit, QInputDialog, QMessageBox
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
from services.config_service import ConfigService
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
|
||||
|
||||
class CelestialDialog(QDialog):
|
||||
class CelestialDialog(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.setWindowTitle("Небесные тела")
|
||||
self.setMinimumSize(400, 500)
|
||||
self.resize(450, 550)
|
||||
|
||||
# Загружаем текущий список
|
||||
self.celestial_bodies = self.config_service.get_celestial_bodies()
|
||||
self.selected_body = None
|
||||
|
||||
self.title("Celestial Bodies")
|
||||
self.geometry("500x550")
|
||||
self.minsize(450, 500)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
self._create_ui()
|
||||
self._update_list()
|
||||
self._center_window()
|
||||
|
||||
def _create_ui(self):
|
||||
"""Создаёт интерфейс диалога"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
# Main frame
|
||||
main_frame = ttk.Frame(self, padding="20")
|
||||
main_frame.pack(fill='both', expand=True)
|
||||
|
||||
# Заголовок
|
||||
title_label = QLabel("Управление небесными телами")
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(16)
|
||||
title_font.setBold(True)
|
||||
title_label.setFont(title_font)
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
# Title
|
||||
ttk.Label(main_frame, text="Celestial Bodies", font=('Segoe UI', 14, 'bold')).pack(pady=(0, 10))
|
||||
ttk.Label(main_frame, text="List of observation targets", font=('Segoe UI', 10)).pack(pady=(0, 15))
|
||||
|
||||
# Подпись
|
||||
subtitle_label = QLabel("Список объектов для наблюдения")
|
||||
subtitle_font = QFont()
|
||||
subtitle_font.setPointSize(11)
|
||||
subtitle_font.setBold(True)
|
||||
subtitle_label.setFont(subtitle_font)
|
||||
layout.addWidget(subtitle_label)
|
||||
# Listbox with scrollbar
|
||||
list_frame = ttk.Frame(main_frame)
|
||||
list_frame.pack(fill='both', expand=True, pady=(0, 10))
|
||||
|
||||
# Список небесных тел
|
||||
self.bodies_list = QListWidget()
|
||||
self.bodies_list.itemClicked.connect(lambda item: self._select_body(item.text()))
|
||||
layout.addWidget(self.bodies_list)
|
||||
scrollbar = ttk.Scrollbar(list_frame)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
# Поле для добавления нового
|
||||
add_layout = QHBoxLayout()
|
||||
self.bodies_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set, height=15,
|
||||
bg='#2d2d2d', fg='#e0e0e0',
|
||||
selectbackground='#4CAF50', selectforeground='white',
|
||||
font=('Segoe UI', 10))
|
||||
self.bodies_listbox.pack(fill='both', expand=True)
|
||||
scrollbar.config(command=self.bodies_listbox.yview)
|
||||
|
||||
self.new_body_entry = QLineEdit()
|
||||
self.new_body_entry.setPlaceholderText("Название объекта (например: M31, NGC 224)")
|
||||
self.new_body_entry.returnPressed.connect(self._add_celestial_body)
|
||||
add_layout.addWidget(self.new_body_entry)
|
||||
|
||||
add_btn = QPushButton("➕ Добавить")
|
||||
add_btn.clicked.connect(self._add_celestial_body)
|
||||
add_layout.addWidget(add_btn)
|
||||
|
||||
layout.addLayout(add_layout)
|
||||
|
||||
# Кнопки удаления и редактирования
|
||||
buttons_layout = QHBoxLayout()
|
||||
|
||||
self.remove_btn = QPushButton("❌ Удалить выбранный")
|
||||
self.remove_btn.setEnabled(False)
|
||||
self.remove_btn.clicked.connect(self._remove_celestial_body)
|
||||
buttons_layout.addWidget(self.remove_btn)
|
||||
|
||||
self.edit_btn = QPushButton("✏ Редактировать")
|
||||
self.edit_btn.setEnabled(False)
|
||||
self.edit_btn.clicked.connect(self._edit_celestial_body)
|
||||
buttons_layout.addWidget(self.edit_btn)
|
||||
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
# Кнопка закрытия
|
||||
close_btn = QPushButton("Закрыть")
|
||||
close_btn.clicked.connect(self.accept)
|
||||
close_layout = QHBoxLayout()
|
||||
close_layout.addStretch()
|
||||
close_layout.addWidget(close_btn)
|
||||
layout.addLayout(close_layout)
|
||||
|
||||
def _update_list(self):
|
||||
"""Обновляет отображение списка небесных тел"""
|
||||
self.bodies_list.clear()
|
||||
for body in self.celestial_bodies:
|
||||
self.bodies_list.addItem(body)
|
||||
self._selected_body = None
|
||||
self.remove_btn.setEnabled(False)
|
||||
self.edit_btn.setEnabled(False)
|
||||
self.bodies_listbox.insert('end', body)
|
||||
|
||||
def _select_body(self, body: str):
|
||||
"""Выделяет объект в списке"""
|
||||
self._selected_body = body
|
||||
self.remove_btn.setEnabled(True)
|
||||
self.edit_btn.setEnabled(True)
|
||||
self.bodies_listbox.bind('<<ListboxSelect>>', self._on_body_select)
|
||||
|
||||
# Add new body
|
||||
add_frame = ttk.Frame(main_frame)
|
||||
add_frame.pack(fill='x', pady=(0, 10))
|
||||
|
||||
self.new_body_entry = ttk.Entry(add_frame)
|
||||
self.new_body_entry.pack(side='left', fill='x', expand=True, padx=(0, 10))
|
||||
ttk.Button(add_frame, text="Add", command=self._add_celestial_body).pack(side='right')
|
||||
|
||||
# Buttons
|
||||
btn_frame = ttk.Frame(main_frame)
|
||||
btn_frame.pack(pady=(0, 10))
|
||||
|
||||
self.remove_btn = ttk.Button(btn_frame, text="Remove Selected", command=self._remove_celestial_body, state='disabled')
|
||||
self.remove_btn.pack(side='left', padx=5)
|
||||
|
||||
self.edit_btn = ttk.Button(btn_frame, text="Edit Selected", command=self._edit_celestial_body, state='disabled')
|
||||
self.edit_btn.pack(side='left', padx=5)
|
||||
|
||||
# Close button
|
||||
ttk.Button(main_frame, text="Close", command=self.destroy).pack(pady=10)
|
||||
|
||||
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 _on_body_select(self, event):
|
||||
selection = self.bodies_listbox.curselection()
|
||||
if selection:
|
||||
self.selected_body = self.bodies_listbox.get(selection[0])
|
||||
self.remove_btn.config(state='normal')
|
||||
self.edit_btn.config(state='normal')
|
||||
|
||||
def _add_celestial_body(self):
|
||||
"""Добавляет новое небесное тело"""
|
||||
new_body = self.new_body_entry.text()
|
||||
if not new_body or not new_body.strip():
|
||||
QMessageBox.warning(self, "Ошибка", "Введите название объекта")
|
||||
new_body = self.new_body_entry.get().strip()
|
||||
if not new_body:
|
||||
messagebox.showwarning("Warning", "Please enter object name!", parent=self)
|
||||
return
|
||||
|
||||
new_name = new_body.strip()
|
||||
if new_name in self.celestial_bodies:
|
||||
QMessageBox.warning(self, "Ошибка", f"Объект '{new_name}' уже существует!")
|
||||
if new_body in self.celestial_bodies:
|
||||
messagebox.showwarning("Warning", f"Object '{new_body}' already exists!", parent=self)
|
||||
return
|
||||
|
||||
self.celestial_bodies.append(new_name)
|
||||
self.config_service.add_celestial_body(new_name)
|
||||
self._update_list()
|
||||
self.new_body_entry.clear()
|
||||
QMessageBox.information(self, "Успех", f"Объект '{new_name}' добавлен")
|
||||
self.celestial_bodies.append(new_body)
|
||||
self.config_service.add_celestial_body(new_body)
|
||||
self.bodies_listbox.insert('end', new_body)
|
||||
self.new_body_entry.delete(0, 'end')
|
||||
messagebox.showinfo("Success", f"Object '{new_body}' added", parent=self)
|
||||
|
||||
def _remove_celestial_body(self):
|
||||
"""Удаляет выбранное небесное тело"""
|
||||
if hasattr(self, '_selected_body') and self._selected_body:
|
||||
reply = QMessageBox.question(self, "Подтверждение",
|
||||
f"Удалить объект '{self._selected_body}'?",
|
||||
QMessageBox.Yes | QMessageBox.No)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.celestial_bodies.remove(self._selected_body)
|
||||
self.config_service.remove_celestial_body(self._selected_body)
|
||||
self._update_list()
|
||||
QMessageBox.information(self, "Успех", f"Объект '{self._selected_body}' удалён")
|
||||
if self.selected_body:
|
||||
reply = messagebox.askyesno("Remove Object", f"Remove '{self.selected_body}'?", parent=self)
|
||||
if reply:
|
||||
self.celestial_bodies.remove(self.selected_body)
|
||||
self.config_service.remove_celestial_body(self.selected_body)
|
||||
self.bodies_listbox.delete(0, 'end')
|
||||
for body in self.celestial_bodies:
|
||||
self.bodies_listbox.insert('end', body)
|
||||
self.selected_body = None
|
||||
self.remove_btn.config(state='disabled')
|
||||
self.edit_btn.config(state='disabled')
|
||||
|
||||
def _edit_celestial_body(self):
|
||||
"""Редактирует выбранное небесное тело"""
|
||||
if hasattr(self, '_selected_body') and self._selected_body:
|
||||
new_name, ok = QInputDialog.getText(self, "Редактировать",
|
||||
f"Изменить '{self._selected_body}' на:",
|
||||
text=self._selected_body)
|
||||
if ok and new_name and new_name.strip():
|
||||
new_name = new_name.strip()
|
||||
if new_name != self._selected_body:
|
||||
if self.selected_body:
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Edit Celestial Body")
|
||||
dialog.geometry("350x120")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
ttk.Label(dialog, text="New name:", font=('Segoe UI', 10)).pack(pady=15)
|
||||
entry = ttk.Entry(dialog, width=40)
|
||||
entry.insert(0, self.selected_body)
|
||||
entry.pack(pady=5)
|
||||
entry.focus()
|
||||
|
||||
def save():
|
||||
new_name = entry.get().strip()
|
||||
if new_name and new_name != self.selected_body:
|
||||
if new_name in self.celestial_bodies:
|
||||
QMessageBox.warning(self, "Ошибка", f"Объект '{new_name}' уже существует!")
|
||||
messagebox.showerror("Error", f"Object '{new_name}' already exists!", parent=dialog)
|
||||
return
|
||||
|
||||
idx = self.celestial_bodies.index(self._selected_body)
|
||||
old_name = self.celestial_bodies[idx]
|
||||
idx = self.celestial_bodies.index(self.selected_body)
|
||||
self.celestial_bodies[idx] = new_name
|
||||
self.config_service.update_celestial_body(old_name, new_name)
|
||||
self._update_list()
|
||||
QMessageBox.information(self, "Успех", f"Объект переименован в '{new_name}'")
|
||||
self.config_service.update_celestial_body(self.selected_body, new_name)
|
||||
self.bodies_listbox.delete(0, 'end')
|
||||
for body in self.celestial_bodies:
|
||||
self.bodies_listbox.insert('end', body)
|
||||
dialog.destroy()
|
||||
elif new_name == self.selected_body:
|
||||
dialog.destroy()
|
||||
else:
|
||||
messagebox.showwarning("Warning", "Please enter a name!", parent=dialog)
|
||||
|
||||
ttk.Button(dialog, text="Save", command=save).pack(pady=10)
|
||||
dialog.bind('<Return>', lambda e: save())
|
||||
|
|
@ -1,361 +1,469 @@
|
|||
"""
|
||||
EquipmentDialog - диалог управления оборудованием
|
||||
Камеры, объективы и телескопы
|
||||
EquipmentDialog - диалог управления оборудованием (tkinter)
|
||||
"""
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QListWidget,
|
||||
QPushButton, QInputDialog, QMessageBox, QWidget, QTabWidget,
|
||||
QFormLayout, QDoubleSpinBox, QSpinBox, QLineEdit
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
from services.config_service import ConfigService
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class EquipmentDialog(QDialog):
|
||||
class EquipmentDialog(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.setWindowTitle("Управление оборудованием")
|
||||
self.setMinimumSize(700, 500)
|
||||
self.resize(800, 550)
|
||||
|
||||
# Загружаем данные
|
||||
self.cameras = self.config_service.get_cameras()
|
||||
self.lenses = self.config_service.get_lenses()
|
||||
self.telescopes = self.config_service.get_telescopes()
|
||||
|
||||
self.selected_camera = None
|
||||
self.selected_lens = None
|
||||
self.selected_telescope = None
|
||||
|
||||
self.title("Manage Equipment")
|
||||
self.geometry("750x500")
|
||||
self.minsize(700, 450)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
self._create_ui()
|
||||
self._update_cameras_list()
|
||||
self._update_lenses_list()
|
||||
self._update_telescopes_list()
|
||||
self._center_window()
|
||||
|
||||
def _create_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(15)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
# Notebook for tabs
|
||||
notebook = ttk.Notebook(self)
|
||||
notebook.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
# Заголовок
|
||||
title_label = QLabel("Управление оборудованием")
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(16)
|
||||
title_font.setBold(True)
|
||||
title_label.setFont(title_font)
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
# Tab 1: Cameras
|
||||
cameras_frame = ttk.Frame(notebook)
|
||||
notebook.add(cameras_frame, text="Cameras")
|
||||
self._create_cameras_tab(cameras_frame)
|
||||
|
||||
# Используем QTabWidget для трёх вкладок
|
||||
tab_widget = QTabWidget()
|
||||
# Tab 2: Lenses
|
||||
lenses_frame = ttk.Frame(notebook)
|
||||
notebook.add(lenses_frame, text="Lenses")
|
||||
self._create_lenses_tab(lenses_frame)
|
||||
|
||||
# Вкладка: Камеры
|
||||
cameras_tab = self._create_cameras_tab()
|
||||
tab_widget.addTab(cameras_tab, "📷 Камеры")
|
||||
# Tab 3: Telescopes
|
||||
telescopes_frame = ttk.Frame(notebook)
|
||||
notebook.add(telescopes_frame, text="Telescopes")
|
||||
self._create_telescopes_tab(telescopes_frame)
|
||||
|
||||
# Вкладка: Объективы
|
||||
lenses_tab = self._create_lenses_tab()
|
||||
tab_widget.addTab(lenses_tab, "🔭 Объективы")
|
||||
# Close button
|
||||
btn_frame = ttk.Frame(self)
|
||||
btn_frame.pack(pady=10)
|
||||
ttk.Button(btn_frame, text="Close", command=self.destroy).pack()
|
||||
|
||||
# Вкладка: Телескопы
|
||||
telescopes_tab = self._create_telescopes_tab()
|
||||
tab_widget.addTab(telescopes_tab, "🪐 Телескопы")
|
||||
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}')
|
||||
|
||||
layout.addWidget(tab_widget)
|
||||
def _create_cameras_tab(self, parent):
|
||||
# Listbox
|
||||
list_frame = ttk.Frame(parent)
|
||||
list_frame.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
# Кнопка закрытия
|
||||
close_btn = QPushButton("Закрыть")
|
||||
close_btn.clicked.connect(self.accept)
|
||||
close_layout = QHBoxLayout()
|
||||
close_layout.addStretch()
|
||||
close_layout.addWidget(close_btn)
|
||||
layout.addLayout(close_layout)
|
||||
scrollbar = ttk.Scrollbar(list_frame)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
def _create_cameras_tab(self) -> QWidget:
|
||||
"""Создаёт вкладку с камерами"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
self.cameras_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set,
|
||||
bg='#2d2d2d', fg='#e0e0e0',
|
||||
selectbackground='#4CAF50', selectforeground='white',
|
||||
font=('Segoe UI', 10))
|
||||
self.cameras_listbox.pack(fill='both', expand=True)
|
||||
scrollbar.config(command=self.cameras_listbox.yview)
|
||||
|
||||
# Список камер
|
||||
self.cameras_list = QListWidget()
|
||||
self.cameras_list.itemClicked.connect(lambda item: self._select_camera(item.text()))
|
||||
layout.addWidget(self.cameras_list)
|
||||
|
||||
# Кнопки
|
||||
buttons_layout = QHBoxLayout()
|
||||
|
||||
add_btn = QPushButton("➕ Добавить камеру")
|
||||
add_btn.clicked.connect(self._add_camera)
|
||||
buttons_layout.addWidget(add_btn)
|
||||
|
||||
self.remove_camera_btn = QPushButton("❌ Удалить")
|
||||
self.remove_camera_btn.setEnabled(False)
|
||||
self.remove_camera_btn.clicked.connect(self._remove_camera)
|
||||
buttons_layout.addWidget(self.remove_camera_btn)
|
||||
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
return tab
|
||||
|
||||
def _create_lenses_tab(self) -> QWidget:
|
||||
"""Создаёт вкладку с объективами"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Список объективов
|
||||
self.lenses_list = QListWidget()
|
||||
self.lenses_list.itemClicked.connect(lambda item: self._select_lens(item.text()))
|
||||
layout.addWidget(self.lenses_list)
|
||||
|
||||
# Кнопки
|
||||
buttons_layout = QHBoxLayout()
|
||||
|
||||
add_btn = QPushButton("➕ Добавить объектив")
|
||||
add_btn.clicked.connect(self._add_lens)
|
||||
buttons_layout.addWidget(add_btn)
|
||||
|
||||
self.remove_lens_btn = QPushButton("❌ Удалить")
|
||||
self.remove_lens_btn.setEnabled(False)
|
||||
self.remove_lens_btn.clicked.connect(self._remove_lens)
|
||||
buttons_layout.addWidget(self.remove_lens_btn)
|
||||
|
||||
edit_btn = QPushButton("✏ Редактировать")
|
||||
edit_btn.clicked.connect(self._edit_lens)
|
||||
buttons_layout.addWidget(edit_btn)
|
||||
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
return tab
|
||||
|
||||
def _create_telescopes_tab(self) -> QWidget:
|
||||
"""Создаёт вкладку с телескопами"""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
# Список телескопов
|
||||
self.telescopes_list = QListWidget()
|
||||
self.telescopes_list.itemClicked.connect(lambda item: self._select_telescope(item.text()))
|
||||
layout.addWidget(self.telescopes_list)
|
||||
|
||||
# Кнопки
|
||||
buttons_layout = QHBoxLayout()
|
||||
|
||||
add_btn = QPushButton("➕ Добавить телескоп")
|
||||
add_btn.clicked.connect(self._add_telescope)
|
||||
buttons_layout.addWidget(add_btn)
|
||||
|
||||
self.remove_telescope_btn = QPushButton("❌ Удалить")
|
||||
self.remove_telescope_btn.setEnabled(False)
|
||||
self.remove_telescope_btn.clicked.connect(self._remove_telescope)
|
||||
buttons_layout.addWidget(self.remove_telescope_btn)
|
||||
|
||||
edit_btn = QPushButton("✏ Редактировать")
|
||||
edit_btn.clicked.connect(self._edit_telescope)
|
||||
buttons_layout.addWidget(edit_btn)
|
||||
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
return tab
|
||||
|
||||
# ===== Методы для камер =====
|
||||
|
||||
def _update_cameras_list(self):
|
||||
self.cameras_list.clear()
|
||||
for camera in self.cameras:
|
||||
self.cameras_list.addItem(camera)
|
||||
self._selected_camera = None
|
||||
self.remove_camera_btn.setEnabled(False)
|
||||
self.cameras_listbox.insert('end', camera)
|
||||
|
||||
def _select_camera(self, camera: str):
|
||||
self._selected_camera = camera
|
||||
self.remove_camera_btn.setEnabled(True)
|
||||
self.lenses_list.clearSelection()
|
||||
self.telescopes_list.clearSelection()
|
||||
self._selected_lens = None
|
||||
self._selected_telescope = None
|
||||
self.remove_lens_btn.setEnabled(False)
|
||||
self.remove_telescope_btn.setEnabled(False)
|
||||
self.cameras_listbox.bind('<<ListboxSelect>>', self._on_camera_select)
|
||||
|
||||
# Buttons
|
||||
btn_frame = ttk.Frame(parent)
|
||||
btn_frame.pack(pady=10)
|
||||
|
||||
ttk.Button(btn_frame, text="Add Camera", command=self._add_camera).pack(side='left', padx=5)
|
||||
self.remove_camera_btn = ttk.Button(btn_frame, text="Remove", command=self._remove_camera, state='disabled')
|
||||
self.remove_camera_btn.pack(side='left', padx=5)
|
||||
self.edit_camera_btn = ttk.Button(btn_frame, text="Edit", command=self._edit_camera, state='disabled')
|
||||
self.edit_camera_btn.pack(side='left', padx=5)
|
||||
|
||||
def _on_camera_select(self, event):
|
||||
selection = self.cameras_listbox.curselection()
|
||||
if selection:
|
||||
self.selected_camera = self.cameras_listbox.get(selection[0])
|
||||
self.remove_camera_btn.config(state='normal')
|
||||
self.edit_camera_btn.config(state='normal')
|
||||
|
||||
def _add_camera(self):
|
||||
name, ok = QInputDialog.getText(self, "Добавить камеру", "Введите название камеры:")
|
||||
if ok and name and name.strip():
|
||||
new_name = name.strip()
|
||||
if new_name in self.cameras:
|
||||
QMessageBox.warning(self, "Ошибка", "Такая камера уже существует!")
|
||||
return
|
||||
self.cameras.append(new_name)
|
||||
self.config_service.add_camera(new_name)
|
||||
self._update_cameras_list()
|
||||
QMessageBox.information(self, "Успех", f"Камера '{new_name}' добавлена")
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Add Camera")
|
||||
dialog.geometry("350x120")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
ttk.Label(dialog, text="Camera name:", font=('Segoe UI', 10)).pack(pady=15)
|
||||
entry = ttk.Entry(dialog, width=40)
|
||||
entry.pack(pady=5)
|
||||
entry.focus()
|
||||
|
||||
def save():
|
||||
new_camera = entry.get().strip()
|
||||
if new_camera:
|
||||
if new_camera in self.cameras:
|
||||
messagebox.showerror("Error", "Camera already exists!", parent=dialog)
|
||||
return
|
||||
self.cameras.append(new_camera)
|
||||
self.config_service.add_camera(new_camera)
|
||||
self.cameras_listbox.insert('end', new_camera)
|
||||
dialog.destroy()
|
||||
else:
|
||||
messagebox.showwarning("Warning", "Please enter camera name!", parent=dialog)
|
||||
|
||||
ttk.Button(dialog, text="OK", command=save).pack(pady=10)
|
||||
dialog.bind('<Return>', lambda e: save())
|
||||
|
||||
def _remove_camera(self):
|
||||
if hasattr(self, '_selected_camera') and self._selected_camera:
|
||||
reply = QMessageBox.question(self, "Подтверждение",
|
||||
f"Удалить камеру '{self._selected_camera}'?",
|
||||
QMessageBox.Yes | QMessageBox.No)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.cameras.remove(self._selected_camera)
|
||||
self.config_service.remove_camera(self._selected_camera)
|
||||
self._update_cameras_list()
|
||||
if self.selected_camera:
|
||||
reply = messagebox.askyesno("Remove Camera", f"Remove '{self.selected_camera}'?", parent=self)
|
||||
if reply:
|
||||
self.cameras.remove(self.selected_camera)
|
||||
self.config_service.remove_camera(self.selected_camera)
|
||||
self.cameras_listbox.delete(0, 'end')
|
||||
for camera in self.cameras:
|
||||
self.cameras_listbox.insert('end', camera)
|
||||
self.selected_camera = None
|
||||
self.remove_camera_btn.config(state='disabled')
|
||||
self.edit_camera_btn.config(state='disabled')
|
||||
|
||||
# ===== Методы для объективов =====
|
||||
def _edit_camera(self):
|
||||
if self.selected_camera:
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Edit Camera")
|
||||
dialog.geometry("350x120")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
ttk.Label(dialog, text="New camera name:", font=('Segoe UI', 10)).pack(pady=15)
|
||||
entry = ttk.Entry(dialog, width=40)
|
||||
entry.insert(0, self.selected_camera)
|
||||
entry.pack(pady=5)
|
||||
entry.focus()
|
||||
|
||||
def save():
|
||||
new_name = entry.get().strip()
|
||||
if new_name and new_name != self.selected_camera:
|
||||
if new_name in self.cameras:
|
||||
messagebox.showerror("Error", "Camera already exists!", parent=dialog)
|
||||
return
|
||||
idx = self.cameras.index(self.selected_camera)
|
||||
self.cameras[idx] = new_name
|
||||
self.config_service.remove_camera(self.selected_camera)
|
||||
self.config_service.add_camera(new_name)
|
||||
self.cameras_listbox.delete(0, 'end')
|
||||
for camera in self.cameras:
|
||||
self.cameras_listbox.insert('end', camera)
|
||||
self.selected_camera = new_name
|
||||
dialog.destroy()
|
||||
elif new_name == self.selected_camera:
|
||||
dialog.destroy()
|
||||
else:
|
||||
messagebox.showwarning("Warning", "Please enter a name!", parent=dialog)
|
||||
|
||||
ttk.Button(dialog, text="Save", command=save).pack(pady=10)
|
||||
dialog.bind('<Return>', lambda e: save())
|
||||
|
||||
def _create_lenses_tab(self, parent):
|
||||
# Listbox
|
||||
list_frame = ttk.Frame(parent)
|
||||
list_frame.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
scrollbar = ttk.Scrollbar(list_frame)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
self.lenses_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set,
|
||||
bg='#2d2d2d', fg='#e0e0e0',
|
||||
selectbackground='#4CAF50', selectforeground='white',
|
||||
font=('Segoe UI', 10))
|
||||
self.lenses_listbox.pack(fill='both', expand=True)
|
||||
scrollbar.config(command=self.lenses_listbox.yview)
|
||||
|
||||
def _update_lenses_list(self):
|
||||
self.lenses_list.clear()
|
||||
for lens in self.lenses:
|
||||
self.lenses_list.addItem(lens)
|
||||
self._selected_lens = None
|
||||
self.remove_lens_btn.setEnabled(False)
|
||||
self.lenses_listbox.insert('end', lens)
|
||||
|
||||
def _select_lens(self, lens: str):
|
||||
self._selected_lens = lens
|
||||
self.remove_lens_btn.setEnabled(True)
|
||||
self.cameras_list.clearSelection()
|
||||
self.telescopes_list.clearSelection()
|
||||
self._selected_camera = None
|
||||
self._selected_telescope = None
|
||||
self.remove_camera_btn.setEnabled(False)
|
||||
self.remove_telescope_btn.setEnabled(False)
|
||||
self.lenses_listbox.bind('<<ListboxSelect>>', self._on_lens_select)
|
||||
|
||||
# Buttons
|
||||
btn_frame = ttk.Frame(parent)
|
||||
btn_frame.pack(pady=10)
|
||||
|
||||
ttk.Button(btn_frame, text="Add Lens", command=self._add_lens).pack(side='left', padx=5)
|
||||
self.remove_lens_btn = ttk.Button(btn_frame, text="Remove", command=self._remove_lens, state='disabled')
|
||||
self.remove_lens_btn.pack(side='left', padx=5)
|
||||
self.edit_lens_btn = ttk.Button(btn_frame, text="Edit", command=self._edit_lens, state='disabled')
|
||||
self.edit_lens_btn.pack(side='left', padx=5)
|
||||
|
||||
def _on_lens_select(self, event):
|
||||
selection = self.lenses_listbox.curselection()
|
||||
if selection:
|
||||
self.selected_lens = self.lenses_listbox.get(selection[0])
|
||||
self.remove_lens_btn.config(state='normal')
|
||||
self.edit_lens_btn.config(state='normal')
|
||||
|
||||
def _add_lens(self):
|
||||
name, ok = QInputDialog.getText(self, "Добавить объектив", "Введите название объектива:")
|
||||
if ok and name and name.strip():
|
||||
new_name = name.strip()
|
||||
if new_name in self.lenses:
|
||||
QMessageBox.warning(self, "Ошибка", "Такой объектив уже существует!")
|
||||
return
|
||||
self.lenses.append(new_name)
|
||||
self.config_service.add_lens(new_name)
|
||||
self._update_lenses_list()
|
||||
QMessageBox.information(self, "Успех", f"Объектив '{new_name}' добавлен")
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Add Lens")
|
||||
dialog.geometry("350x120")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
ttk.Label(dialog, text="Lens name:", font=('Segoe UI', 10)).pack(pady=15)
|
||||
entry = ttk.Entry(dialog, width=40)
|
||||
entry.pack(pady=5)
|
||||
entry.focus()
|
||||
|
||||
def save():
|
||||
new_lens = entry.get().strip()
|
||||
if new_lens:
|
||||
if new_lens in self.lenses:
|
||||
messagebox.showerror("Error", "Lens already exists!", parent=dialog)
|
||||
return
|
||||
self.lenses.append(new_lens)
|
||||
self.config_service.add_lens(new_lens)
|
||||
self.lenses_listbox.insert('end', new_lens)
|
||||
dialog.destroy()
|
||||
else:
|
||||
messagebox.showwarning("Warning", "Please enter lens name!", parent=dialog)
|
||||
|
||||
ttk.Button(dialog, text="OK", command=save).pack(pady=10)
|
||||
dialog.bind('<Return>', lambda e: save())
|
||||
|
||||
def _remove_lens(self):
|
||||
if hasattr(self, '_selected_lens') and self._selected_lens:
|
||||
reply = QMessageBox.question(self, "Подтверждение",
|
||||
f"Удалить объектив '{self._selected_lens}'?",
|
||||
QMessageBox.Yes | QMessageBox.No)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.lenses.remove(self._selected_lens)
|
||||
self.config_service.remove_lens(self._selected_lens)
|
||||
self._update_lenses_list()
|
||||
if self.selected_lens:
|
||||
reply = messagebox.askyesno("Remove Lens", f"Remove '{self.selected_lens}'?", parent=self)
|
||||
if reply:
|
||||
self.lenses.remove(self.selected_lens)
|
||||
self.config_service.remove_lens(self.selected_lens)
|
||||
self.lenses_listbox.delete(0, 'end')
|
||||
for lens in self.lenses:
|
||||
self.lenses_listbox.insert('end', lens)
|
||||
self.selected_lens = None
|
||||
self.remove_lens_btn.config(state='disabled')
|
||||
self.edit_lens_btn.config(state='disabled')
|
||||
|
||||
def _edit_lens(self):
|
||||
if hasattr(self, '_selected_lens') and self._selected_lens:
|
||||
new_name, ok = QInputDialog.getText(self, "Редактировать объектив",
|
||||
f"Изменить '{self._selected_lens}' на:",
|
||||
text=self._selected_lens)
|
||||
if ok and new_name and new_name.strip():
|
||||
new_name = new_name.strip()
|
||||
if new_name != self._selected_lens:
|
||||
if self.selected_lens:
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Edit Lens")
|
||||
dialog.geometry("350x120")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
ttk.Label(dialog, text="New lens name:", font=('Segoe UI', 10)).pack(pady=15)
|
||||
entry = ttk.Entry(dialog, width=40)
|
||||
entry.insert(0, self.selected_lens)
|
||||
entry.pack(pady=5)
|
||||
entry.focus()
|
||||
|
||||
def save():
|
||||
new_name = entry.get().strip()
|
||||
if new_name and new_name != self.selected_lens:
|
||||
if new_name in self.lenses:
|
||||
QMessageBox.warning(self, "Ошибка", "Такой объектив уже существует!")
|
||||
messagebox.showerror("Error", "Lens already exists!", parent=dialog)
|
||||
return
|
||||
idx = self.lenses.index(self._selected_lens)
|
||||
idx = self.lenses.index(self.selected_lens)
|
||||
self.lenses[idx] = new_name
|
||||
# Обновляем в конфиге (пока просто удаляем старый и добавляем новый)
|
||||
self.config_service.remove_lens(self._selected_lens)
|
||||
self.config_service.remove_lens(self.selected_lens)
|
||||
self.config_service.add_lens(new_name)
|
||||
self._update_lenses_list()
|
||||
self.lenses_listbox.delete(0, 'end')
|
||||
for lens in self.lenses:
|
||||
self.lenses_listbox.insert('end', lens)
|
||||
self.selected_lens = new_name
|
||||
dialog.destroy()
|
||||
elif new_name == self.selected_lens:
|
||||
dialog.destroy()
|
||||
else:
|
||||
messagebox.showwarning("Warning", "Please enter a name!", parent=dialog)
|
||||
|
||||
# ===== Методы для телескопов =====
|
||||
ttk.Button(dialog, text="Save", command=save).pack(pady=10)
|
||||
dialog.bind('<Return>', lambda e: save())
|
||||
|
||||
def _create_telescopes_tab(self, parent):
|
||||
# Listbox
|
||||
list_frame = ttk.Frame(parent)
|
||||
list_frame.pack(fill='both', expand=True, padx=10, pady=10)
|
||||
|
||||
scrollbar = ttk.Scrollbar(list_frame)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
self.telescopes_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set,
|
||||
bg='#2d2d2d', fg='#e0e0e0',
|
||||
selectbackground='#4CAF50', selectforeground='white',
|
||||
font=('Segoe UI', 10))
|
||||
self.telescopes_listbox.pack(fill='both', expand=True)
|
||||
scrollbar.config(command=self.telescopes_listbox.yview)
|
||||
|
||||
def _update_telescopes_list(self):
|
||||
self.telescopes_list.clear()
|
||||
for telescope in self.telescopes:
|
||||
self.telescopes_list.addItem(telescope)
|
||||
self._selected_telescope = None
|
||||
self.remove_telescope_btn.setEnabled(False)
|
||||
self.telescopes_listbox.insert('end', telescope)
|
||||
|
||||
def _select_telescope(self, telescope: str):
|
||||
self._selected_telescope = telescope
|
||||
self.remove_telescope_btn.setEnabled(True)
|
||||
self.cameras_list.clearSelection()
|
||||
self.lenses_list.clearSelection()
|
||||
self._selected_camera = None
|
||||
self._selected_lens = None
|
||||
self.remove_camera_btn.setEnabled(False)
|
||||
self.remove_lens_btn.setEnabled(False)
|
||||
self.telescopes_listbox.bind('<<ListboxSelect>>', self._on_telescope_select)
|
||||
|
||||
# Buttons
|
||||
btn_frame = ttk.Frame(parent)
|
||||
btn_frame.pack(pady=10)
|
||||
|
||||
ttk.Button(btn_frame, text="Add Telescope", command=self._add_telescope).pack(side='left', padx=5)
|
||||
self.remove_telescope_btn = ttk.Button(btn_frame, text="Remove", command=self._remove_telescope, state='disabled')
|
||||
self.remove_telescope_btn.pack(side='left', padx=5)
|
||||
self.edit_telescope_btn = ttk.Button(btn_frame, text="Edit", command=self._edit_telescope, state='disabled')
|
||||
self.edit_telescope_btn.pack(side='left', padx=5)
|
||||
|
||||
def _on_telescope_select(self, event):
|
||||
selection = self.telescopes_listbox.curselection()
|
||||
if selection:
|
||||
self.selected_telescope = self.telescopes_listbox.get(selection[0])
|
||||
self.remove_telescope_btn.config(state='normal')
|
||||
self.edit_telescope_btn.config(state='normal')
|
||||
|
||||
def _add_telescope(self):
|
||||
"""Добавляет телескоп с указанием диафрагмы (фиксированной)"""
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("Добавить телескоп")
|
||||
dialog.setMinimumWidth(400)
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Add Telescope")
|
||||
dialog.geometry("400x320")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
ttk.Label(dialog, text="Telescope name:").pack(pady=(15, 5))
|
||||
name_entry = ttk.Entry(dialog, width=40)
|
||||
name_entry.pack()
|
||||
|
||||
form_layout = QFormLayout()
|
||||
ttk.Label(dialog, text="Aperture (f/):").pack(pady=(10, 5))
|
||||
aperture_entry = ttk.Entry(dialog, width=20)
|
||||
aperture_entry.insert(0, "5.0")
|
||||
aperture_entry.pack()
|
||||
|
||||
name_edit = QLineEdit()
|
||||
name_edit.setPlaceholderText("例如: Celestron 8\"")
|
||||
form_layout.addRow("Название:", name_edit)
|
||||
ttk.Label(dialog, text="Focal length (mm):").pack(pady=(10, 5))
|
||||
focal_entry = ttk.Entry(dialog, width=20)
|
||||
focal_entry.insert(0, "1000")
|
||||
focal_entry.pack()
|
||||
|
||||
aperture_spin = QDoubleSpinBox()
|
||||
aperture_spin.setRange(0.5, 20.0)
|
||||
aperture_spin.setSingleStep(0.1)
|
||||
aperture_spin.setValue(5.0)
|
||||
aperture_spin.setSuffix(" (f/)")
|
||||
form_layout.addRow("Диафрагма (f/):", aperture_spin)
|
||||
ttk.Label(dialog, text="Diameter (mm):").pack(pady=(10, 5))
|
||||
diameter_entry = ttk.Entry(dialog, width=20)
|
||||
diameter_entry.insert(0, "200")
|
||||
diameter_entry.pack()
|
||||
|
||||
focal_spin = QSpinBox()
|
||||
focal_spin.setRange(100, 5000)
|
||||
focal_spin.setSingleStep(50)
|
||||
focal_spin.setSuffix(" мм")
|
||||
form_layout.addRow("Фокусное расстояние:", focal_spin)
|
||||
def save():
|
||||
name = name_entry.get().strip()
|
||||
if not name:
|
||||
messagebox.showerror("Error", "Please enter telescope name!", parent=dialog)
|
||||
return
|
||||
|
||||
diameter_spin = QSpinBox()
|
||||
diameter_spin.setRange(50, 500)
|
||||
diameter_spin.setSingleStep(10)
|
||||
diameter_spin.setSuffix(" мм")
|
||||
form_layout.addRow("Диаметр объектива:", diameter_spin)
|
||||
try:
|
||||
aperture = float(aperture_entry.get())
|
||||
focal = int(focal_entry.get())
|
||||
diameter = int(diameter_entry.get())
|
||||
except ValueError:
|
||||
messagebox.showerror("Error", "Invalid numeric values!", parent=dialog)
|
||||
return
|
||||
|
||||
layout.addLayout(form_layout)
|
||||
telescope_info = f"{name} (f/{aperture}, F={focal}mm, D={diameter}mm)"
|
||||
if telescope_info in self.telescopes:
|
||||
messagebox.showerror("Error", "Telescope already exists!", parent=dialog)
|
||||
return
|
||||
|
||||
buttons_layout = QHBoxLayout()
|
||||
ok_btn = QPushButton("OK")
|
||||
cancel_btn = QPushButton("Отмена")
|
||||
buttons_layout.addWidget(ok_btn)
|
||||
buttons_layout.addWidget(cancel_btn)
|
||||
layout.addLayout(buttons_layout)
|
||||
self.telescopes.append(telescope_info)
|
||||
self.config_service.add_telescope(telescope_info)
|
||||
self.telescopes_listbox.insert('end', telescope_info)
|
||||
dialog.destroy()
|
||||
|
||||
ok_btn.clicked.connect(dialog.accept)
|
||||
cancel_btn.clicked.connect(dialog.reject)
|
||||
|
||||
if dialog.exec():
|
||||
name = name_edit.text().strip()
|
||||
if name:
|
||||
telescope_info = f"{name} (f/{aperture_spin.value()}, F={focal_spin.value()}mm, D={diameter_spin.value()}mm)"
|
||||
if telescope_info not in self.telescopes:
|
||||
self.telescopes.append(telescope_info)
|
||||
self.config_service.add_telescope(telescope_info)
|
||||
self._update_telescopes_list()
|
||||
QMessageBox.information(self, "Успех", f"Телескоп '{name}' добавлен")
|
||||
btn_frame = ttk.Frame(dialog)
|
||||
btn_frame.pack(pady=20)
|
||||
ttk.Button(btn_frame, text="OK", command=save).pack(side='left', padx=10)
|
||||
ttk.Button(btn_frame, text="Cancel", command=dialog.destroy).pack(side='left', padx=10)
|
||||
|
||||
def _remove_telescope(self):
|
||||
if hasattr(self, '_selected_telescope') and self._selected_telescope:
|
||||
reply = QMessageBox.question(self, "Подтверждение",
|
||||
f"Удалить телескоп '{self._selected_telescope}'?",
|
||||
QMessageBox.Yes | QMessageBox.No)
|
||||
if reply == QMessageBox.Yes:
|
||||
self.telescopes.remove(self._selected_telescope)
|
||||
self.config_service.remove_telescope(self._selected_telescope)
|
||||
self._update_telescopes_list()
|
||||
if self.selected_telescope:
|
||||
reply = messagebox.askyesno("Remove Telescope", f"Remove '{self.selected_telescope}'?", parent=self)
|
||||
if reply:
|
||||
self.telescopes.remove(self.selected_telescope)
|
||||
self.config_service.remove_telescope(self.selected_telescope)
|
||||
self.telescopes_listbox.delete(0, 'end')
|
||||
for telescope in self.telescopes:
|
||||
self.telescopes_listbox.insert('end', telescope)
|
||||
self.selected_telescope = None
|
||||
self.remove_telescope_btn.config(state='disabled')
|
||||
self.edit_telescope_btn.config(state='disabled')
|
||||
|
||||
def _edit_telescope(self):
|
||||
if hasattr(self, '_selected_telescope') and self._selected_telescope:
|
||||
new_name, ok = QInputDialog.getText(self, "Редактировать телескоп",
|
||||
f"Изменить '{self._selected_telescope}' на:",
|
||||
text=self._selected_telescope)
|
||||
if ok and new_name and new_name.strip():
|
||||
new_name = new_name.strip()
|
||||
if new_name != self._selected_telescope:
|
||||
if new_name in self.telescopes:
|
||||
QMessageBox.warning(self, "Ошибка", "Такой телескоп уже существует!")
|
||||
return
|
||||
idx = self.telescopes.index(self._selected_telescope)
|
||||
self.telescopes[idx] = new_name
|
||||
self.config_service.remove_telescope(self._selected_telescope)
|
||||
self.config_service.add_telescope(new_name)
|
||||
self._update_telescopes_list()
|
||||
if self.selected_telescope:
|
||||
import re
|
||||
match = re.search(r'(.+?) \(f/([\d\.]+), F=(\d+)mm, D=(\d+)mm\)', self.selected_telescope)
|
||||
if match:
|
||||
old_name = match.group(1)
|
||||
old_aperture = match.group(2)
|
||||
old_focal = match.group(3)
|
||||
old_diameter = match.group(4)
|
||||
else:
|
||||
old_name = self.selected_telescope
|
||||
old_aperture = "5.0"
|
||||
old_focal = "1000"
|
||||
old_diameter = "200"
|
||||
|
||||
dialog = tk.Toplevel(self)
|
||||
dialog.title("Edit Telescope")
|
||||
dialog.geometry("400x320")
|
||||
dialog.transient(self)
|
||||
dialog.grab_set()
|
||||
|
||||
ttk.Label(dialog, text="Telescope name:").pack(pady=(15, 5))
|
||||
name_entry = ttk.Entry(dialog, width=40)
|
||||
name_entry.insert(0, old_name)
|
||||
name_entry.pack()
|
||||
|
||||
ttk.Label(dialog, text="Aperture (f/):").pack(pady=(10, 5))
|
||||
aperture_entry = ttk.Entry(dialog, width=20)
|
||||
aperture_entry.insert(0, old_aperture)
|
||||
aperture_entry.pack()
|
||||
|
||||
ttk.Label(dialog, text="Focal length (mm):").pack(pady=(10, 5))
|
||||
focal_entry = ttk.Entry(dialog, width=20)
|
||||
focal_entry.insert(0, old_focal)
|
||||
focal_entry.pack()
|
||||
|
||||
ttk.Label(dialog, text="Diameter (mm):").pack(pady=(10, 5))
|
||||
diameter_entry = ttk.Entry(dialog, width=20)
|
||||
diameter_entry.insert(0, old_diameter)
|
||||
diameter_entry.pack()
|
||||
|
||||
def save():
|
||||
new_name = name_entry.get().strip()
|
||||
try:
|
||||
aperture = float(aperture_entry.get())
|
||||
focal = int(focal_entry.get())
|
||||
diameter = int(diameter_entry.get())
|
||||
except ValueError:
|
||||
messagebox.showerror("Error", "Invalid numeric values!", parent=dialog)
|
||||
return
|
||||
|
||||
new_info = f"{new_name} (f/{aperture}, F={focal}mm, D={diameter}mm)"
|
||||
if new_info != self.selected_telescope and new_info in self.telescopes:
|
||||
messagebox.showerror("Error", "Telescope already exists!", parent=dialog)
|
||||
return
|
||||
|
||||
idx = self.telescopes.index(self.selected_telescope)
|
||||
self.telescopes[idx] = new_info
|
||||
self.config_service.remove_telescope(self.selected_telescope)
|
||||
self.config_service.add_telescope(new_info)
|
||||
self.telescopes_listbox.delete(0, 'end')
|
||||
for telescope in self.telescopes:
|
||||
self.telescopes_listbox.insert('end', telescope)
|
||||
dialog.destroy()
|
||||
|
||||
btn_frame = ttk.Frame(dialog)
|
||||
btn_frame.pack(pady=20)
|
||||
ttk.Button(btn_frame, text="Save", command=save).pack(side='left', padx=10)
|
||||
ttk.Button(btn_frame, text="Cancel", command=dialog.destroy).pack(side='left', padx=10)
|
||||
|
|
@ -1,164 +1,152 @@
|
|||
"""
|
||||
InstructionsDialog - диалог с инструкцией по использованию
|
||||
InstructionsDialog - диалог с инструкцией на английском (tkinter)
|
||||
"""
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QTextEdit,
|
||||
QPushButton, QScrollArea
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
|
||||
class InstructionsDialog(QDialog):
|
||||
"""Диалог с подробной инструкцией пользователя"""
|
||||
class InstructionsDialog(tk.Toplevel):
|
||||
"""Диалог с подробной инструкцией пользователя на английском"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
|
||||
self.setWindowTitle("Инструкция по использованию")
|
||||
self.setMinimumSize(700, 500)
|
||||
self.resize(750, 550)
|
||||
self.title("Instructions")
|
||||
self.geometry("750x600")
|
||||
self.minsize(700, 500)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
self._create_ui()
|
||||
self._center_window()
|
||||
|
||||
def _create_ui(self):
|
||||
"""Создаёт интерфейс диалога"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(10)
|
||||
layout.setContentsMargins(15, 15, 15, 15)
|
||||
# Main frame
|
||||
main_frame = ttk.Frame(self, padding="15")
|
||||
main_frame.pack(fill='both', expand=True)
|
||||
|
||||
# Заголовок
|
||||
title_label = QLabel("Astro Session Watcher - Руководство пользователя")
|
||||
title_font = QFont()
|
||||
title_font.setPointSize(16)
|
||||
title_font.setBold(True)
|
||||
title_label.setFont(title_font)
|
||||
title_label.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
# Title
|
||||
ttk.Label(main_frame, text="Astro Session Watcher - User Guide", font=('Segoe UI', 16, 'bold')).pack(pady=(0, 10))
|
||||
|
||||
# Текст инструкции
|
||||
text_edit = QTextEdit()
|
||||
text_edit.setReadOnly(True)
|
||||
text_edit.setFont(QFont("Consolas", 10))
|
||||
# Text with scrollbar
|
||||
text_frame = ttk.Frame(main_frame)
|
||||
text_frame.pack(fill='both', expand=True)
|
||||
|
||||
instructions = """
|
||||
======================= ASTRO SESSION WATCHER =======================
|
||||
scrollbar = ttk.Scrollbar(text_frame)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
Приложение автоматически отслеживает появление новых фотографий в указанной папке,
|
||||
сортирует их по объектам съемки и ведет подробный лог всего процесса.
|
||||
self.text_widget = tk.Text(text_frame, yscrollcommand=scrollbar.set, wrap='word',
|
||||
bg='#1e1e1e', fg='#e0e0e0', font=('Consolas', 10))
|
||||
self.text_widget.pack(fill='both', expand=True)
|
||||
scrollbar.config(command=self.text_widget.yview)
|
||||
|
||||
📸 ДЛЯ ЧЕГО ЭТО НУЖНО?
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
# Instructions text
|
||||
instructions = """======================= ASTRO SESSION WATCHER =======================
|
||||
|
||||
Когда вы снимаете астрономические объекты через EOS Utility или аналогичное ПО,
|
||||
все фотографии сохраняются в одну папку. Astro Session Watcher помогает:
|
||||
The application automatically tracks new photos in the selected folder,
|
||||
sorts them by observation targets and maintains detailed logs.
|
||||
|
||||
• Автоматически распределять снимки по папкам объектов
|
||||
• Вести лог каждой сессии
|
||||
• Не пропустить ни одного кадра при смене объекта
|
||||
• Хранить историю оборудования и небесных тел
|
||||
📸 WHAT IS IT FOR?
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🚀 КАК ЭТО РАБОТАЕТ?
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
• Automatically distribute shots into target folders
|
||||
• Keep a log of each session
|
||||
• Don't miss a single frame when changing targets
|
||||
• Store equipment and celestial bodies history
|
||||
|
||||
1. Вы выбираете папку, куда камера сохраняет снимки
|
||||
2. Приложение создает папку сессии: "AstroSession_ГГГГ-ММ-ДД"
|
||||
3. Каждый объект съемки получает свою подпапку внутри папки сессии
|
||||
4. Когда вы меняете объект (нажимаете "Новая цель"), все накопленные
|
||||
в папке наблюдения файлы автоматически ПЕРЕМЕЩАЮТСЯ в папку предыдущего объекта
|
||||
5. При завершении сессии оставшиеся файлы также перемещаются в папку последнего объекта
|
||||
🚀 HOW IT WORKS?
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📝 ПОШАГОВАЯ ИНСТРУКЦИЯ
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
1. Select the folder where your camera saves photos
|
||||
2. The app creates a session folder: "AstroSession_YYYY-MM-DD"
|
||||
3. Each target gets its own subfolder inside the session folder
|
||||
4. When you change target (press "New Target"), all accumulated files are moved
|
||||
5. When you end the session, remaining files are moved to the last target
|
||||
|
||||
█ 1. ПЕРВЫЙ ЗАПУСК (НАСТРОЙКА)
|
||||
───────────────────────────────────────────────────────────────────────────────
|
||||
📝 STEP-BY-STEP GUIDE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
• Откройте меню "Файл" → "Оборудование" и добавьте ваши камеры и объективы
|
||||
• Откройте меню "Файл" → "Небесные тела" и добавьте объекты для наблюдения
|
||||
• Все данные сохраняются автоматически в файлах настроек
|
||||
█ 1. FIRST LAUNCH (SETUP)
|
||||
───────────────────────────────────────────────────────────────────
|
||||
|
||||
█ 2. ЗАПУСК СЕССИИ
|
||||
───────────────────────────────────────────────────────────────────────────────
|
||||
• Go to "File" → "Equipment" and add your cameras and lenses/telescopes
|
||||
• Go to "File" → "Celestial Bodies" and add your observation targets
|
||||
• All data is saved automatically in config files
|
||||
|
||||
1. Нажмите "Обзор" и выберите папку, куда камера сохраняет снимки
|
||||
2. Выберите камеру и объектив из выпадающих списков
|
||||
3. Введите название цели (или выберите из списка небесных тел)
|
||||
4. Нажмите ▶ "Начать отслеживание"
|
||||
█ 2. STARTING A SESSION
|
||||
───────────────────────────────────────────────────────────────────
|
||||
|
||||
✅ После запуска:
|
||||
• Статус изменится на "● ON AIR" с мигающим красным текстом
|
||||
• Кнопка "Новая цель" начнет мигать красным контуром
|
||||
• В папке наблюдения создастся папка "AstroSession_дата"
|
||||
• Внутри - папка с вашей первой целью
|
||||
1. Click "Browse" and select the folder where your camera saves photos
|
||||
2. Select camera and lens/telescope from dropdowns
|
||||
3. Enter target name (or select from celestial bodies list)
|
||||
4. Click "▶ Start Tracking"
|
||||
|
||||
█ 3. СМЕНА ОБЪЕКТА ВО ВРЕМЯ СЕССИИ
|
||||
───────────────────────────────────────────────────────────────────────────────
|
||||
✅ After launch:
|
||||
• Status changes to "● ON AIR" with blinking
|
||||
• "New Target" button becomes active
|
||||
• Session folder "AstroSession_date" is created
|
||||
• Inside - folder with your first target
|
||||
|
||||
Когда вы заканчиваете снимать один объект и переходите к другому:
|
||||
█ 3. CHANGING TARGET DURING SESSION
|
||||
───────────────────────────────────────────────────────────────────
|
||||
|
||||
1. Нажмите кнопку "Новая цель" (или Ctrl+Shift+N)
|
||||
2. Введите название нового объекта
|
||||
3. Приложение автоматически:
|
||||
• Переместит все накопленные файлы в папку предыдущего объекта
|
||||
• Создаст новую папку для следующего объекта
|
||||
• Сбросит счетчик файлов
|
||||
• Продолжит отслеживание
|
||||
1. Click "New Target" button (or Ctrl+Shift+N)
|
||||
2. Enter new target name
|
||||
3. The app automatically moves all accumulated files to the previous target
|
||||
4. Creates new folder for the next target
|
||||
5. Resets file counter
|
||||
6. Continues tracking
|
||||
|
||||
💡 ВАЖНО: Если перед сменой объекта в папке наблюдения уже есть файлы,
|
||||
они НЕ ПОТЕРЯЮТСЯ - все будут перемещены в папку текущего объекта!
|
||||
💡 IMPORTANT: If there are files in the watch folder before changing target,
|
||||
they will NOT be lost - all will be moved!
|
||||
|
||||
█ 4. ЗАВЕРШЕНИЕ СЕССИИ
|
||||
───────────────────────────────────────────────────────────────────────────────
|
||||
█ 4. ENDING A SESSION
|
||||
───────────────────────────────────────────────────────────────────
|
||||
|
||||
1. Нажмите ■ "Остановить" (или Ctrl+X)
|
||||
2. Приложение:
|
||||
• Переместит все оставшиеся файлы в папку последнего объекта
|
||||
• Запишет итоговый лог сессии
|
||||
• Покажет диалог с предложением открыть папку сессии
|
||||
• Восстановит интерфейс для новой сессии
|
||||
1. Click "■ Stop" (or Ctrl+X)
|
||||
2. The app moves all remaining files to the last target
|
||||
3. Writes final session log
|
||||
4. Shows dialog with option to open session folder
|
||||
5. Restores interface for new session
|
||||
|
||||
⌨️ ГОРЯЧИЕ КЛАВИШИ
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
⌨️ HOTKEYS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Ctrl + O → Выбрать папку наблюдения
|
||||
Ctrl + E → Управление оборудованием
|
||||
Ctrl + B → Управление небесными телами
|
||||
Ctrl + S → Начать сессию
|
||||
Ctrl + X → Остановить сессию
|
||||
Ctrl + F → Открыть папку текущей сессии
|
||||
Ctrl + Shift+N → Создать новый объект
|
||||
F1 → О программе
|
||||
F2 → Эта инструкция
|
||||
Ctrl + O → Select watch folder
|
||||
Ctrl + E → Equipment management
|
||||
Ctrl + B → Celestial bodies management
|
||||
Ctrl + S → Start session
|
||||
Ctrl + X → Stop session
|
||||
Ctrl + F → Open current session folder
|
||||
Ctrl + Shift+N → Create new target
|
||||
F1 → About
|
||||
F2 → This instruction
|
||||
|
||||
🔧 ФАЙЛЫ НАСТРОЕК
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔧 CONFIG FILES
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📄 astro_settings.json ← камеры, объективы, последняя папка
|
||||
📄 celestial_bodies.json ← список небесных тел
|
||||
📄 astro_settings.json ← cameras, lenses, last folder
|
||||
📄 celestial_bodies.json ← list of celestial bodies
|
||||
|
||||
Все файлы хранятся в папке с программой. Вы можете редактировать их вручную.
|
||||
All files are stored in the program folder. You can edit them manually.
|
||||
|
||||
📧 ТЕХНИЧЕСКАЯ ПОДДЕРЖКА
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Разработчик: Vic Sergeev
|
||||
Версия: 0.3.0-alpha
|
||||
|
||||
При обнаружении ошибок или для предложений по улучшению:
|
||||
• Сообщите разработчику
|
||||
• Приложите файлы логов (SessionLog.txt, ObjectLog.txt)
|
||||
📧 TECHNICAL SUPPORT
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Text me: norvicdev@gmail.com
|
||||
Developer: Vic Sergeev
|
||||
Version: 0.4.0-alpha
|
||||
"""
|
||||
|
||||
text_edit.setText(instructions)
|
||||
layout.addWidget(text_edit)
|
||||
self.text_widget.insert('1.0', instructions)
|
||||
self.text_widget.config(state='disabled')
|
||||
|
||||
# Кнопка закрытия
|
||||
close_layout = QHBoxLayout()
|
||||
close_layout.addStretch()
|
||||
# Close button
|
||||
ttk.Button(main_frame, text="Close", command=self.destroy).pack(pady=10)
|
||||
|
||||
close_btn = QPushButton("Закрыть")
|
||||
close_btn.clicked.connect(self.accept)
|
||||
close_layout.addWidget(close_btn)
|
||||
|
||||
layout.addLayout(close_layout)
|
||||
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}')
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue