feat: GUI multi-formats + fichier unique + textes mis à jour
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user