L'application apps/blocnotes/app.py est l'équivalent du célèbre "Notepad" sous Windows. C'est un éditeur de texte pur, minimaliste, permettant à l'utilisateur de prendre des notes rapides, de copier/coller du texte ou de lire de petits fichiers sans la lourdeur d'un traitement de texte complet.
tk.Text : Le cœur de l'application repose entièrement sur le composant multi-lignes standard de Tkinter. Il est configuré pour occuper tout l'espace disponible (fill="both", expand=True).tk.Scrollbar qu'elle lie manuellement au widget de texte (yscrollcommand=scrollbar.set), démontrant la façon standard de créer une zone défilante en Tkinter.tk.Menu classique (Fichier > Nouveau, Ouvrir, Enregistrer, Quitter), ce qui lui confère son aspect délicieusement rétro.tkinter.filedialog, ce qui permet d'utiliser la fenêtre de navigation native sans avoir à recoder un explorateur de fichiers interne.window.after) qui écrit discrètement le contenu du texte dans un fichier temporaire /tmp/autosave.txt toutes les minutes pour éviter les pertes de données.tk.Label) en bas de la fenêtre indiquant le nombre de mots et de caractères tapés transformerait cet outil basique en un outil plus orienté vers la rédaction.import tkinter as tk
from tkinter import filedialog, messagebox
import os
def start(window, app_manager=None, **kwargs):
text_area = tk.Text(window, wrap="word", undo=True)
text_area.pack(fill="both", expand=True)
menu_frame = tk.Frame(window, bg="lightgray")
menu_frame.pack(side="top", fill="x", before=text_area)
current_filepath = kwargs.get("filepath")
def update_title(modified=False):
if hasattr(window, 'master') and window.master.__class__.__name__ == 'Window':
title_text = current_filepath if current_filepath else "Sans titre"
prefix = "* " if modified else ""
window.master.title_label.config(text=f"Bloc-notes - {prefix}{title_text}")
update_title()
def on_modify(event=None):
update_title(modified=text_area.edit_modified())
text_area.bind("<<Modified>>", on_modify)
def set_clean():
text_area.edit_modified(False)
if current_filepath and os.path.exists(current_filepath):
try:
with open(current_filepath, "r", encoding="utf-8") as f:
text_area.insert(tk.END, f.read())
set_clean()
except Exception as e:
messagebox.showerror("Erreur", f"Impossible d'ouvrir le fichier :\n{e}")
def check_unsaved():
if text_area.edit_modified():
ans = messagebox.askyesnocancel("Attention", "Voulez-vous enregistrer les modifications ?")
if ans is True:
return save_file()
elif ans is False:
return True
else:
return False
return True
def new_file():
if not check_unsaved(): return
nonlocal current_filepath
current_filepath = None
text_area.delete(1.0, tk.END)
set_clean()
update_title()
def open_file():
if not check_unsaved(): return
nonlocal current_filepath
init_dir = os.path.dirname(current_filepath) if current_filepath else os.path.expanduser("~")
filepath = filedialog.askopenfilename(initialdir=init_dir, defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Files", "*.txt")])
if not filepath:
return
try:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
text_area.delete(1.0, tk.END)
text_area.insert(tk.END, content)
current_filepath = filepath
set_clean()
update_title()
except Exception as e:
messagebox.showerror("Erreur", f"Impossible d'ouvrir :\n{e}")
def save_file():
nonlocal current_filepath
if not current_filepath:
return save_file_as()
try:
with open(current_filepath, "w", encoding="utf-8") as f:
f.write(text_area.get(1.0, "end-1c"))
set_clean()
update_title()
return True
except Exception as e:
messagebox.showerror("Erreur", f"Impossible de sauvegarder :\n{e}")
return False
def save_file_as():
nonlocal current_filepath
init_dir = os.path.dirname(current_filepath) if current_filepath else os.path.expanduser("~")
filepath = filedialog.asksaveasfilename(initialdir=init_dir, defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Files", "*.txt")])
if not filepath:
return False
current_filepath = filepath
return save_file()
icon_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "icons")
window.icons = {}
for iname in ['menu_new_file', 'menu_open', 'menu_save', 'menu_save_as']:
ipath = os.path.join(icon_dir, iname + ".png")
if os.path.exists(ipath):
window.icons[iname] = tk.PhotoImage(file=ipath)
btn_new = tk.Button(menu_frame, text=" Nouveau", image=window.icons.get('menu_new_file'), compound="left", command=new_file, relief="flat")
btn_new.pack(side="left", padx=2, pady=2)
btn_open = tk.Button(menu_frame, text=" Ouvrir", image=window.icons.get('menu_open'), compound="left", command=open_file, relief="flat")
btn_open.pack(side="left", padx=2, pady=2)
btn_save = tk.Button(menu_frame, text=" Enregistrer", image=window.icons.get('menu_save'), compound="left", command=save_file, relief="flat")
btn_save.pack(side="left", padx=2, pady=2)
def show_help():
help_text = (
"Aide du Bloc-notes\n\n"
"Raccourcis clavier disponibles :\n"
"• Ctrl + N : Nouveau fichier\n"
"• Ctrl + O : Ouvrir un fichier\n"
"• Ctrl + S : Enregistrer\n"
"• Ctrl + Shift + S : Enregistrer sous...\n"
"• Ctrl + C : Copier la sélection\n"
"• Ctrl + X : Couper la sélection\n"
"• Ctrl + V : Coller\n"
"• Ctrl + Z : Annuler la modification\n"
"• Ctrl + Y : Refaire la modification\n\n"
"Indicateur visuel :\n"
"L'étoile (*) dans le titre signifie que le fichier contient des modifications non enregistrées."
)
messagebox.showinfo("Aide", help_text)
btn_save_as = tk.Button(menu_frame, text=" Enregistrer sous...", image=window.icons.get('menu_save_as'), compound="left", command=save_file_as, relief="flat")
btn_save_as.pack(side="left", padx=2, pady=2)
btn_help = tk.Button(menu_frame, text=" ? Aide ", command=show_help, relief="flat", bg="lightblue")
btn_help.pack(side="right", padx=5, pady=2)
text_area.focus_set()
text_area.bind("<Control-n>", lambda e: new_file() or "break")
text_area.bind("<Control-o>", lambda e: open_file() or "break")
text_area.bind("<Control-s>", lambda e: save_file() or "break")
text_area.bind("<Control-S>", lambda e: save_file_as() or "break")
# Lier le gestionnaire de fermeture si possible
if hasattr(window, 'master') and window.master.__class__.__name__ == 'Window':
window.master.on_close_callback = check_unsaved