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
|
# Constantes
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
APP_TITLE = "Pseudonymisation de PDF"
|
APP_TITLE = "Pseudonymisation de vos documents"
|
||||||
APP_VERSION = "v5.0"
|
APP_VERSION = "v5.0"
|
||||||
|
|
||||||
def _app_dir() -> Path:
|
def _app_dir() -> Path:
|
||||||
@@ -305,6 +305,8 @@ class App:
|
|||||||
|
|
||||||
# --- Contrôle d'arrêt ---
|
# --- Contrôle d'arrêt ---
|
||||||
self._stop_requested = False
|
self._stop_requested = False
|
||||||
|
# --- Fichier unique (None = mode dossier) ---
|
||||||
|
self._single_file: Optional[Path] = None
|
||||||
|
|
||||||
# --- Construction UI ---
|
# --- Construction UI ---
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
@@ -382,7 +384,7 @@ class App:
|
|||||||
|
|
||||||
tk.Label(
|
tk.Label(
|
||||||
main,
|
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",
|
font=self._f_body, bg=CLR_BG, fg=CLR_TEXT_SECONDARY, anchor="w",
|
||||||
).pack(fill=tk.X, padx=pad_x, pady=(0, 18))
|
).pack(fill=tk.X, padx=pad_x, pady=(0, 18))
|
||||||
|
|
||||||
@@ -414,7 +416,7 @@ class App:
|
|||||||
|
|
||||||
self._folder_text_lbl = tk.Label(
|
self._folder_text_lbl = tk.Label(
|
||||||
self._folder_inner,
|
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,
|
font=self._f_body, bg=CLR_CARD_BG, fg=CLR_TEXT_SECONDARY,
|
||||||
)
|
)
|
||||||
self._folder_text_lbl.pack(pady=(4, 0))
|
self._folder_text_lbl.pack(pady=(4, 0))
|
||||||
@@ -448,7 +450,7 @@ class App:
|
|||||||
|
|
||||||
tk.Label(
|
tk.Label(
|
||||||
info_inner,
|
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 Sortie PDF Image (raster) — sécurité maximale, aucun texte résiduel\n"
|
||||||
"\u2022 Résultats dans le dossier « anonymise/ » à la racine"),
|
"\u2022 Résultats dans le dossier « anonymise/ » à la racine"),
|
||||||
font=self._f_card_desc, bg=CLR_BLUE_LIGHT, fg=CLR_TEXT_SECONDARY,
|
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))
|
buttons_frame.pack(fill=tk.X, padx=pad_x, pady=(0, 4))
|
||||||
|
|
||||||
self.btn_run = tk.Button(
|
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",
|
font=self._f_button, bg=CLR_PRIMARY, fg="white",
|
||||||
activebackground="#1d4ed8", activeforeground="white",
|
activebackground="#1d4ed8", activeforeground="white",
|
||||||
relief=tk.FLAT, cursor="hand2", pady=10,
|
relief=tk.FLAT, cursor="hand2", pady=10,
|
||||||
@@ -648,29 +650,71 @@ class App:
|
|||||||
# Actions dossier
|
# Actions dossier
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
def _browse(self):
|
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()
|
d = filedialog.askdirectory()
|
||||||
if d:
|
if d:
|
||||||
|
self._single_file = None
|
||||||
self.dir_var.set(d)
|
self.dir_var.set(d)
|
||||||
self._update_folder_display()
|
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):
|
def _update_folder_display(self):
|
||||||
folder = self.dir_var.get()
|
folder = self.dir_var.get()
|
||||||
if not folder:
|
if not folder:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
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)
|
# Compter les documents supportés (récursif)
|
||||||
try:
|
try:
|
||||||
from format_converter import SUPPORTED_EXTENSIONS
|
from format_converter import SUPPORTED_EXTENSIONS
|
||||||
except ImportError:
|
except ImportError:
|
||||||
SUPPORTED_EXTENSIONS = {".pdf"}
|
SUPPORTED_EXTENSIONS = {".pdf"}
|
||||||
pdf_count = 0
|
doc_count = 0
|
||||||
try:
|
try:
|
||||||
pdf_count = len([
|
doc_count = len([
|
||||||
p for p in Path(folder).rglob("*")
|
p for p in Path(folder).rglob("*")
|
||||||
if p.is_file() and p.suffix.lower() in SUPPORTED_EXTENSIONS
|
if p.is_file() and p.suffix.lower() in SUPPORTED_EXTENSIONS
|
||||||
])
|
])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
display_label = folder
|
||||||
|
|
||||||
# Vider et reconstruire l'intérieur
|
# Vider et reconstruire l'intérieur
|
||||||
for w in self._folder_inner.winfo_children():
|
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 = tk.Frame(self._folder_inner, bg=CLR_CARD_BG)
|
||||||
row.pack(fill=tk.X)
|
row.pack(fill=tk.X)
|
||||||
|
|
||||||
|
icon = "\U0001f4c4" if is_single else "\U0001f4c2" # 📄 ou 📂
|
||||||
tk.Label(
|
tk.Label(
|
||||||
row, text="\U0001f4c2", font=(self._font_family, 16),
|
row, text=icon, font=(self._font_family, 16),
|
||||||
bg=CLR_CARD_BG,
|
bg=CLR_CARD_BG,
|
||||||
).pack(side=tk.LEFT, padx=(0, 8))
|
).pack(side=tk.LEFT, padx=(0, 8))
|
||||||
|
|
||||||
@@ -688,7 +733,7 @@ class App:
|
|||||||
info_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
info_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||||
|
|
||||||
# Chemin (tronqué si trop long)
|
# Chemin (tronqué si trop long)
|
||||||
display_path = folder
|
display_path = display_label
|
||||||
if len(display_path) > 60:
|
if len(display_path) > 60:
|
||||||
display_path = "..." + display_path[-57:]
|
display_path = "..." + display_path[-57:]
|
||||||
tk.Label(
|
tk.Label(
|
||||||
@@ -696,9 +741,13 @@ class App:
|
|||||||
bg=CLR_CARD_BG, fg=CLR_TEXT, anchor="w",
|
bg=CLR_CARD_BG, fg=CLR_TEXT, anchor="w",
|
||||||
).pack(fill=tk.X)
|
).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(
|
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",
|
font=self._f_small, bg=CLR_CARD_BG, fg=CLR_TEXT_SECONDARY, anchor="w",
|
||||||
).pack(fill=tk.X)
|
).pack(fill=tk.X)
|
||||||
|
|
||||||
@@ -716,14 +765,24 @@ class App:
|
|||||||
# Lancement
|
# Lancement
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
def _run(self):
|
def _run(self):
|
||||||
|
is_single = getattr(self, '_single_file', None) is not None
|
||||||
|
|
||||||
|
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())
|
folder = Path(self.dir_var.get().strip())
|
||||||
if not folder.is_dir():
|
if not folder.is_dir():
|
||||||
messagebox.showwarning(
|
messagebox.showwarning(
|
||||||
"Dossier invalide",
|
"Dossier invalide",
|
||||||
"Choisissez un dossier contenant des documents.",
|
"Choisissez un dossier ou un fichier.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from format_converter import SUPPORTED_EXTENSIONS
|
from format_converter import SUPPORTED_EXTENSIONS
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
Reference in New Issue
Block a user