This commit is contained in:
Vic Sergeev 2026-07-04 16:04:48 +03:00
commit a385308f38
44 changed files with 1614 additions and 0 deletions

212
app/ui/main_window.py Normal file
View file

@ -0,0 +1,212 @@
"""Главное окно приложения"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import json
import zipfile
import os
from datetime import datetime
from app.config import Config
from app.database import Database
from app.auth import Auth
from app.ui.sidebar import Sidebar
from app.ui.note_editor import NoteEditor
from app.ui.dialogs import PasswordDialog
class MainWindow(tk.Tk):
"""Главное окно приложения"""
def __init__(self):
super().__init__()
self.title(Config.APP_NAME)
self.geometry(f"{Config.WINDOW_WIDTH}x{Config.WINDOW_HEIGHT}")
self.minsize(Config.MIN_WINDOW_WIDTH, Config.MIN_WINDOW_HEIGHT)
self.configure(bg=Config.COLORS['bg'])
# Инициализация компонентов
self.db = Database()
self.auth = Auth(self.db)
# Проверка пароля
if not self.check_auth():
self.destroy()
return
# Создание интерфейса
self.create_menu()
self.create_widgets()
# Загрузка заметок для текущей даты
self.sidebar.refresh_notes()
# Привязка закрытия окна
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def check_auth(self):
"""Проверка аутентификации"""
if self.auth.is_password_set():
if self.auth.ask_password_on_startup():
dialog = PasswordDialog(self, self.auth, mode='check')
self.wait_window(dialog)
return dialog.result
else:
# Предлагаем создать пароль
dialog = PasswordDialog(self, self.auth, mode='set')
self.wait_window(dialog)
return dialog.result
return True
def create_menu(self):
"""Создание меню"""
menubar = tk.Menu(self)
self.config(menu=menubar)
# Меню Файл
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Файл", menu=file_menu)
file_menu.add_command(label="Экспорт в ZIP", command=self.export_to_zip, accelerator="Ctrl+E")
file_menu.add_command(label="Импорт из ZIP", command=self.import_from_zip, accelerator="Ctrl+I")
file_menu.add_separator()
file_menu.add_command(label="Выход", command=self.on_closing, accelerator="Ctrl+Q")
# Меню Настройки
settings_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Настройки", menu=settings_menu)
settings_menu.add_command(label="Изменить мастер-пароль", command=self.change_password)
# Горячие клавиши
self.bind('<Control-e>', lambda e: self.export_to_zip())
self.bind('<Control-i>', lambda e: self.import_from_zip())
self.bind('<Control-q>', lambda e: self.on_closing())
def create_widgets(self):
"""Создание виджетов интерфейса"""
# Основной контейнер
main_container = tk.Frame(self, bg=Config.COLORS['bg'])
main_container.pack(fill='both', expand=True)
# Боковая панель
self.sidebar = Sidebar(
main_container,
self.db,
on_note_select=self.on_note_selected,
on_note_delete=self.on_note_deleted
)
# Разделитель
separator = tk.Frame(main_container, width=2, bg=Config.COLORS['separator'])
separator.pack(side='left', fill='y')
# Редактор заметок
self.editor = NoteEditor(
main_container,
self.db,
on_note_saved=self.on_note_saved,
on_note_deleted=self.on_note_deleted,
on_new_note=self.on_new_note_created
)
self.editor.pack(side='left', fill='both', expand=True)
def on_note_selected(self, note):
"""Обработчик выбора заметки"""
self.editor.show_note(note)
def on_note_saved(self, note):
"""Обработчик сохранения заметки"""
self.sidebar.refresh_notes()
def on_note_deleted(self):
"""Обработчик удаления заметки"""
self.sidebar.refresh_notes()
def on_new_note_created(self):
"""Обработчик создания новой заметки"""
pass
def export_to_zip(self):
"""Экспорт данных в ZIP архив"""
filename = filedialog.asksaveasfilename(
defaultextension=".zip",
filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")],
initialfile=f"captains_log_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
)
if filename:
try:
# Экспортируем данные
data = self.db.export_data()
# Создаем временный JSON файл
temp_json = os.path.join(os.path.dirname(filename), 'temp_export.json')
with open(temp_json, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# Создаем ZIP архив
with zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.write(temp_json, 'captains_log_backup.json')
# Удаляем временный файл
os.remove(temp_json)
messagebox.showinfo("Успех", f"Данные экспортированы в:\n{filename}")
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось экспортировать данные:\n{str(e)}")
def import_from_zip(self):
"""Импорт данных из ZIP архива"""
filename = filedialog.askopenfilename(
filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")]
)
if filename:
# Подтверждение
if not messagebox.askyesno(
"Подтверждение",
"Импорт данных заменит все существующие заметки.\nПродолжить?"
):
return
try:
# Извлекаем из ZIP
with zipfile.ZipFile(filename, 'r') as zipf:
temp_json = os.path.join(os.path.dirname(filename), 'temp_import.json')
zipf.extract('captains_log_backup.json', os.path.dirname(filename))
# Читаем JSON
with open(temp_json, 'r', encoding='utf-8') as f:
data = json.load(f)
# Импортируем данные
self.db.import_data(data)
# Удаляем временный файл
os.remove(temp_json)
# Обновляем интерфейс
self.sidebar.refresh_notes()
self.editor.show_empty_state()
messagebox.showinfo("Успех", "Данные успешно импортированы")
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось импортировать данные:\n{str(e)}")
def change_password(self):
"""Изменение мастер-пароля"""
if self.auth.is_password_set():
dialog = PasswordDialog(self, self.auth, mode='check')
self.wait_window(dialog)
if dialog.result:
dialog = PasswordDialog(self, self.auth, mode='set')
self.wait_window(dialog)
else:
dialog = PasswordDialog(self, self.auth, mode='set')
self.wait_window(dialog)
def on_closing(self):
"""Обработчик закрытия окна"""
self.destroy()