212 lines
8 KiB
Python
212 lines
8 KiB
Python
|
|
"""Главное окно приложения"""
|
|||
|
|
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()
|