212 lines
No EOL
8 KiB
Python
212 lines
No EOL
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() |