<===
2025-10-03 10:01:22
$ cat notes-tk.py
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import sqlite3
from datetime import datetime
import os
import re
class NotebookApp:
def __init__(self, root):
self.root = root
self.root.title("Записная книжка")
self.root.geometry("640x480")
# Путь к базе в домашней папке пользователя
home_dir = os.path.expanduser("~")
self.db_path = os.path.join(home_dir, "notebook.db")
# Подключение к базе
self.conn = sqlite3.connect(self.db_path)
self.create_database()
# Виджеты интерфейса
self.note_input = tk.Text(root, height=3)
self.note_input.pack(fill="x", padx=10, pady=5)
button_frame = tk.Frame(root)
button_frame.pack(fill="x", padx=10, pady=5)
tk.Button(button_frame, text="Добавить заметку", command=self.add_note).pack(side="left")
tk.Button(button_frame, text="Очистить", command=self.clear_input).pack(side="left", padx=5)
self.search_var = tk.StringVar()
self.search_var.trace("w", lambda *args: self.search_notes())
search_entry = tk.Entry(root, textvariable=self.search_var)
search_entry.pack(fill="x", padx=10, pady=5)
columns = ("id", "date", "note")
self.tree = ttk.Treeview(root, columns=columns, show="headings")
self.tree.heading("id", text="ID")
self.tree.heading("date", text="Дата")
self.tree.heading("note", text="Заметка")
self.tree.column("id", width=50)
self.tree.column("date", width=150)
self.tree.column("note", width=550)
self.tree.pack(fill="both", expand=True, padx=10, pady=5)
self.tree.bind("<Double-1>", self.edit_note)
action_frame = tk.Frame(root)
action_frame.pack(fill="x", padx=10, pady=5)
tk.Button(action_frame, text="Редактировать", command=self.edit_selected_note).pack(side="left")
tk.Button(action_frame, text="Удалить", command=self.delete_selected_note).pack(side="left", padx=5)
self.load_notes()
def create_database(self):
cursor = self.conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
note TEXT NOT NULL
)
""")
cursor.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(
note,
content='notes',
content_rowid='id'
)
""")
cursor.executescript("""
CREATE TRIGGER IF NOT EXISTS notes_insert AFTER INSERT ON notes BEGIN
INSERT INTO notes_fts(rowid, note) VALUES (new.id, new.note);
END;
CREATE TRIGGER IF NOT EXISTS notes_update AFTER UPDATE ON notes BEGIN
INSERT INTO notes_fts(notes_fts, rowid, note) VALUES('delete', old.id, old.note);
INSERT INTO notes_fts(rowid, note) VALUES (new.id, new.note);
END;
CREATE TRIGGER IF NOT EXISTS notes_delete AFTER DELETE ON notes BEGIN
INSERT INTO notes_fts(notes_fts, rowid, note) VALUES('delete', old.id, old.note);
END;
""")
self.conn.commit()
def add_note(self):
note = self.note_input.get("1.0", tk.END).strip()
if not note:
messagebox.showwarning("Ошибка", "Заметка не может быть пустой!")
return
if len(note) > 255:
messagebox.showwarning("Ошибка", "Заметка не может превышать 255 символов!")
return
date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cursor = self.conn.cursor()
cursor.execute("INSERT INTO notes (date, note) VALUES (?, ?)", (date, note))
self.conn.commit()
self.note_input.delete("1.0", tk.END)
self.load_notes()
def clear_input(self):
self.note_input.delete("1.0", tk.END)
self.search_var.set("")
def load_notes(self):
cursor = self.conn.cursor()
cursor.execute("SELECT id, date, note FROM notes ORDER BY date DESC")
rows = cursor.fetchall()
for item in self.tree.get_children():
self.tree.delete(item)
for row in rows:
self.tree.insert("", tk.END, values=row)
def safe_fts_query(self, query):
# Разбиваем и очищаем запрос для FTS5, разрешаем только буквы, цифры, дефис и нижнее подчеркивание
terms = query.split()
safe_terms = []
for term in terms:
cleaned = re.sub(r'[^\w\-]', '', term)
if cleaned:
# Добавляем кавычки и префиксную звездочку
safe_terms.append(f'"{cleaned}"*')
return ' AND '.join(safe_terms)
def search_notes(self):
query = self.search_var.get().strip()
cursor = self.conn.cursor()
if query:
fts_query = self.safe_fts_query(query)
try:
cursor.execute("""
SELECT n.id, n.date, n.note
FROM notes n
JOIN notes_fts f ON n.id = f.rowid
WHERE notes_fts MATCH ?
ORDER BY n.date DESC
""", (fts_query,))
except sqlite3.OperationalError as e:
messagebox.showerror("Ошибка поиска", f"Неверный запрос для поиска: {e}")
return
else:
cursor.execute("SELECT id, date, note FROM notes ORDER BY date DESC")
rows = cursor.fetchall()
for item in self.tree.get_children():
self.tree.delete(item)
for row in rows:
self.tree.insert("", tk.END, values=row)
def edit_selected_note(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("Ошибка", "Выберите заметку для редактирования!")
return
item = selected[0]
values = self.tree.item(item, "values")
note_id = values[0]
current_note = values[2]
new_note = simpledialog.askstring("Редактировать заметку", "Введите новую заметку:", initialvalue=current_note)
if new_note and new_note.strip():
if len(new_note) > 255:
messagebox.showwarning("Ошибка", "Заметка не может превышать 255 символов!")
return
cursor = self.conn.cursor()
cursor.execute("UPDATE notes SET note = ? WHERE id = ?", (new_note, note_id))
self.conn.commit()
self.load_notes()
def edit_note(self, event):
self.edit_selected_note()
def delete_selected_note(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("Ошибка", "Выберите заметку для удаления!")
return
if messagebox.askyesno("Подтверждение", "Удалить выбранную заметку?"):
item = selected[0]
note_id = self.tree.item(item, "values")[0]
cursor = self.conn.cursor()
cursor.execute("DELETE FROM notes WHERE id = ?", (note_id,))
self.conn.commit()
self.load_notes()
if __name__ == "__main__":
root = tk.Tk()
app = NotebookApp(root)
root.mainloop()