|
from typing import Optional
|
|
from tkinter import filedialog, messagebox
|
|
from pathlib import Path
|
|
import tkinter as tk
|
|
|
|
|
|
class Notepad(tk.Tk):
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.title("Notepad")
|
|
self.geometry("800x500")
|
|
self.menubar = tk.Menu()
|
|
self.file_menu = tk.Menu(tearoff=False)
|
|
self.file_menu.add_command(label="New", command=self.new)
|
|
self.file_menu.add_command(label="Open", command=self.open)
|
|
self.file_menu.add_command(label="Save", command=self.save)
|
|
self.file_menu.add_command(
|
|
label="Save As...",
|
|
command=self.save_as
|
|
)
|
|
self.menubar.add_cascade(menu=self.file_menu, label="File")
|
|
self.config(menu=self.menubar)
|
|
# Main widget to edit a text file.
|
|
self.text = tk.Text()
|
|
self.text.pack(expand=True, fill=tk.BOTH)
|
|
# The `current_file` attribute contains the path of the
|
|
# file that is being edited or `None` if the file has
|
|
# not been saved yet.
|
|
self.current_file: Optional[Path] = None
|
|
# File types that will show in the open and save
|
|
# file dialogs.
|
|
self.filetypes: tuple[tuple[str, str], ...] = (
|
|
("Text Files", "*.txt"),
|
|
("All Files", "*.*")
|
|
)
|
|
# Replace Tk's default behaviour to the event that
|
|
# closes the window so we can save the file before
|
|
# exiting the program.
|
|
self.protocol("WM_DELETE_WINDOW", self.close)
|
|
|
|
def close(self) -> None:
|
|
if not self.can_continue():
|
|
return
|
|
# Destroy the window.
|
|
self.destroy()
|
|
|
|
def set_current_file(self, current_file: Path) -> None:
|
|
self.current_file = current_file
|
|
self.title(self.current_file.name + " - Notepad")
|
|
|
|
def can_continue(self) -> bool:
|
|
if self.text.edit_modified():
|
|
result = messagebox.askyesnocancel(
|
|
title="Unsaved Changes",
|
|
message="Do you want to save changes?"
|
|
)
|
|
cancel = result is None
|
|
save_before = result is True
|
|
if cancel:
|
|
return False
|
|
elif save_before:
|
|
self.save()
|
|
return True
|
|
# If there were no editions, continue without saving.
|
|
return True
|
|
|
|
def new(self) -> None:
|
|
if not self.can_continue():
|
|
return
|
|
# Delete the previous text.
|
|
self.text.delete("1.0", tk.END)
|
|
self.current_file = None
|
|
self.title("Notepad")
|
|
|
|
def open(self) -> None:
|
|
filename = filedialog.askopenfilename(filetypes=self.filetypes)
|
|
if not filename or not self.can_continue():
|
|
return
|
|
# Remove previous text and load the new one.
|
|
self.text.delete("1.0", tk.END)
|
|
file = Path(filename)
|
|
self.text.insert("1.0", file.read_text("utf8"))
|
|
# Reset text state when a new file is opened.
|
|
self.text.edit_modified(False)
|
|
self.set_current_file(file)
|
|
|
|
def save_current_file(self) -> None:
|
|
if self.current_file is None:
|
|
return
|
|
self.current_file.write_text(self.text.get("1.0", tk.END), "utf8")
|
|
|
|
def save(self) -> None:
|
|
if self.current_file is None:
|
|
self.save_as()
|
|
return
|
|
self.save_current_file()
|
|
|
|
def save_as(self) -> None:
|
|
filename = filedialog.asksaveasfilename(
|
|
defaultextension=".txt",
|
|
filetypes=self.filetypes
|
|
)
|
|
# Do nothing if the user closed or cancelled the dialog.
|
|
if not filename:
|
|
return
|
|
self.set_current_file(Path(filename))
|
|
self.save_current_file()
|
|
|
|
|
|
notepad = Notepad()
|
|
notepad.mainloop()
|