reworked with tkinter, UI improements needed
This commit is contained in:
parent
d3878857af
commit
3ae07ca289
26 changed files with 1488 additions and 1751 deletions
23
.idea/workspace.xml
generated
23
.idea/workspace.xml
generated
|
|
@ -1,7 +1,18 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="40c00e43-cac1-46a2-8996-291e69775bbb" name="Changes" comment="" />
|
<list default="true" id="40c00e43-cac1-46a2-8996-291e69775bbb" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/astro_settings.json" beforeDir="false" afterPath="$PROJECT_DIR$/astro_settings.json" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/celestial_bodies.json" beforeDir="false" afterPath="$PROJECT_DIR$/celestial_bodies.json" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/ui/dialogs/calibration_dialog.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/dialogs/calibration_dialog.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/ui/dialogs/calibration_type_dialog.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/dialogs/calibration_type_dialog.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/ui/dialogs/celestial_dialog.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/dialogs/celestial_dialog.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/ui/dialogs/equipment_dialog.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/dialogs/equipment_dialog.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/ui/dialogs/instructions_dialog.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/dialogs/instructions_dialog.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/ui/main_window.py" beforeDir="false" afterPath="$PROJECT_DIR$/ui/main_window.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
|
@ -17,9 +28,9 @@
|
||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo"><![CDATA[{
|
<component name="ProjectColorInfo">{
|
||||||
"associatedIndex": 8
|
"associatedIndex": 8
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="ProjectId" id="3DOCpPEAGgO0as5zvcSEwPDpHTU" />
|
<component name="ProjectId" id="3DOCpPEAGgO0as5zvcSEwPDpHTU" />
|
||||||
<component name="ProjectLevelVcsManager">
|
<component name="ProjectLevelVcsManager">
|
||||||
<ConfirmationsSetting value="2" id="Add" />
|
<ConfirmationsSetting value="2" id="Add" />
|
||||||
|
|
@ -39,7 +50,7 @@
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||||
"ai.playground.ignore.import.keys.banner.in.settings": "true",
|
"ai.playground.ignore.import.keys.banner.in.settings": "true",
|
||||||
"git-widget-placeholder": "dev-pyside",
|
"git-widget-placeholder": "dev-tkinter-v2",
|
||||||
"ignore.virus.scanning.warn.message": "true",
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
|
@ -68,6 +79,6 @@
|
||||||
<option name="version" value="3" />
|
<option name="version" value="3" />
|
||||||
</component>
|
</component>
|
||||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
<SUITE FILE_PATH="coverage/AstroSessionWatcher$main.coverage" NAME="main Coverage Results" MODIFIED="1778176299697" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
<SUITE FILE_PATH="coverage/AstroSessionWatcher$main.coverage" NAME="main Coverage Results" MODIFIED="1778343435220" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
{
|
{
|
||||||
"cameras": [
|
"cameras": [
|
||||||
"Canon 40D",
|
"Canon 40D",
|
||||||
"Canon 400D",
|
"Canon 400D"
|
||||||
"Canon 500D"
|
|
||||||
],
|
],
|
||||||
"lenses": [
|
"lenses": [
|
||||||
"MTO-500A",
|
"MTO-500A",
|
||||||
"Юпитер-21м",
|
"Tamron 18-200mm",
|
||||||
"Tamron 18-200mm"
|
"Юпитер-21м 200мм"
|
||||||
],
|
],
|
||||||
"telescopes": [
|
"telescopes": [
|
||||||
"Celestron Astromaster 130 (f/5.0, F=650mm, D=130mm)"
|
"Celestron Astromaster 130 (f/5.0, F=650mm, D=130mm)"
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,6 @@
|
||||||
"M89",
|
"M89",
|
||||||
"Венера",
|
"Венера",
|
||||||
"Меркурий",
|
"Меркурий",
|
||||||
"Нептун"
|
"Нептун",
|
||||||
|
"Saturn"
|
||||||
]
|
]
|
||||||
24
main.py
24
main.py
|
|
@ -1,32 +1,22 @@
|
||||||
"""
|
"""
|
||||||
Astro Session Watcher - Главный входной файл
|
Astro Session Watcher v0.4.0 - tkinter версия
|
||||||
Приложение для астрофотографов с отслеживанием файлов и сортировкой по объектам
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Добавляем корневую директорию в путь
|
# Добавляем корневую директорию в путь
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
from ui.main_window import MainWindow
|
from ui.main_window import MainWindow
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Точка входа в приложение"""
|
root = tk.Tk()
|
||||||
app = QApplication(sys.argv)
|
app = MainWindow(root)
|
||||||
|
root.mainloop()
|
||||||
# Устанавливаем стиль Fusion для современного вида
|
|
||||||
app.setStyle("Fusion")
|
|
||||||
|
|
||||||
# Тёмная палитра
|
|
||||||
app.setPalette(app.style().standardPalette())
|
|
||||||
|
|
||||||
window = MainWindow()
|
|
||||||
window.show()
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
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.
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)
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
self.config_service = config_service
|
self.config_service = config_service
|
||||||
|
self._blink_active = False
|
||||||
|
self._blink_after_id = None
|
||||||
|
|
||||||
self.setWindowTitle("🌑 Калибровочные кадры")
|
self.title("Calibration Frames")
|
||||||
self.setMinimumSize(600, 450)
|
self.geometry("650x500")
|
||||||
self.resize(650, 500)
|
self.minsize(600, 450)
|
||||||
|
self.transient(parent)
|
||||||
|
self.grab_set()
|
||||||
|
|
||||||
self._create_ui()
|
self._create_ui()
|
||||||
self._load_saved_settings()
|
self._load_saved_settings()
|
||||||
|
|
||||||
# Таймер для мигания кнопки "Обзор"
|
|
||||||
self._browse_blink_timer = None
|
|
||||||
self._check_folder_path()
|
self._check_folder_path()
|
||||||
|
self._center_window()
|
||||||
|
|
||||||
def _create_ui(self):
|
def _create_ui(self):
|
||||||
layout = QVBoxLayout(self)
|
# Main frame
|
||||||
layout.setSpacing(20)
|
main_frame = ttk.Frame(self, padding="25")
|
||||||
layout.setContentsMargins(25, 25, 25, 25)
|
main_frame.pack(fill='both', expand=True)
|
||||||
|
|
||||||
# Заголовок
|
# Title
|
||||||
title_label = QLabel("🌑 Калибровочные кадры")
|
title_label = ttk.Label(main_frame, text="Calibration Frames", font=('Segoe UI', 18, 'bold'))
|
||||||
title_font = QFont()
|
title_label.pack(pady=(0, 15))
|
||||||
title_font.setPointSize(18)
|
|
||||||
title_font.setBold(True)
|
|
||||||
title_label.setFont(title_font)
|
|
||||||
layout.addWidget(title_label)
|
|
||||||
|
|
||||||
# Основная сетка
|
# Separator
|
||||||
grid = QGridLayout()
|
ttk.Separator(main_frame, orient='horizontal').pack(fill='x', pady=(0, 15))
|
||||||
grid.setVerticalSpacing(15)
|
|
||||||
grid.setHorizontalSpacing(15)
|
|
||||||
|
|
||||||
# Строка 0: Камера
|
# Camera selection
|
||||||
camera_label = QLabel("📷 Камера:")
|
camera_frame = ttk.Frame(main_frame)
|
||||||
camera_label.setFont(QFont("", 10, QFont.Bold))
|
camera_frame.pack(fill='x', pady=5)
|
||||||
grid.addWidget(camera_label, 0, 0)
|
|
||||||
|
|
||||||
self.camera_combo = QComboBox()
|
ttk.Label(camera_frame, text="Camera:", font=('Segoe UI', 10, 'bold')).pack(side='left', padx=(0, 10))
|
||||||
self.camera_combo.setEditable(True)
|
|
||||||
self.camera_combo.setMinimumWidth(250)
|
|
||||||
grid.addWidget(self.camera_combo, 0, 1)
|
|
||||||
|
|
||||||
# Строка 1: Папка
|
self.camera_combo = ttk.Combobox(camera_frame, width=30)
|
||||||
folder_label = QLabel("📁 Папка:")
|
self.camera_combo.pack(side='left', fill='x', expand=True)
|
||||||
folder_label.setFont(QFont("", 10, QFont.Bold))
|
|
||||||
grid.addWidget(folder_label, 1, 0)
|
|
||||||
|
|
||||||
folder_widget = QWidget()
|
# Folder selection
|
||||||
folder_layout = QHBoxLayout(folder_widget)
|
folder_frame = ttk.Frame(main_frame)
|
||||||
folder_layout.setContentsMargins(0, 0, 0, 0)
|
folder_frame.pack(fill='x', pady=5)
|
||||||
folder_layout.setSpacing(10)
|
|
||||||
|
|
||||||
self.folder_entry = QLineEdit()
|
ttk.Label(folder_frame, text="Folder:", font=('Segoe UI', 10, 'bold')).pack(side='left', padx=(0, 10))
|
||||||
self.folder_entry.setPlaceholderText("Выберите папку для сохранения калибровочных кадров")
|
|
||||||
folder_layout.addWidget(self.folder_entry)
|
|
||||||
|
|
||||||
self.browse_button = QPushButton("✨ Обзор")
|
folder_input_frame = ttk.Frame(folder_frame)
|
||||||
self.browse_button.setFixedWidth(100)
|
folder_input_frame.pack(side='left', fill='x', expand=True)
|
||||||
self.browse_button.clicked.connect(self._browse_folder)
|
|
||||||
folder_layout.addWidget(self.browse_button)
|
|
||||||
|
|
||||||
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
|
||||||
separator = QFrame()
|
ttk.Separator(main_frame, orient='horizontal').pack(fill='x', pady=15)
|
||||||
separator.setFrameShape(QFrame.HLine)
|
|
||||||
separator.setStyleSheet("background-color: #333333; max-height: 1px;")
|
|
||||||
layout.addWidget(separator)
|
|
||||||
|
|
||||||
# Кнопки типов кадров
|
# Type buttons
|
||||||
types_layout = QHBoxLayout()
|
types_frame = ttk.Frame(main_frame)
|
||||||
types_layout.setSpacing(20)
|
types_frame.pack(pady=10)
|
||||||
types_layout.setAlignment(Qt.AlignCenter)
|
|
||||||
|
|
||||||
self.bias_btn = QPushButton("⚪ BIAS")
|
self.bias_btn = tk.Button(types_frame, text="BIAS", font=('Segoe UI', 12, 'bold'),
|
||||||
self.bias_btn.setFixedSize(120, 50)
|
bg='#2196F3', fg='white', activebackground='#1976D2',
|
||||||
self.bias_btn.setStyleSheet("""
|
width=12, height=2,
|
||||||
QPushButton {
|
command=lambda: self._open_calibration_type('bias'))
|
||||||
background-color: #2196F3;
|
self.bias_btn.pack(side='left', padx=10)
|
||||||
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 = tk.Button(types_frame, text="DARK", font=('Segoe UI', 12, 'bold'),
|
||||||
self.dark_btn.setFixedSize(120, 50)
|
bg='#9C27B0', fg='white', activebackground='#7B1FA2',
|
||||||
self.dark_btn.setStyleSheet("""
|
width=12, height=2,
|
||||||
QPushButton {
|
command=lambda: self._open_calibration_type('dark'))
|
||||||
background-color: #9C27B0;
|
self.dark_btn.pack(side='left', padx=10)
|
||||||
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 = tk.Button(types_frame, text="FLAT", font=('Segoe UI', 12, 'bold'),
|
||||||
self.flat_btn.setFixedSize(120, 50)
|
bg='#4CAF50', fg='white', activebackground='#388E3C',
|
||||||
self.flat_btn.setStyleSheet("""
|
width=12, height=2,
|
||||||
QPushButton {
|
command=lambda: self._open_calibration_type('flat'))
|
||||||
background-color: #4CAF50;
|
self.flat_btn.pack(side='left', padx=10)
|
||||||
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)
|
# Tips frame
|
||||||
types_layout.addWidget(self.dark_btn)
|
tips_frame = tk.Frame(main_frame, bg='#2d2d2d', relief='groove', bd=1)
|
||||||
types_layout.addWidget(self.flat_btn)
|
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))
|
||||||
|
|
||||||
# Совет
|
self.tips_label = tk.Label(tips_frame,
|
||||||
tips_frame = QFrame()
|
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",
|
||||||
tips_frame.setStyleSheet("""
|
bg='#2d2d2d', fg='#e0e0e0', justify='left', font=('Segoe UI', 9))
|
||||||
QFrame {
|
self.tips_label.pack(anchor='w', padx=10, pady=(0, 10))
|
||||||
background-color: #2d2d2d;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
tips_layout = QVBoxLayout(tips_frame)
|
|
||||||
|
|
||||||
tips_title = QLabel("💡 Совет")
|
# Cancel button
|
||||||
tips_title.setFont(QFont("", 11, QFont.Bold))
|
btn_frame = ttk.Frame(main_frame)
|
||||||
tips_layout.addWidget(tips_title)
|
btn_frame.pack(pady=10)
|
||||||
|
ttk.Button(btn_frame, text="Cancel", command=self.destroy).pack()
|
||||||
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):
|
def _load_saved_settings(self):
|
||||||
"""Загружает сохранённые камеры"""
|
|
||||||
cameras = self.config_service.get_cameras()
|
cameras = self.config_service.get_cameras()
|
||||||
if cameras:
|
if cameras:
|
||||||
self.camera_combo.addItems(cameras)
|
self.camera_combo['values'] = cameras
|
||||||
|
|
||||||
last_camera = self.config_service.get_last_camera()
|
last_camera = self.config_service.get_last_camera()
|
||||||
if last_camera and last_camera in cameras:
|
if last_camera and last_camera in cameras:
|
||||||
self.camera_combo.setCurrentText(last_camera)
|
self.camera_combo.set(last_camera)
|
||||||
|
|
||||||
def _browse_folder(self):
|
def _browse_folder(self):
|
||||||
"""Выбор папки для калибровочных кадров"""
|
folder = filedialog.askdirectory(title="Select folder for calibration frames")
|
||||||
folder = QFileDialog.getExistingDirectory(self, "Выберите папку для калибровочных кадров")
|
|
||||||
if folder:
|
if folder:
|
||||||
self.folder_entry.setText(folder)
|
self.folder_entry.delete(0, tk.END)
|
||||||
self._stop_browse_blinking()
|
self.folder_entry.insert(0, folder)
|
||||||
|
self._stop_blinking()
|
||||||
|
self.browse_btn.config(bg='#3c3c3c', fg='#e0e0e0')
|
||||||
|
|
||||||
def _check_folder_path(self):
|
def _check_folder_path(self):
|
||||||
"""Проверяет, заполнено ли поле пути и запускает мигание если нет"""
|
if not self.folder_entry.get():
|
||||||
if not self.folder_entry.text():
|
self._start_blinking()
|
||||||
self._start_browse_blinking()
|
|
||||||
else:
|
else:
|
||||||
self._stop_browse_blinking()
|
self._stop_blinking()
|
||||||
|
|
||||||
def _start_browse_blinking(self):
|
def _start_blinking(self):
|
||||||
"""Запускает мигание кнопки 'Обзор' зелёным цветом"""
|
self._blink_active = True
|
||||||
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):
|
def blink():
|
||||||
"""Мигание кнопки"""
|
if not self._blink_active:
|
||||||
current_style = self.browse_button.styleSheet()
|
return
|
||||||
if "background-color: #4CAF50" in current_style:
|
if self.browse_btn.cget('bg') == '#3c3c3c':
|
||||||
self.browse_button.setStyleSheet("""
|
self.browse_btn.config(bg='#f44336', fg='white')
|
||||||
QPushButton {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
else:
|
else:
|
||||||
self.browse_button.setStyleSheet("""
|
self.browse_btn.config(bg='#3c3c3c', fg='#e0e0e0')
|
||||||
QPushButton {
|
self._blink_after_id = self.after(1500, blink)
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
def _stop_browse_blinking(self):
|
blink()
|
||||||
"""Останавливает мигание кнопки"""
|
|
||||||
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):
|
def _stop_blinking(self):
|
||||||
"""Открывает дочернее окно для выбранного типа калибровки"""
|
self._blink_active = False
|
||||||
if not self.folder_entry.text():
|
if self._blink_after_id:
|
||||||
QMessageBox.warning(self, "Внимание", "Сначала выберите папку для сохранения!")
|
self.after_cancel(self._blink_after_id)
|
||||||
self._start_browse_blinking()
|
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
|
return
|
||||||
|
|
||||||
camera_name = self.camera_combo.currentText()
|
camera_name = self.camera_combo.get()
|
||||||
if not camera_name:
|
if not camera_name:
|
||||||
QMessageBox.warning(self, "Внимание", "Введите или выберите название камеры!")
|
messagebox.showwarning("Warning", "Please enter or select a camera name!", parent=self)
|
||||||
return
|
return
|
||||||
|
|
||||||
from ui.dialogs.calibration_type_dialog import CalibrationTypeDialog
|
from ui.dialogs.calibration_type_dialog import CalibrationTypeDialog
|
||||||
dialog = CalibrationTypeDialog(
|
dialog = CalibrationTypeDialog(
|
||||||
self,
|
self,
|
||||||
cal_type,
|
cal_type,
|
||||||
self.folder_entry.text(),
|
self.folder_entry.get(),
|
||||||
camera_name,
|
camera_name,
|
||||||
self.config_service
|
self.config_service
|
||||||
)
|
)
|
||||||
|
self.wait_window(dialog)
|
||||||
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()
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,160 +1,153 @@
|
||||||
"""
|
"""
|
||||||
CelestialDialog - диалог управления небесными телами
|
CelestialDialog - диалог управления небесными телами (tkinter)
|
||||||
Аналог CelestialBodiesDialogController из JavaFX версии
|
|
||||||
"""
|
"""
|
||||||
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)
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
self.config_service = config_service
|
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.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._create_ui()
|
||||||
self._update_list()
|
self._center_window()
|
||||||
|
|
||||||
def _create_ui(self):
|
def _create_ui(self):
|
||||||
"""Создаёт интерфейс диалога"""
|
# Main frame
|
||||||
layout = QVBoxLayout(self)
|
main_frame = ttk.Frame(self, padding="20")
|
||||||
layout.setSpacing(15)
|
main_frame.pack(fill='both', expand=True)
|
||||||
layout.setContentsMargins(20, 20, 20, 20)
|
|
||||||
|
|
||||||
# Заголовок
|
# Title
|
||||||
title_label = QLabel("Управление небесными телами")
|
ttk.Label(main_frame, text="Celestial Bodies", font=('Segoe UI', 14, 'bold')).pack(pady=(0, 10))
|
||||||
title_font = QFont()
|
ttk.Label(main_frame, text="List of observation targets", font=('Segoe UI', 10)).pack(pady=(0, 15))
|
||||||
title_font.setPointSize(16)
|
|
||||||
title_font.setBold(True)
|
|
||||||
title_label.setFont(title_font)
|
|
||||||
title_label.setAlignment(Qt.AlignCenter)
|
|
||||||
layout.addWidget(title_label)
|
|
||||||
|
|
||||||
# Подпись
|
# Listbox with scrollbar
|
||||||
subtitle_label = QLabel("Список объектов для наблюдения")
|
list_frame = ttk.Frame(main_frame)
|
||||||
subtitle_font = QFont()
|
list_frame.pack(fill='both', expand=True, pady=(0, 10))
|
||||||
subtitle_font.setPointSize(11)
|
|
||||||
subtitle_font.setBold(True)
|
|
||||||
subtitle_label.setFont(subtitle_font)
|
|
||||||
layout.addWidget(subtitle_label)
|
|
||||||
|
|
||||||
# Список небесных тел
|
scrollbar = ttk.Scrollbar(list_frame)
|
||||||
self.bodies_list = QListWidget()
|
scrollbar.pack(side='right', fill='y')
|
||||||
self.bodies_list.itemClicked.connect(lambda item: self._select_body(item.text()))
|
|
||||||
layout.addWidget(self.bodies_list)
|
|
||||||
|
|
||||||
# Поле для добавления нового
|
self.bodies_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set, height=15,
|
||||||
add_layout = QHBoxLayout()
|
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:
|
for body in self.celestial_bodies:
|
||||||
self.bodies_list.addItem(body)
|
self.bodies_listbox.insert('end', body)
|
||||||
self._selected_body = None
|
|
||||||
self.remove_btn.setEnabled(False)
|
|
||||||
self.edit_btn.setEnabled(False)
|
|
||||||
|
|
||||||
def _select_body(self, body: str):
|
self.bodies_listbox.bind('<<ListboxSelect>>', self._on_body_select)
|
||||||
"""Выделяет объект в списке"""
|
|
||||||
self._selected_body = body
|
# Add new body
|
||||||
self.remove_btn.setEnabled(True)
|
add_frame = ttk.Frame(main_frame)
|
||||||
self.edit_btn.setEnabled(True)
|
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):
|
def _add_celestial_body(self):
|
||||||
"""Добавляет новое небесное тело"""
|
new_body = self.new_body_entry.get().strip()
|
||||||
new_body = self.new_body_entry.text()
|
if not new_body:
|
||||||
if not new_body or not new_body.strip():
|
messagebox.showwarning("Warning", "Please enter object name!", parent=self)
|
||||||
QMessageBox.warning(self, "Ошибка", "Введите название объекта")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
new_name = new_body.strip()
|
if new_body in self.celestial_bodies:
|
||||||
if new_name in self.celestial_bodies:
|
messagebox.showwarning("Warning", f"Object '{new_body}' already exists!", parent=self)
|
||||||
QMessageBox.warning(self, "Ошибка", f"Объект '{new_name}' уже существует!")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.celestial_bodies.append(new_name)
|
self.celestial_bodies.append(new_body)
|
||||||
self.config_service.add_celestial_body(new_name)
|
self.config_service.add_celestial_body(new_body)
|
||||||
self._update_list()
|
self.bodies_listbox.insert('end', new_body)
|
||||||
self.new_body_entry.clear()
|
self.new_body_entry.delete(0, 'end')
|
||||||
QMessageBox.information(self, "Успех", f"Объект '{new_name}' добавлен")
|
messagebox.showinfo("Success", f"Object '{new_body}' added", parent=self)
|
||||||
|
|
||||||
def _remove_celestial_body(self):
|
def _remove_celestial_body(self):
|
||||||
"""Удаляет выбранное небесное тело"""
|
if self.selected_body:
|
||||||
if hasattr(self, '_selected_body') and self._selected_body:
|
reply = messagebox.askyesno("Remove Object", f"Remove '{self.selected_body}'?", parent=self)
|
||||||
reply = QMessageBox.question(self, "Подтверждение",
|
if reply:
|
||||||
f"Удалить объект '{self._selected_body}'?",
|
self.celestial_bodies.remove(self.selected_body)
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
self.config_service.remove_celestial_body(self.selected_body)
|
||||||
if reply == QMessageBox.Yes:
|
self.bodies_listbox.delete(0, 'end')
|
||||||
self.celestial_bodies.remove(self._selected_body)
|
for body in self.celestial_bodies:
|
||||||
self.config_service.remove_celestial_body(self._selected_body)
|
self.bodies_listbox.insert('end', body)
|
||||||
self._update_list()
|
self.selected_body = None
|
||||||
QMessageBox.information(self, "Успех", f"Объект '{self._selected_body}' удалён")
|
self.remove_btn.config(state='disabled')
|
||||||
|
self.edit_btn.config(state='disabled')
|
||||||
|
|
||||||
def _edit_celestial_body(self):
|
def _edit_celestial_body(self):
|
||||||
"""Редактирует выбранное небесное тело"""
|
if self.selected_body:
|
||||||
if hasattr(self, '_selected_body') and self._selected_body:
|
dialog = tk.Toplevel(self)
|
||||||
new_name, ok = QInputDialog.getText(self, "Редактировать",
|
dialog.title("Edit Celestial Body")
|
||||||
f"Изменить '{self._selected_body}' на:",
|
dialog.geometry("350x120")
|
||||||
text=self._selected_body)
|
dialog.transient(self)
|
||||||
if ok and new_name and new_name.strip():
|
dialog.grab_set()
|
||||||
new_name = new_name.strip()
|
|
||||||
if new_name != self._selected_body:
|
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:
|
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
|
return
|
||||||
|
|
||||||
idx = self.celestial_bodies.index(self._selected_body)
|
idx = self.celestial_bodies.index(self.selected_body)
|
||||||
old_name = self.celestial_bodies[idx]
|
|
||||||
self.celestial_bodies[idx] = new_name
|
self.celestial_bodies[idx] = new_name
|
||||||
self.config_service.update_celestial_body(old_name, new_name)
|
self.config_service.update_celestial_body(self.selected_body, new_name)
|
||||||
self._update_list()
|
self.bodies_listbox.delete(0, 'end')
|
||||||
QMessageBox.information(self, "Успех", f"Объект переименован в '{new_name}'")
|
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)
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
self.config_service = config_service
|
self.config_service = config_service
|
||||||
self.setWindowTitle("Управление оборудованием")
|
|
||||||
self.setMinimumSize(700, 500)
|
|
||||||
self.resize(800, 550)
|
|
||||||
|
|
||||||
# Загружаем данные
|
|
||||||
self.cameras = self.config_service.get_cameras()
|
self.cameras = self.config_service.get_cameras()
|
||||||
self.lenses = self.config_service.get_lenses()
|
self.lenses = self.config_service.get_lenses()
|
||||||
self.telescopes = self.config_service.get_telescopes()
|
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._create_ui()
|
||||||
self._update_cameras_list()
|
self._center_window()
|
||||||
self._update_lenses_list()
|
|
||||||
self._update_telescopes_list()
|
|
||||||
|
|
||||||
def _create_ui(self):
|
def _create_ui(self):
|
||||||
layout = QVBoxLayout(self)
|
# Notebook for tabs
|
||||||
layout.setSpacing(15)
|
notebook = ttk.Notebook(self)
|
||||||
layout.setContentsMargins(20, 20, 20, 20)
|
notebook.pack(fill='both', expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
# Заголовок
|
# Tab 1: Cameras
|
||||||
title_label = QLabel("Управление оборудованием")
|
cameras_frame = ttk.Frame(notebook)
|
||||||
title_font = QFont()
|
notebook.add(cameras_frame, text="Cameras")
|
||||||
title_font.setPointSize(16)
|
self._create_cameras_tab(cameras_frame)
|
||||||
title_font.setBold(True)
|
|
||||||
title_label.setFont(title_font)
|
|
||||||
title_label.setAlignment(Qt.AlignCenter)
|
|
||||||
layout.addWidget(title_label)
|
|
||||||
|
|
||||||
# Используем QTabWidget для трёх вкладок
|
# Tab 2: Lenses
|
||||||
tab_widget = QTabWidget()
|
lenses_frame = ttk.Frame(notebook)
|
||||||
|
notebook.add(lenses_frame, text="Lenses")
|
||||||
|
self._create_lenses_tab(lenses_frame)
|
||||||
|
|
||||||
# Вкладка: Камеры
|
# Tab 3: Telescopes
|
||||||
cameras_tab = self._create_cameras_tab()
|
telescopes_frame = ttk.Frame(notebook)
|
||||||
tab_widget.addTab(cameras_tab, "📷 Камеры")
|
notebook.add(telescopes_frame, text="Telescopes")
|
||||||
|
self._create_telescopes_tab(telescopes_frame)
|
||||||
|
|
||||||
# Вкладка: Объективы
|
# Close button
|
||||||
lenses_tab = self._create_lenses_tab()
|
btn_frame = ttk.Frame(self)
|
||||||
tab_widget.addTab(lenses_tab, "🔭 Объективы")
|
btn_frame.pack(pady=10)
|
||||||
|
ttk.Button(btn_frame, text="Close", command=self.destroy).pack()
|
||||||
|
|
||||||
# Вкладка: Телескопы
|
def _center_window(self):
|
||||||
telescopes_tab = self._create_telescopes_tab()
|
self.update_idletasks()
|
||||||
tab_widget.addTab(telescopes_tab, "🪐 Телескопы")
|
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)
|
||||||
|
|
||||||
# Кнопка закрытия
|
scrollbar = ttk.Scrollbar(list_frame)
|
||||||
close_btn = QPushButton("Закрыть")
|
scrollbar.pack(side='right', fill='y')
|
||||||
close_btn.clicked.connect(self.accept)
|
|
||||||
close_layout = QHBoxLayout()
|
|
||||||
close_layout.addStretch()
|
|
||||||
close_layout.addWidget(close_btn)
|
|
||||||
layout.addLayout(close_layout)
|
|
||||||
|
|
||||||
def _create_cameras_tab(self) -> QWidget:
|
self.cameras_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set,
|
||||||
"""Создаёт вкладку с камерами"""
|
bg='#2d2d2d', fg='#e0e0e0',
|
||||||
tab = QWidget()
|
selectbackground='#4CAF50', selectforeground='white',
|
||||||
layout = QVBoxLayout(tab)
|
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:
|
for camera in self.cameras:
|
||||||
self.cameras_list.addItem(camera)
|
self.cameras_listbox.insert('end', camera)
|
||||||
self._selected_camera = None
|
|
||||||
self.remove_camera_btn.setEnabled(False)
|
|
||||||
|
|
||||||
def _select_camera(self, camera: str):
|
self.cameras_listbox.bind('<<ListboxSelect>>', self._on_camera_select)
|
||||||
self._selected_camera = camera
|
|
||||||
self.remove_camera_btn.setEnabled(True)
|
# Buttons
|
||||||
self.lenses_list.clearSelection()
|
btn_frame = ttk.Frame(parent)
|
||||||
self.telescopes_list.clearSelection()
|
btn_frame.pack(pady=10)
|
||||||
self._selected_lens = None
|
|
||||||
self._selected_telescope = None
|
ttk.Button(btn_frame, text="Add Camera", command=self._add_camera).pack(side='left', padx=5)
|
||||||
self.remove_lens_btn.setEnabled(False)
|
self.remove_camera_btn = ttk.Button(btn_frame, text="Remove", command=self._remove_camera, state='disabled')
|
||||||
self.remove_telescope_btn.setEnabled(False)
|
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):
|
def _add_camera(self):
|
||||||
name, ok = QInputDialog.getText(self, "Добавить камеру", "Введите название камеры:")
|
dialog = tk.Toplevel(self)
|
||||||
if ok and name and name.strip():
|
dialog.title("Add Camera")
|
||||||
new_name = name.strip()
|
dialog.geometry("350x120")
|
||||||
if new_name in self.cameras:
|
dialog.transient(self)
|
||||||
QMessageBox.warning(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
|
return
|
||||||
self.cameras.append(new_name)
|
self.cameras.append(new_camera)
|
||||||
self.config_service.add_camera(new_name)
|
self.config_service.add_camera(new_camera)
|
||||||
self._update_cameras_list()
|
self.cameras_listbox.insert('end', new_camera)
|
||||||
QMessageBox.information(self, "Успех", f"Камера '{new_name}' добавлена")
|
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):
|
def _remove_camera(self):
|
||||||
if hasattr(self, '_selected_camera') and self._selected_camera:
|
if self.selected_camera:
|
||||||
reply = QMessageBox.question(self, "Подтверждение",
|
reply = messagebox.askyesno("Remove Camera", f"Remove '{self.selected_camera}'?", parent=self)
|
||||||
f"Удалить камеру '{self._selected_camera}'?",
|
if reply:
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
self.cameras.remove(self.selected_camera)
|
||||||
if reply == QMessageBox.Yes:
|
self.config_service.remove_camera(self.selected_camera)
|
||||||
self.cameras.remove(self._selected_camera)
|
self.cameras_listbox.delete(0, 'end')
|
||||||
self.config_service.remove_camera(self._selected_camera)
|
for camera in self.cameras:
|
||||||
self._update_cameras_list()
|
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:
|
for lens in self.lenses:
|
||||||
self.lenses_list.addItem(lens)
|
self.lenses_listbox.insert('end', lens)
|
||||||
self._selected_lens = None
|
|
||||||
self.remove_lens_btn.setEnabled(False)
|
|
||||||
|
|
||||||
def _select_lens(self, lens: str):
|
self.lenses_listbox.bind('<<ListboxSelect>>', self._on_lens_select)
|
||||||
self._selected_lens = lens
|
|
||||||
self.remove_lens_btn.setEnabled(True)
|
# Buttons
|
||||||
self.cameras_list.clearSelection()
|
btn_frame = ttk.Frame(parent)
|
||||||
self.telescopes_list.clearSelection()
|
btn_frame.pack(pady=10)
|
||||||
self._selected_camera = None
|
|
||||||
self._selected_telescope = None
|
ttk.Button(btn_frame, text="Add Lens", command=self._add_lens).pack(side='left', padx=5)
|
||||||
self.remove_camera_btn.setEnabled(False)
|
self.remove_lens_btn = ttk.Button(btn_frame, text="Remove", command=self._remove_lens, state='disabled')
|
||||||
self.remove_telescope_btn.setEnabled(False)
|
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):
|
def _add_lens(self):
|
||||||
name, ok = QInputDialog.getText(self, "Добавить объектив", "Введите название объектива:")
|
dialog = tk.Toplevel(self)
|
||||||
if ok and name and name.strip():
|
dialog.title("Add Lens")
|
||||||
new_name = name.strip()
|
dialog.geometry("350x120")
|
||||||
if new_name in self.lenses:
|
dialog.transient(self)
|
||||||
QMessageBox.warning(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
|
return
|
||||||
self.lenses.append(new_name)
|
self.lenses.append(new_lens)
|
||||||
self.config_service.add_lens(new_name)
|
self.config_service.add_lens(new_lens)
|
||||||
self._update_lenses_list()
|
self.lenses_listbox.insert('end', new_lens)
|
||||||
QMessageBox.information(self, "Успех", f"Объектив '{new_name}' добавлен")
|
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):
|
def _remove_lens(self):
|
||||||
if hasattr(self, '_selected_lens') and self._selected_lens:
|
if self.selected_lens:
|
||||||
reply = QMessageBox.question(self, "Подтверждение",
|
reply = messagebox.askyesno("Remove Lens", f"Remove '{self.selected_lens}'?", parent=self)
|
||||||
f"Удалить объектив '{self._selected_lens}'?",
|
if reply:
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
self.lenses.remove(self.selected_lens)
|
||||||
if reply == QMessageBox.Yes:
|
self.config_service.remove_lens(self.selected_lens)
|
||||||
self.lenses.remove(self._selected_lens)
|
self.lenses_listbox.delete(0, 'end')
|
||||||
self.config_service.remove_lens(self._selected_lens)
|
for lens in self.lenses:
|
||||||
self._update_lenses_list()
|
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):
|
def _edit_lens(self):
|
||||||
if hasattr(self, '_selected_lens') and self._selected_lens:
|
if self.selected_lens:
|
||||||
new_name, ok = QInputDialog.getText(self, "Редактировать объектив",
|
dialog = tk.Toplevel(self)
|
||||||
f"Изменить '{self._selected_lens}' на:",
|
dialog.title("Edit Lens")
|
||||||
text=self._selected_lens)
|
dialog.geometry("350x120")
|
||||||
if ok and new_name and new_name.strip():
|
dialog.transient(self)
|
||||||
new_name = new_name.strip()
|
dialog.grab_set()
|
||||||
if new_name != self._selected_lens:
|
|
||||||
|
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:
|
if new_name in self.lenses:
|
||||||
QMessageBox.warning(self, "Ошибка", "Такой объектив уже существует!")
|
messagebox.showerror("Error", "Lens already exists!", parent=dialog)
|
||||||
return
|
return
|
||||||
idx = self.lenses.index(self._selected_lens)
|
idx = self.lenses.index(self.selected_lens)
|
||||||
self.lenses[idx] = new_name
|
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.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:
|
for telescope in self.telescopes:
|
||||||
self.telescopes_list.addItem(telescope)
|
self.telescopes_listbox.insert('end', telescope)
|
||||||
self._selected_telescope = None
|
|
||||||
self.remove_telescope_btn.setEnabled(False)
|
|
||||||
|
|
||||||
def _select_telescope(self, telescope: str):
|
self.telescopes_listbox.bind('<<ListboxSelect>>', self._on_telescope_select)
|
||||||
self._selected_telescope = telescope
|
|
||||||
self.remove_telescope_btn.setEnabled(True)
|
# Buttons
|
||||||
self.cameras_list.clearSelection()
|
btn_frame = ttk.Frame(parent)
|
||||||
self.lenses_list.clearSelection()
|
btn_frame.pack(pady=10)
|
||||||
self._selected_camera = None
|
|
||||||
self._selected_lens = None
|
ttk.Button(btn_frame, text="Add Telescope", command=self._add_telescope).pack(side='left', padx=5)
|
||||||
self.remove_camera_btn.setEnabled(False)
|
self.remove_telescope_btn = ttk.Button(btn_frame, text="Remove", command=self._remove_telescope, state='disabled')
|
||||||
self.remove_lens_btn.setEnabled(False)
|
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):
|
def _add_telescope(self):
|
||||||
"""Добавляет телескоп с указанием диафрагмы (фиксированной)"""
|
dialog = tk.Toplevel(self)
|
||||||
dialog = QDialog(self)
|
dialog.title("Add Telescope")
|
||||||
dialog.setWindowTitle("Добавить телескоп")
|
dialog.geometry("400x320")
|
||||||
dialog.setMinimumWidth(400)
|
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()
|
ttk.Label(dialog, text="Focal length (mm):").pack(pady=(10, 5))
|
||||||
name_edit.setPlaceholderText("例如: Celestron 8\"")
|
focal_entry = ttk.Entry(dialog, width=20)
|
||||||
form_layout.addRow("Название:", name_edit)
|
focal_entry.insert(0, "1000")
|
||||||
|
focal_entry.pack()
|
||||||
|
|
||||||
aperture_spin = QDoubleSpinBox()
|
ttk.Label(dialog, text="Diameter (mm):").pack(pady=(10, 5))
|
||||||
aperture_spin.setRange(0.5, 20.0)
|
diameter_entry = ttk.Entry(dialog, width=20)
|
||||||
aperture_spin.setSingleStep(0.1)
|
diameter_entry.insert(0, "200")
|
||||||
aperture_spin.setValue(5.0)
|
diameter_entry.pack()
|
||||||
aperture_spin.setSuffix(" (f/)")
|
|
||||||
form_layout.addRow("Диафрагма (f/):", aperture_spin)
|
|
||||||
|
|
||||||
focal_spin = QSpinBox()
|
def save():
|
||||||
focal_spin.setRange(100, 5000)
|
name = name_entry.get().strip()
|
||||||
focal_spin.setSingleStep(50)
|
if not name:
|
||||||
focal_spin.setSuffix(" мм")
|
messagebox.showerror("Error", "Please enter telescope name!", parent=dialog)
|
||||||
form_layout.addRow("Фокусное расстояние:", focal_spin)
|
return
|
||||||
|
|
||||||
diameter_spin = QSpinBox()
|
try:
|
||||||
diameter_spin.setRange(50, 500)
|
aperture = float(aperture_entry.get())
|
||||||
diameter_spin.setSingleStep(10)
|
focal = int(focal_entry.get())
|
||||||
diameter_spin.setSuffix(" мм")
|
diameter = int(diameter_entry.get())
|
||||||
form_layout.addRow("Диаметр объектива:", diameter_spin)
|
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)
|
|
||||||
|
|
||||||
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.telescopes.append(telescope_info)
|
||||||
self.config_service.add_telescope(telescope_info)
|
self.config_service.add_telescope(telescope_info)
|
||||||
self._update_telescopes_list()
|
self.telescopes_listbox.insert('end', telescope_info)
|
||||||
QMessageBox.information(self, "Успех", f"Телескоп '{name}' добавлен")
|
dialog.destroy()
|
||||||
|
|
||||||
|
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):
|
def _remove_telescope(self):
|
||||||
if hasattr(self, '_selected_telescope') and self._selected_telescope:
|
if self.selected_telescope:
|
||||||
reply = QMessageBox.question(self, "Подтверждение",
|
reply = messagebox.askyesno("Remove Telescope", f"Remove '{self.selected_telescope}'?", parent=self)
|
||||||
f"Удалить телескоп '{self._selected_telescope}'?",
|
if reply:
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
self.telescopes.remove(self.selected_telescope)
|
||||||
if reply == QMessageBox.Yes:
|
self.config_service.remove_telescope(self.selected_telescope)
|
||||||
self.telescopes.remove(self._selected_telescope)
|
self.telescopes_listbox.delete(0, 'end')
|
||||||
self.config_service.remove_telescope(self._selected_telescope)
|
for telescope in self.telescopes:
|
||||||
self._update_telescopes_list()
|
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):
|
def _edit_telescope(self):
|
||||||
if hasattr(self, '_selected_telescope') and self._selected_telescope:
|
if self.selected_telescope:
|
||||||
new_name, ok = QInputDialog.getText(self, "Редактировать телескоп",
|
import re
|
||||||
f"Изменить '{self._selected_telescope}' на:",
|
match = re.search(r'(.+?) \(f/([\d\.]+), F=(\d+)mm, D=(\d+)mm\)', self.selected_telescope)
|
||||||
text=self._selected_telescope)
|
if match:
|
||||||
if ok and new_name and new_name.strip():
|
old_name = match.group(1)
|
||||||
new_name = new_name.strip()
|
old_aperture = match.group(2)
|
||||||
if new_name != self._selected_telescope:
|
old_focal = match.group(3)
|
||||||
if new_name in self.telescopes:
|
old_diameter = match.group(4)
|
||||||
QMessageBox.warning(self, "Ошибка", "Такой телескоп уже существует!")
|
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
|
return
|
||||||
idx = self.telescopes.index(self._selected_telescope)
|
|
||||||
self.telescopes[idx] = new_name
|
new_info = f"{new_name} (f/{aperture}, F={focal}mm, D={diameter}mm)"
|
||||||
self.config_service.remove_telescope(self._selected_telescope)
|
if new_info != self.selected_telescope and new_info in self.telescopes:
|
||||||
self.config_service.add_telescope(new_name)
|
messagebox.showerror("Error", "Telescope already exists!", parent=dialog)
|
||||||
self._update_telescopes_list()
|
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,
|
import tkinter as tk
|
||||||
QPushButton, QScrollArea
|
from tkinter import ttk
|
||||||
)
|
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from PySide6.QtGui import QFont
|
|
||||||
|
|
||||||
|
|
||||||
class InstructionsDialog(QDialog):
|
class InstructionsDialog(tk.Toplevel):
|
||||||
"""Диалог с подробной инструкцией пользователя"""
|
"""Диалог с подробной инструкцией пользователя на английском"""
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
self.setWindowTitle("Инструкция по использованию")
|
self.title("Instructions")
|
||||||
self.setMinimumSize(700, 500)
|
self.geometry("750x600")
|
||||||
self.resize(750, 550)
|
self.minsize(700, 500)
|
||||||
|
self.transient(parent)
|
||||||
|
self.grab_set()
|
||||||
|
|
||||||
self._create_ui()
|
self._create_ui()
|
||||||
|
self._center_window()
|
||||||
|
|
||||||
def _create_ui(self):
|
def _create_ui(self):
|
||||||
"""Создаёт интерфейс диалога"""
|
# Main frame
|
||||||
layout = QVBoxLayout(self)
|
main_frame = ttk.Frame(self, padding="15")
|
||||||
layout.setSpacing(10)
|
main_frame.pack(fill='both', expand=True)
|
||||||
layout.setContentsMargins(15, 15, 15, 15)
|
|
||||||
|
|
||||||
# Заголовок
|
# Title
|
||||||
title_label = QLabel("Astro Session Watcher - Руководство пользователя")
|
ttk.Label(main_frame, text="Astro Session Watcher - User Guide", font=('Segoe UI', 16, 'bold')).pack(pady=(0, 10))
|
||||||
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)
|
|
||||||
|
|
||||||
# Текст инструкции
|
# Text with scrollbar
|
||||||
text_edit = QTextEdit()
|
text_frame = ttk.Frame(main_frame)
|
||||||
text_edit.setReadOnly(True)
|
text_frame.pack(fill='both', expand=True)
|
||||||
text_edit.setFont(QFont("Consolas", 10))
|
|
||||||
|
|
||||||
instructions = """
|
scrollbar = ttk.Scrollbar(text_frame)
|
||||||
======================= ASTRO SESSION WATCHER =======================
|
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 или аналогичное ПО,
|
The application automatically tracks new photos in the selected folder,
|
||||||
все фотографии сохраняются в одну папку. Astro Session Watcher помогает:
|
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. Вы выбираете папку, куда камера сохраняет снимки
|
🚀 HOW IT WORKS?
|
||||||
2. Приложение создает папку сессии: "AstroSession_ГГГГ-ММ-ДД"
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
3. Каждый объект съемки получает свою подпапку внутри папки сессии
|
|
||||||
4. Когда вы меняете объект (нажимаете "Новая цель"), все накопленные
|
|
||||||
в папке наблюдения файлы автоматически ПЕРЕМЕЩАЮТСЯ в папку предыдущего объекта
|
|
||||||
5. При завершении сессии оставшиеся файлы также перемещаются в папку последнего объекта
|
|
||||||
|
|
||||||
📝 ПОШАГОВАЯ ИНСТРУКЦИЯ
|
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. STARTING A SESSION
|
||||||
2. Выберите камеру и объектив из выпадающих списков
|
───────────────────────────────────────────────────────────────────
|
||||||
3. Введите название цели (или выберите из списка небесных тел)
|
|
||||||
4. Нажмите ▶ "Начать отслеживание"
|
|
||||||
|
|
||||||
✅ После запуска:
|
1. Click "Browse" and select the folder where your camera saves photos
|
||||||
• Статус изменится на "● ON AIR" с мигающим красным текстом
|
2. Select camera and lens/telescope from dropdowns
|
||||||
• Кнопка "Новая цель" начнет мигать красным контуром
|
3. Enter target name (or select from celestial bodies list)
|
||||||
• В папке наблюдения создастся папка "AstroSession_дата"
|
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)
|
1. Click "New Target" button (or Ctrl+Shift+N)
|
||||||
2. Введите название нового объекта
|
2. Enter new target name
|
||||||
3. Приложение автоматически:
|
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)
|
1. Click "■ Stop" (or Ctrl+X)
|
||||||
2. Приложение:
|
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 + O → Select watch folder
|
||||||
Ctrl + E → Управление оборудованием
|
Ctrl + E → Equipment management
|
||||||
Ctrl + B → Управление небесными телами
|
Ctrl + B → Celestial bodies management
|
||||||
Ctrl + S → Начать сессию
|
Ctrl + S → Start session
|
||||||
Ctrl + X → Остановить сессию
|
Ctrl + X → Stop session
|
||||||
Ctrl + F → Открыть папку текущей сессии
|
Ctrl + F → Open current session folder
|
||||||
Ctrl + Shift+N → Создать новый объект
|
Ctrl + Shift+N → Create new target
|
||||||
F1 → О программе
|
F1 → About
|
||||||
F2 → Эта инструкция
|
F2 → This instruction
|
||||||
|
|
||||||
🔧 ФАЙЛЫ НАСТРОЕК
|
🔧 CONFIG FILES
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
📄 astro_settings.json ← камеры, объективы, последняя папка
|
📄 astro_settings.json ← cameras, lenses, last folder
|
||||||
📄 celestial_bodies.json ← список небесных тел
|
📄 celestial_bodies.json ← list of celestial bodies
|
||||||
|
|
||||||
Все файлы хранятся в папке с программой. Вы можете редактировать их вручную.
|
All files are stored in the program folder. You can edit them manually.
|
||||||
|
|
||||||
📧 ТЕХНИЧЕСКАЯ ПОДДЕРЖКА
|
📧 TECHNICAL SUPPORT
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
Text me: norvicdev@gmail.com
|
||||||
Разработчик: Vic Sergeev
|
Developer: Vic Sergeev
|
||||||
Версия: 0.3.0-alpha
|
Version: 0.4.0-alpha
|
||||||
|
|
||||||
При обнаружении ошибок или для предложений по улучшению:
|
|
||||||
• Сообщите разработчику
|
|
||||||
• Приложите файлы логов (SessionLog.txt, ObjectLog.txt)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
text_edit.setText(instructions)
|
self.text_widget.insert('1.0', instructions)
|
||||||
layout.addWidget(text_edit)
|
self.text_widget.config(state='disabled')
|
||||||
|
|
||||||
# Кнопка закрытия
|
# Close button
|
||||||
close_layout = QHBoxLayout()
|
ttk.Button(main_frame, text="Close", command=self.destroy).pack(pady=10)
|
||||||
close_layout.addStretch()
|
|
||||||
|
|
||||||
close_btn = QPushButton("Закрыть")
|
def _center_window(self):
|
||||||
close_btn.clicked.connect(self.accept)
|
self.update_idletasks()
|
||||||
close_layout.addWidget(close_btn)
|
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)
|
||||||
layout.addLayout(close_layout)
|
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