297 lines
10 KiB
Python
297 lines
10 KiB
Python
|
|
"""Редактор заметок"""
|
|||
|
|
import tkinter as tk
|
|||
|
|
from tkinter import ttk, messagebox
|
|||
|
|
from datetime import datetime
|
|||
|
|
from app.config import Config
|
|||
|
|
from app.ui.dialogs import ConfirmDialog
|
|||
|
|
|
|||
|
|
class NoteEditor(tk.Frame):
|
|||
|
|
"""Правая панель с редактором заметок"""
|
|||
|
|
|
|||
|
|
def __init__(self, parent, db, on_note_saved=None, on_note_deleted=None, on_new_note=None):
|
|||
|
|
super().__init__(parent, bg=Config.COLORS['editor_bg'])
|
|||
|
|
self.db = db
|
|||
|
|
self.on_note_saved = on_note_saved
|
|||
|
|
self.on_note_deleted = on_note_deleted
|
|||
|
|
self.on_new_note = on_new_note
|
|||
|
|
|
|||
|
|
self.current_note = None
|
|||
|
|
self.is_new_note = False
|
|||
|
|
self.is_editing = False
|
|||
|
|
|
|||
|
|
self.create_widgets()
|
|||
|
|
self.show_empty_state()
|
|||
|
|
|
|||
|
|
def create_widgets(self):
|
|||
|
|
# Панель инструментов
|
|||
|
|
self.toolbar = tk.Frame(self, bg=Config.COLORS['sidebar_bg'], height=40)
|
|||
|
|
self.toolbar.pack(fill='x')
|
|||
|
|
self.toolbar.pack_propagate(False)
|
|||
|
|
|
|||
|
|
# Дата и время (левый край)
|
|||
|
|
self.date_label = tk.Label(
|
|||
|
|
self.toolbar,
|
|||
|
|
text="",
|
|||
|
|
bg=Config.COLORS['sidebar_bg'],
|
|||
|
|
fg=Config.COLORS['fg'],
|
|||
|
|
font=('Arial', 11)
|
|||
|
|
)
|
|||
|
|
self.date_label.pack(side='left', padx=10)
|
|||
|
|
|
|||
|
|
# Кнопки (правый край)
|
|||
|
|
buttons_frame = tk.Frame(self.toolbar, bg=Config.COLORS['sidebar_bg'])
|
|||
|
|
buttons_frame.pack(side='right', padx=5)
|
|||
|
|
|
|||
|
|
# Кнопка сохранения
|
|||
|
|
self.save_btn = tk.Button(
|
|||
|
|
buttons_frame,
|
|||
|
|
text="💾",
|
|||
|
|
command=self.save_note,
|
|||
|
|
bg=Config.COLORS['button_bg'],
|
|||
|
|
fg=Config.COLORS['fg'],
|
|||
|
|
relief='flat',
|
|||
|
|
font=('Arial', 14),
|
|||
|
|
cursor='hand2',
|
|||
|
|
width=2
|
|||
|
|
)
|
|||
|
|
self.save_btn.pack(side='left', padx=2)
|
|||
|
|
|
|||
|
|
# Кнопка удаления
|
|||
|
|
self.delete_btn = tk.Button(
|
|||
|
|
buttons_frame,
|
|||
|
|
text="🗑",
|
|||
|
|
command=self.delete_current_note,
|
|||
|
|
bg=Config.COLORS['button_bg'],
|
|||
|
|
fg=Config.COLORS['fg'],
|
|||
|
|
relief='flat',
|
|||
|
|
font=('Arial', 14),
|
|||
|
|
cursor='hand2',
|
|||
|
|
width=2
|
|||
|
|
)
|
|||
|
|
self.delete_btn.pack(side='left', padx=2)
|
|||
|
|
|
|||
|
|
# Кнопка создания новой заметки
|
|||
|
|
self.new_btn = tk.Button(
|
|||
|
|
buttons_frame,
|
|||
|
|
text="+",
|
|||
|
|
command=self.create_new_note,
|
|||
|
|
bg=Config.COLORS['accent'],
|
|||
|
|
fg=Config.COLORS['fg'],
|
|||
|
|
relief='flat',
|
|||
|
|
font=('Arial', 14, 'bold'),
|
|||
|
|
cursor='hand2',
|
|||
|
|
width=2
|
|||
|
|
)
|
|||
|
|
self.new_btn.pack(side='left', padx=2)
|
|||
|
|
|
|||
|
|
# Скрываем кнопки сохранения и удаления по умолчанию
|
|||
|
|
self.hide_editor_buttons()
|
|||
|
|
|
|||
|
|
# Область контента
|
|||
|
|
self.content_frame = tk.Frame(self, bg=Config.COLORS['editor_bg'])
|
|||
|
|
self.content_frame.pack(fill='both', expand=True, padx=10, pady=10)
|
|||
|
|
|
|||
|
|
# Текстовое поле для редактирования
|
|||
|
|
self.text_widget = tk.Text(
|
|||
|
|
self.content_frame,
|
|||
|
|
bg=Config.COLORS['text_bg'],
|
|||
|
|
fg=Config.COLORS['text_fg'],
|
|||
|
|
font=('Arial', 12),
|
|||
|
|
wrap='word',
|
|||
|
|
relief='flat',
|
|||
|
|
padx=10,
|
|||
|
|
pady=10,
|
|||
|
|
insertbackground=Config.COLORS['fg']
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Scrollbar для текстового поля
|
|||
|
|
self.text_scrollbar = ttk.Scrollbar(
|
|||
|
|
self.content_frame,
|
|||
|
|
orient='vertical',
|
|||
|
|
command=self.text_widget.yview
|
|||
|
|
)
|
|||
|
|
self.text_widget.configure(yscrollcommand=self.text_scrollbar.set)
|
|||
|
|
|
|||
|
|
# Метка для пустого состояния
|
|||
|
|
self.empty_label = tk.Label(
|
|||
|
|
self.content_frame,
|
|||
|
|
text="Выберите заметку из списка\nили создайте новую",
|
|||
|
|
bg=Config.COLORS['editor_bg'],
|
|||
|
|
fg=Config.COLORS['fg'],
|
|||
|
|
font=('Arial', 14),
|
|||
|
|
justify='center'
|
|||
|
|
)
|
|||
|
|
self.empty_label.pack(expand=True)
|
|||
|
|
|
|||
|
|
# Привязываем события изменения текста
|
|||
|
|
self.text_widget.bind('<<Modified>>', self.on_text_modified)
|
|||
|
|
|
|||
|
|
def show_editor_buttons(self):
|
|||
|
|
"""Показать кнопки редактора"""
|
|||
|
|
self.save_btn.pack(side='left', padx=2)
|
|||
|
|
self.delete_btn.pack(side='left', padx=2)
|
|||
|
|
|
|||
|
|
def hide_editor_buttons(self):
|
|||
|
|
"""Скрыть кнопки редактора"""
|
|||
|
|
self.save_btn.pack_forget()
|
|||
|
|
self.delete_btn.pack_forget()
|
|||
|
|
|
|||
|
|
def show_empty_state(self):
|
|||
|
|
"""Показать пустое состояние"""
|
|||
|
|
self.text_widget.pack_forget()
|
|||
|
|
self.text_scrollbar.pack_forget()
|
|||
|
|
self.empty_label.pack(expand=True)
|
|||
|
|
self.date_label.config(text="")
|
|||
|
|
self.hide_editor_buttons()
|
|||
|
|
self.current_note = None
|
|||
|
|
self.is_new_note = False
|
|||
|
|
self.is_editing = False
|
|||
|
|
|
|||
|
|
def show_note(self, note):
|
|||
|
|
"""Показать существующую заметку"""
|
|||
|
|
self.current_note = note
|
|||
|
|
self.is_new_note = False
|
|||
|
|
self.is_editing = False
|
|||
|
|
|
|||
|
|
# Скрываем пустую метку
|
|||
|
|
self.empty_label.pack_forget()
|
|||
|
|
|
|||
|
|
# Показываем текстовое поле
|
|||
|
|
self.text_widget.pack(side='left', fill='both', expand=True)
|
|||
|
|
self.text_scrollbar.pack(side='right', fill='y')
|
|||
|
|
|
|||
|
|
# Устанавливаем дату
|
|||
|
|
created_at = datetime.strptime(
|
|||
|
|
note['created_at'], '%Y-%m-%d %H:%M:%S'
|
|||
|
|
)
|
|||
|
|
self.date_label.config(
|
|||
|
|
text=created_at.strftime('%d.%m.%Y %H:%M')
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Очищаем и вставляем текст
|
|||
|
|
self.text_widget.delete('1.0', 'end')
|
|||
|
|
self.text_widget.insert('1.0', note.get('content', ''))
|
|||
|
|
self.text_widget.edit_modified(False)
|
|||
|
|
|
|||
|
|
# Показываем кнопки
|
|||
|
|
self.show_editor_buttons()
|
|||
|
|
|
|||
|
|
# Делаем поле только для чтения
|
|||
|
|
self.text_widget.configure(state='disabled')
|
|||
|
|
|
|||
|
|
# Привязываем клик для редактирования
|
|||
|
|
self.text_widget.bind('<Button-1>', self.enable_editing)
|
|||
|
|
|
|||
|
|
def enable_editing(self, event=None):
|
|||
|
|
"""Включить режим редактирования"""
|
|||
|
|
if not self.is_editing and self.current_note:
|
|||
|
|
self.is_editing = True
|
|||
|
|
self.text_widget.configure(state='normal')
|
|||
|
|
self.text_widget.unbind('<Button-1>')
|
|||
|
|
|
|||
|
|
def create_new_note(self):
|
|||
|
|
"""Создать новую заметку"""
|
|||
|
|
self.current_note = None
|
|||
|
|
self.is_new_note = True
|
|||
|
|
self.is_editing = True
|
|||
|
|
|
|||
|
|
# Скрываем пустую метку
|
|||
|
|
self.empty_label.pack_forget()
|
|||
|
|
|
|||
|
|
# Показываем текстовое поле
|
|||
|
|
self.text_widget.pack(side='left', fill='both', expand=True)
|
|||
|
|
self.text_scrollbar.pack(side='right', fill='y')
|
|||
|
|
|
|||
|
|
# Устанавливаем текущую дату
|
|||
|
|
now = datetime.now()
|
|||
|
|
self.date_label.config(text=now.strftime('%d.%m.%Y %H:%M'))
|
|||
|
|
|
|||
|
|
# Очищаем текстовое поле
|
|||
|
|
self.text_widget.delete('1.0', 'end')
|
|||
|
|
self.text_widget.configure(state='normal')
|
|||
|
|
self.text_widget.edit_modified(False)
|
|||
|
|
|
|||
|
|
# Показываем кнопки
|
|||
|
|
self.show_editor_buttons()
|
|||
|
|
|
|||
|
|
# Убираем привязку клика
|
|||
|
|
self.text_widget.unbind('<Button-1>')
|
|||
|
|
|
|||
|
|
# Ставим фокус
|
|||
|
|
self.text_widget.focus_set()
|
|||
|
|
|
|||
|
|
if self.on_new_note:
|
|||
|
|
self.on_new_note()
|
|||
|
|
|
|||
|
|
def save_note(self):
|
|||
|
|
"""Сохранить заметку"""
|
|||
|
|
content = self.text_widget.get('1.0', 'end-1c')
|
|||
|
|
|
|||
|
|
if not content.strip() and self.is_new_note:
|
|||
|
|
messagebox.showwarning(
|
|||
|
|
"Предупреждение",
|
|||
|
|
"Нельзя сохранить пустую заметку"
|
|||
|
|
)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if self.is_new_note:
|
|||
|
|
# Создаем новую заметку
|
|||
|
|
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|||
|
|
note_id = self.db.create_note(
|
|||
|
|
title=None,
|
|||
|
|
content=content,
|
|||
|
|
created_at=now
|
|||
|
|
)
|
|||
|
|
self.current_note = self.db.get_note_by_id(note_id)
|
|||
|
|
self.is_new_note = False
|
|||
|
|
self.is_editing = False
|
|||
|
|
|
|||
|
|
# Делаем поле только для чтения
|
|||
|
|
self.text_widget.configure(state='disabled')
|
|||
|
|
self.text_widget.bind('<Button-1>', self.enable_editing)
|
|||
|
|
|
|||
|
|
messagebox.showinfo("Успех", "Заметка сохранена")
|
|||
|
|
else:
|
|||
|
|
# Обновляем существующую заметку
|
|||
|
|
self.db.update_note(
|
|||
|
|
self.current_note['id'],
|
|||
|
|
content=content
|
|||
|
|
)
|
|||
|
|
self.current_note = self.db.get_note_by_id(self.current_note['id'])
|
|||
|
|
self.is_editing = False
|
|||
|
|
|
|||
|
|
# Делаем поле только для чтения
|
|||
|
|
self.text_widget.configure(state='disabled')
|
|||
|
|
self.text_widget.bind('<Button-1>', self.enable_editing)
|
|||
|
|
|
|||
|
|
messagebox.showinfo("Успех", "Заметка обновлена")
|
|||
|
|
|
|||
|
|
self.text_widget.edit_modified(False)
|
|||
|
|
|
|||
|
|
if self.on_note_saved:
|
|||
|
|
self.on_note_saved(self.current_note)
|
|||
|
|
|
|||
|
|
def delete_current_note(self):
|
|||
|
|
"""Удалить текущую заметку"""
|
|||
|
|
if not self.current_note:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
dialog = ConfirmDialog(
|
|||
|
|
self.winfo_toplevel(),
|
|||
|
|
"Удаление заметки",
|
|||
|
|
"Вы уверены, что хотите удалить эту заметку?"
|
|||
|
|
)
|
|||
|
|
self.wait_window(dialog)
|
|||
|
|
|
|||
|
|
if dialog.result:
|
|||
|
|
self.db.delete_note(self.current_note['id'])
|
|||
|
|
self.show_empty_state()
|
|||
|
|
|
|||
|
|
if self.on_note_deleted:
|
|||
|
|
self.on_note_deleted()
|
|||
|
|
|
|||
|
|
def on_text_modified(self, event=None):
|
|||
|
|
"""Обработчик изменения текста"""
|
|||
|
|
if self.text_widget.edit_modified() and self.current_note and not self.is_editing:
|
|||
|
|
# Текст был изменен программно, игнорируем
|
|||
|
|
self.text_widget.edit_modified(False)
|