<===

ProNotes

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()
← Previous Next →
Back to list