Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Первое приложение на Python

Апр
23
3
Пользователь
Спустя 4 месяцев изучения python (я самоучка) решил попробовать что-то написать, скрывать не буду несколько раз прибегал к помощи вайб кодинга, но основную часть писал сам так как было интересно чему я научился прошу оценить, проект написан на tkinter, ибо он идет в комплекте с питоном и простой в освоении, дизайн конечно не блещет красотой, но я попытался выжать максимум из него (насколько я способен)
Буду рад наставлениям и поправкам

AkoBook:
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import time

class AdvancedNotepad:
    def __init__(self, root):
        self.root = root
        self.root.title("AkoBook")
        self.root.geometry("1000x700")
        self.root.configure(bg="#2c2c2c")

        self.current_file = None
        self.auto_save_interval = 30
        self.last_save_time = time.time()
        self.highlight_delay = None

        self.setup_style()
        self.create_widgets()
        self.setup_layout()
        self.bind_events()

    def setup_style(self):
        style = ttk.Style()
        style.theme_use("clam")
        style.configure("TButton", padding=5, font=("Arial", 9))
        style.configure("TLabel", background="#1e1e1e", foreground="#ffffff")
        style.map("TButton",
                  background=[("active", "#404040")],
                  foreground=[("active", "#ffffff")])

    def create_widgets(self):
        self.main_frame = tk.Frame(self.root, bg="#2c2c2c")
        self.sidebar = tk.Frame(self.main_frame, width=220, bg="#1e1e1e", relief="sunken", bd=1)
        self.content_frame = tk.Frame(self.main_frame, bg="#2c2c2c")

        self.line_numbers = tk.Text(
            self.content_frame,
            width=4,
            padx=3,
            takefocus=0,
            border=0,
            background="#252525",
            foreground="#808080",
            font=("Consolas", 11),
            state="disabled"
        )

        self.text_area = tk.Text(
            self.content_frame,
            wrap="word",
            font=("Consolas", 11),
            bg="#1e1e1e",
            fg="#e0e0e0",
            insertbackground="#ffffff",
            selectbackground="#404040",
            selectforeground="#ffffff",
            padx=10,
            pady=10,
            undo=True
        )

        self.scrollbar = tk.Scrollbar(self.content_frame, command=self.text_area.yview)
        self.text_area.config(yscrollcommand=self.scrollbar.set)

        self.status_bar = tk.Label(
            self.root,
            text="Готов",
            bd=1,
            relief=tk.SUNKEN,
            anchor=tk.W,
            bg="#1e1e1e",
            fg="#a0a0a0",
            font=("TkDefaultFont", 9)
        )

        self.setup_sidebar_widgets()
        self.setup_tags()

    def setup_tags(self):
        self.text_area.tag_configure("search", background="#ffcc00", foreground="#000000")
        self.text_area.tag_configure("keyword", foreground="#ff9900", font=("Consolas", 11, "bold"))
        self.text_area.tag_configure("current_line", background="#2a2a2a")

    def setup_sidebar_widgets(self):
        tk.Label(
            self.sidebar,
            text="Панель инструментов",
            bg="#1e1e1e",
            fg="#ffffff",
            font=("Arial", 10, "bold"),
            pady=10
        ).pack(pady=(10, 5))

        self.char_count_label = tk.Label(
            self.sidebar,
            text="Символов: 0 | Слов: 0 | Строк: 1",
            bg="#1e1e1e",
            fg="#aaaaaa",
            font=("Arial", 9),
            anchor="w",
            padx=10
        )
        self.char_count_label.pack(fill="x", padx=10, pady=5)

        tk.Label(self.sidebar, text="Поиск:", bg="#1e1e1e", fg="#ffffff", font=("Arial", 9)).pack(anchor="w", padx=10)
        self.search_entry = tk.Entry(self.sidebar, font=("Arial", 9), bg="#333333", fg="#ffffff", insertbackground="#ffffff")
        self.search_entry.pack(fill="x", padx=10, pady=(0, 5))

        tk.Label(self.sidebar, text="Заменить на:", bg="#1e1e1e", fg="#ffffff", font=("Arial", 9)).pack(anchor="w", padx=10)
        self.replace_entry = tk.Entry(self.sidebar, font=("Arial", 9), bg="#333333", fg="#ffffff", insertbackground="#ffffff")
        self.replace_entry.pack(fill="x", padx=10, pady=(0, 5))

        ttk.Button(self.sidebar, text="Найти", command=self.find_text).pack(fill="x", padx=10, pady=(0, 5))
        ttk.Button(self.sidebar, text="Заменить", command=self.replace_text).pack(fill="x", padx=10, pady=(0, 5))
        ttk.Button(self.sidebar, text="Заменить всё", command=self.replace_all_text).pack(fill="x", padx=10, pady=(0, 10))

        tk.Label(self.sidebar, text="Тема:", bg="#1e1e1e", fg="#ffffff", font=("Arial", 9)).pack(anchor="w", padx=10)
        self.theme_var = tk.StringVar(value="Тёмная")
        themes = ["Тёмная", "Светлая", "Синий", "Зелёный"]
        for theme in themes:
            tk.Radiobutton(
                self.sidebar,
                text=theme,
                variable=self.theme_var,
                value=theme,
                bg="#1e1e1e",
                fg="#ffffff",
                selectcolor="#404040",
                activebackground="#1e1e1e",
                font=("Arial", 9),
                command=self.change_theme
            ).pack(anchor="w", padx=20, pady=2)

        ttk.Button(self.sidebar, text="Очистить всё", command=self.clear_text).pack(fill="x", padx=10, pady=(20, 0))

    def setup_layout(self):
        self.main_frame.pack(fill="both", expand=True, padx=5, pady=5)
        self.sidebar.pack(side="left", fill="y")
        self.content_frame.pack(side="right", fill="both", expand=True)

        self.line_numbers.pack(side="left", fill="y")
        self.text_area.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")
        self.status_bar.pack(side="bottom", fill="x")

    def bind_events(self):
        self.text_area.bind("<KeyRelease>", self.on_key_release)
        self.text_area.bind("<Button-3>", self.show_context_menu)
        self.text_area.bind("<MouseWheel>", self.on_mousewheel)
        self.search_entry.bind("<KeyRelease>", lambda e: self.find_text())
        self.root.bind("<F11>", self.toggle_fullscreen)
        self.root.bind("<Control-plus>", lambda e: self.zoom_in())
        self.root.bind("<Control-minus>", lambda e: self.zoom_out())
        self.root.after(100, self.update_line_numbers)
        self.root.after(1000, self.auto_save_check)

    def on_key_release(self, event=None):
        self.update_char_count()
        self.schedule_highlight()

    def schedule_highlight(self):
        if self.highlight_delay:
            self.text_area.after_cancel(self.highlight_delay)
        self.highlight_delay = self.text_area.after(200, self.highlight_syntax)

    def highlight_syntax(self):
        keywords = ["def", "class", "if", "else", "elif", "for", "while", "import", "from", "return", "try", "except", "with"]
        self.text_area.tag_remove("keyword", "1.0", "end")
        for word in keywords:
            start = "1.0"
            while True:
                start = self.text_area.search(r'\b' + word + r'\b', start, stopindex="end", regexp=True)
                if not start:
                    break
                end = f"{start}+{len(word)}c"
                self.text_area.tag_add("keyword", start, end)
                start = end

    def update_line_numbers(self):
        self.line_numbers.config(state="normal")
        self.line_numbers.delete("1.0", "end")
        lines = self.text_area.get("1.0", "end-1c").split("\n")
        line_numbers = "\n".join(str(i) for i in range(1, len(lines) + 1))
        self.line_numbers.insert("1.0", line_numbers)
        self.line_numbers.config(state="disabled")
        self.root.after(100, self.update_line_numbers)

    def update_char_count(self):
        text = self.text_area.get("1.0", "end-1c")
        chars = len(text)
        words = len(text.split())
        lines = int(self.text_area.index('end-1c').split('.')[0])
        self.char_count_label.config(text=f"Символов: {chars} | Слов: {words} | Строк: {lines}")

    def show_context_menu(self, event):
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label="Вырезать", command=self.cut_text)
        menu.add_command(label="Копировать", command=self.copy_text)
        menu.add_command(label="Вставить", command=self.paste_text)
        menu.add_separator()
        menu.add_command(label="Выделить всё", command=self.select_all)
        menu.tk_popup(event.x_root, event.y_root)

    def find_text(self):
        term = self.search_entry.get().strip()
        if not term:
            return
        self.text_area.tag_remove("search", "1.0", "end")
        start = "1.0"
        count = tk.IntVar()
        found = 0
        while True:
            start = self.text_area.search(term, start, stopindex="end", count=count)
            if not start:
                break
            end = f"{start}+{count.get()}c"
            self.text_area.tag_add("search", start, end)
            start = end
            found += 1
        self.status_bar.config(text=f"Найдено: {found} вхождений" if found else "Не найдено")

    def replace_text(self):
        find = self.search_entry.get().strip()
        replace = self.replace_entry.get().strip()
        if not find or not self.text_area.tag_ranges("sel"):
            return
        self.text_area.insert(tk.INSERT, replace)
        self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)

    def replace_all_text(self):
        find = self.search_entry.get().strip()
        replace = self.replace_entry.get().strip()
        if not find:
            return
        content = self.text_area.get("1.0", "end-1c")
        new_content = content.replace(find, replace)
        self.text_area.delete("1.0", "end")
        self.text_area.insert("1.0", new_content)
        self.status_bar.config(text=f"Заменено все вхождения '{find}' на '{replace}'")

    def change_theme(self):
        theme = self.theme_var.get()
        themes = {
            "Тёмная": {"bg": "#1e1e1e", "fg": "#e0e0e0", "insert": "#ffffff", "select": "#404040"},
            "Светлая": {"bg": "#ffffff", "fg": "#000000", "insert": "#000000", "select": "#c0c0c0"},
            "Синий": {"bg": "#001f3f", "fg": "#ffffff", "insert": "#ffffff", "select": "#003366"},
            "Зелёный": {"bg": "#003300", "fg": "#00ff00", "insert": "#00ff00", "select": "#006600"},
        }
        cfg = themes.get(theme, themes["Тёмная"])
        self.text_area.config(bg=cfg["bg"], fg=cfg["fg"], insertbackground=cfg["insert"], selectbackground=cfg["select"])

    def clear_text(self):
        if messagebox.askyesno("Подтверждение", "Очистить весь текст?"):
            self.text_area.delete("1.0", "end")

    def auto_save_check(self):
        if self.current_file and time.time() - self.last_save_time > self.auto_save_interval:
            self.save_file()
            self.status_bar.config(text="Автосохранение...")
        self.root.after(1000, self.auto_save_check)

    def toggle_fullscreen(self, event=None):
        state = self.root.attributes("-fullscreen")
        self.root.attributes("-fullscreen", not state)

    def zoom_in(self):
        font = self.text_area.cget("font")
        family, size = font.split()[0], int(font.split()[1])
        if size < 24:
            self.text_area.config(font=(family, size + 1))
            self.line_numbers.config(font=(family, size + 1))

    def zoom_out(self):
        font = self.text_area.cget("font")
        family, size = font.split()[0], int(font.split()[1])
        if size > 8:
            self.text_area.config(font=(family, size - 1))
            self.line_numbers.config(font=(family, size - 1))

    def on_mousewheel(self, event):
        if event.state & 0x04:  # Ctrl + колесо
            if event.delta > 0:
                self.zoom_in()
            else:
                self.zoom_out()
            return "break"

    def cut_text(self):
        self.text_area.event_generate("<<Cut>>")

    def copy_text(self):
        self.text_area.event_generate("<<Copy>>")

    def paste_text(self):
        self.text_area.event_generate("<<Paste>>")

    def select_all(self):
        self.text_area.tag_add("sel", "1.0", "end")

    def setup_menu(self):
        menu = tk.Menu(self.root)
        self.root.config(menu=menu)

        file_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Файл", menu=file_menu)
        file_menu.add_command(label="Новый", command=self.new_file, accelerator="Ctrl+N")
        file_menu.add_command(label="Открыть...", command=self.open_file, accelerator="Ctrl+O")
        file_menu.add_command(label="Сохранить", command=self.save_file, accelerator="Ctrl+S")
        file_menu.add_command(label="Сохранить как...", command=self.save_file_as, accelerator="Ctrl+Shift+S")
        file_menu.add_separator()
        file_menu.add_command(label="Выход", command=self.exit_app)

        edit_menu = tk.Menu(menu, tearoff=0)
        menu.add_cascade(label="Правка", menu=edit_menu)
        edit_menu.add_command(label="Отменить", command=self.undo_text, accelerator="Ctrl+Z")
        edit_menu.add_command(label="Повторить", command=self.redo_text, accelerator="Ctrl+Y")
        edit_menu.add_separator()
        edit_menu.add_command(label="Вырезать", command=self.cut_text, accelerator="Ctrl+X")
        edit_menu.add_command(label="Копировать", command=self.copy_text, accelerator="Ctrl+C")
        edit_menu.add_command(label="Вставить", command=self.paste_text, accelerator="Ctrl+V")
        edit_menu.add_command(label="Удалить", command=self.delete_text, accelerator="Del")
        edit_menu.add_separator()
        edit_menu.add_command(label="Выделить всё", command=self.select_all, accelerator="Ctrl+A")

    def new_file(self):
        if messagebox.askyesno("Новый файл", "Сохранить текущий?"):
            self.save_file()
        self.text_area.delete("1.0", "end")
        self.current_file = None

    def open_file(self):
        path = filedialog.askopenfilename(
            filetypes=[("Текстовые файлы", "*.txt"), ("Python", "*.py"), ("HTML", "*.html"), ("Все файлы", "*.*")]
        )
        if path:
            try:
                with open(path, "r", encoding="utf-8") as f:
                    content = f.read()
                self.text_area.delete("1.0", "end")
                self.text_area.insert("1.0", content)
                self.current_file = path
                self.update_char_count()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось открыть файл:\n{e}")

    def save_file(self):
        if self.current_file:
            try:
                content = self.text_area.get("1.0", "end-1c")
                with open(self.current_file, "w", encoding="utf-8") as f:
                    f.write(content)
                self.last_save_time = time.time()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось сохранить:\n{e}")
        else:
            self.save_file_as()

    def save_file_as(self):
        path = filedialog.asksaveasfilename(
            defaultextension=".txt",
            filetypes=[("Текстовые файлы", "*.txt"), ("Python", "*.py"), ("HTML", "*.html"), ("Все файлы", "*.*")]
        )
        if path:
            try:
                content = self.text_area.get("1.0", "end-1c")
                with open(path, "w", encoding="utf-8") as f:
                    f.write(content)
                self.current_file = path
                self.last_save_time = time.time()
            except Exception as e:
                messagebox.showerror("Ошибка", f"Не удалось сохранить:\n{e}")

    def exit_app(self):
        if messagebox.askokcancel("Выход", "Закрыть приложение?"):
            self.root.destroy()

    def delete_text(self):
        self.text_area.delete("sel.first", "sel.last")

    def undo_text(self):
        try:
            self.text_area.edit_undo()
        except:
            pass

    def redo_text(self):
        try:
            self.text_area.edit_redo()
        except:
            pass


if __name__ == "__main__":
    root = tk.Tk()
    app = AdvancedNotepad(root)
    app.setup_menu()
    root.mainloop()
 
Сверху