# Annexe : Application - Bloc-notes

## Rôle et utilité

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.

## Implémentation technique

- Composant `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`).
- Barre de défilement (Scrollbar) : L'application instancie une `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.
- Menu Fichier : Contrairement aux applications modernes avec des barres de boutons massives, le bloc-notes utilise un `tk.Menu` classique (Fichier > Nouveau, Ouvrir, Enregistrer, Quitter), ce qui lui confère son aspect délicieusement rétro.
- Dialogue système : Pour ouvrir et sauvegarder, l'application s'appuie sur `tkinter.filedialog`, ce qui permet d'utiliser la fenêtre de navigation native sans avoir à recoder un explorateur de fichiers interne.

## Pistes de modification

- Sauvegarde automatique : Un développeur pourrait ajouter un mécanisme (via `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.
- Statistiques en temps réel : L'ajout d'une petite barre d'état (`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.

## Code Source

    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