From 63a4a013a264188412a82c6864a2a0223fe89ac8 Mon Sep 17 00:00:00 2001 From: Domi31tls Date: Wed, 18 Mar 2026 09:39:06 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20GUI=20multi-formats=20+=20fichier=20uni?= =?UTF-8?q?que=20+=20textes=20mis=20=C3=A0=20jour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Titre : "Pseudonymisation de vos documents" - Sous-titre, étape 1, paramètres, bouton : textes adaptés - Choix fichier unique : clic → menu "Dossier / Fichier" avec filedialog filtré par formats supportés - 14 formats supportés : PDF, DOCX, ODT, RTF, TXT, HTML, JPEG, PNG, TIFF, BMP Co-Authored-By: Claude Opus 4.6 (1M context) --- Pseudonymisation_Gui_V5.py | 151 ++++++++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 46 deletions(-) diff --git a/Pseudonymisation_Gui_V5.py b/Pseudonymisation_Gui_V5.py index d62fdff..4f1cfc4 100644 --- a/Pseudonymisation_Gui_V5.py +++ b/Pseudonymisation_Gui_V5.py @@ -85,7 +85,7 @@ except ImportError: # --------------------------------------------------------------------------- # Constantes # --------------------------------------------------------------------------- -APP_TITLE = "Pseudonymisation de PDF" +APP_TITLE = "Pseudonymisation de vos documents" APP_VERSION = "v5.0" def _app_dir() -> Path: @@ -305,6 +305,8 @@ class App: # --- Contrôle d'arrêt --- self._stop_requested = False + # --- Fichier unique (None = mode dossier) --- + self._single_file: Optional[Path] = None # --- Construction UI --- self._build_ui() @@ -382,7 +384,7 @@ class App: tk.Label( main, - text="Masquez automatiquement les données personnelles de vos documents PDF.", + text="Masquez automatiquement les données personnelles de vos documents.", font=self._f_body, bg=CLR_BG, fg=CLR_TEXT_SECONDARY, anchor="w", ).pack(fill=tk.X, padx=pad_x, pady=(0, 18)) @@ -414,7 +416,7 @@ class App: self._folder_text_lbl = tk.Label( self._folder_inner, - text="Cliquez pour choisir un dossier (tous les PDF seront recherchés récursivement)", + text="Cliquez pour choisir un dossier ou un fichier", font=self._f_body, bg=CLR_CARD_BG, fg=CLR_TEXT_SECONDARY, ) self._folder_text_lbl.pack(pady=(4, 0)) @@ -448,7 +450,7 @@ class App: tk.Label( info_inner, - text=("\u2022 Recherche récursive de tous les PDF dans les sous-dossiers\n" + text=("\u2022 Recherche récursive de tous les documents dans les sous-dossiers\n" "\u2022 Sortie PDF Image (raster) — sécurité maximale, aucun texte résiduel\n" "\u2022 Résultats dans le dossier « anonymise/ » à la racine"), font=self._f_card_desc, bg=CLR_BLUE_LIGHT, fg=CLR_TEXT_SECONDARY, @@ -480,7 +482,7 @@ class App: buttons_frame.pack(fill=tk.X, padx=pad_x, pady=(0, 4)) self.btn_run = tk.Button( - buttons_frame, text="Lancer la pseudonymisation", + buttons_frame, text="Lancer l'anonymisation", font=self._f_button, bg=CLR_PRIMARY, fg="white", activebackground="#1d4ed8", activeforeground="white", relief=tk.FLAT, cursor="hand2", pady=10, @@ -648,29 +650,71 @@ class App: # Actions dossier # --------------------------------------------------------------- def _browse(self): + """Propose le choix entre dossier et fichier unique via un menu contextuel.""" + menu = tk.Menu(self.root, tearoff=0) + menu.add_command(label="Choisir un dossier", command=self._browse_folder) + menu.add_command(label="Choisir un fichier", command=self._browse_file) + # Afficher le menu sous le curseur + try: + menu.tk_popup(self.root.winfo_pointerx(), self.root.winfo_pointery()) + finally: + menu.grab_release() + + def _browse_folder(self): d = filedialog.askdirectory() if d: + self._single_file = None self.dir_var.set(d) self._update_folder_display() + def _browse_file(self): + try: + from format_converter import SUPPORTED_EXTENSIONS + except ImportError: + SUPPORTED_EXTENSIONS = {".pdf"} + # Construire les filtres pour le dialogue + ext_list = " ".join(f"*{e}" for e in sorted(SUPPORTED_EXTENSIONS)) + f = filedialog.askopenfilename( + title="Choisir un document à anonymiser", + filetypes=[ + ("Documents supportés", ext_list), + ("PDF", "*.pdf"), + ("Word", "*.docx"), + ("Images", "*.jpg *.jpeg *.png *.tiff *.tif *.bmp"), + ("Texte", "*.txt *.rtf *.odt *.html *.htm"), + ("Tous", "*.*"), + ], + ) + if f: + self._single_file = Path(f) + self.dir_var.set(str(self._single_file.parent)) + self._update_folder_display() + def _update_folder_display(self): folder = self.dir_var.get() if not folder: return - # Compter les documents supportés (récursif) - try: - from format_converter import SUPPORTED_EXTENSIONS - except ImportError: - SUPPORTED_EXTENSIONS = {".pdf"} - pdf_count = 0 - try: - pdf_count = len([ - p for p in Path(folder).rglob("*") - if p.is_file() and p.suffix.lower() in SUPPORTED_EXTENSIONS - ]) - except Exception: - pass + is_single = getattr(self, '_single_file', None) is not None + + if is_single: + doc_count = 1 + display_label = self._single_file.name + else: + # Compter les documents supportés (récursif) + try: + from format_converter import SUPPORTED_EXTENSIONS + except ImportError: + SUPPORTED_EXTENSIONS = {".pdf"} + doc_count = 0 + try: + doc_count = len([ + p for p in Path(folder).rglob("*") + if p.is_file() and p.suffix.lower() in SUPPORTED_EXTENSIONS + ]) + except Exception: + pass + display_label = folder # Vider et reconstruire l'intérieur for w in self._folder_inner.winfo_children(): @@ -679,8 +723,9 @@ class App: row = tk.Frame(self._folder_inner, bg=CLR_CARD_BG) row.pack(fill=tk.X) + icon = "\U0001f4c4" if is_single else "\U0001f4c2" # 📄 ou 📂 tk.Label( - row, text="\U0001f4c2", font=(self._font_family, 16), + row, text=icon, font=(self._font_family, 16), bg=CLR_CARD_BG, ).pack(side=tk.LEFT, padx=(0, 8)) @@ -688,7 +733,7 @@ class App: info_frame.pack(side=tk.LEFT, fill=tk.X, expand=True) # Chemin (tronqué si trop long) - display_path = folder + display_path = display_label if len(display_path) > 60: display_path = "..." + display_path[-57:] tk.Label( @@ -696,9 +741,13 @@ class App: bg=CLR_CARD_BG, fg=CLR_TEXT, anchor="w", ).pack(fill=tk.X) - suffix = "document trouvé (récursif)" if pdf_count <= 1 else "documents trouvés (récursif)" + if is_single: + subtitle = f"Fichier unique — {self._single_file.suffix.upper().lstrip('.')}" + else: + suffix = "document trouvé (récursif)" if doc_count <= 1 else "documents trouvés (récursif)" + subtitle = f"{doc_count} {suffix}" tk.Label( - info_frame, text=f"{pdf_count} {suffix}", + info_frame, text=subtitle, font=self._f_small, bg=CLR_CARD_BG, fg=CLR_TEXT_SECONDARY, anchor="w", ).pack(fill=tk.X) @@ -716,31 +765,41 @@ class App: # Lancement # --------------------------------------------------------------- def _run(self): - folder = Path(self.dir_var.get().strip()) - if not folder.is_dir(): - messagebox.showwarning( - "Dossier invalide", - "Choisissez un dossier contenant des documents.", - ) - return + is_single = getattr(self, '_single_file', None) is not None - try: - from format_converter import SUPPORTED_EXTENSIONS - except ImportError: - SUPPORTED_EXTENSIONS = {".pdf"} - pdfs = sorted([ - p for p in folder.rglob("*") - if p.is_file() and p.suffix.lower() in SUPPORTED_EXTENSIONS - ]) - if not pdfs: - exts = ", ".join(sorted(SUPPORTED_EXTENSIONS)) - messagebox.showwarning( - "Aucun document", - f"Aucun fichier supporté trouvé.\n" - f"Formats acceptés : {exts}\n" - f"(recherche récursive dans les sous-dossiers)", - ) - return + if is_single: + # Mode fichier unique + if not self._single_file.is_file(): + messagebox.showwarning("Fichier introuvable", f"{self._single_file}") + return + folder = self._single_file.parent + pdfs = [self._single_file] + else: + # Mode dossier + folder = Path(self.dir_var.get().strip()) + if not folder.is_dir(): + messagebox.showwarning( + "Dossier invalide", + "Choisissez un dossier ou un fichier.", + ) + return + try: + from format_converter import SUPPORTED_EXTENSIONS + except ImportError: + SUPPORTED_EXTENSIONS = {".pdf"} + pdfs = sorted([ + p for p in folder.rglob("*") + if p.is_file() and p.suffix.lower() in SUPPORTED_EXTENSIONS + ]) + if not pdfs: + exts = ", ".join(sorted(SUPPORTED_EXTENSIONS)) + messagebox.showwarning( + "Aucun document", + f"Aucun fichier supporté trouvé.\n" + f"Formats acceptés : {exts}\n" + f"(recherche récursive dans les sous-dossiers)", + ) + return self._stop_requested = False self.btn_run.pack_forget()