feat(gui): addenda Dom GUI V6 — sous-onglet Profils, libellés, aide, bêta

Suite des retours Dom sur la GUI V6 (par-dessus 6a0a581).

Addendum Profils / Réglages :
- Nouveau sous-onglet Administration « 👤 Profils » : le profil actif devient
  un objet lisible (nom, description, masque requis, template, listes locales
  avec compteurs) — données réelles lues depuis profile_defaults.
- Fenêtre « Tableau des termes » (terms_table_window.py) : table scrollable
  avec recherche/filtre, colonnes Type/Terme/Source ; reste lisible à 50+
  termes. Ajouter/éditer/supprimer désactivés « (à venir) » (écriture par
  profil non câblée).
- Réglages : « Profil métier » → « Profil d'anonymisation », « Sortie… » →
  « Dossier de sortie… » (+ infobulle), hints moteurs (standard/optionnel/
  plus lent), bouton « Voir le profil », « Ouvrir le tableau des termes ».
- Aide « ? » + infobulles (ui_kit.attach_tooltip) près des éléments ambigus.
- profile_view.py : logique pure (résumé profil + lignes du tableau),
  testable sans display.

Addendum bêta : en-tête « aivanonym » + badge « bêta », titre fenêtre
« … — bêta ». Détail version conservé dans À propos.

tests/unit/test_gui_v6_profiles.py + ajouts shell. 237 tests unit OK
(228 → 237, 0 régression), self-test GUI V6 OK, navigation des 5 sous-onglets
+ thème OK. V5/moteur/app_aivanov/profile_defaults non touchés, 0 dépendance.
Aucun build/push sans GO Dom — validation visuelle Dom attendue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:02:54 +02:00
parent 6a0a5811a5
commit a9e8b2c2e6
8 changed files with 597 additions and 12 deletions

View File

@@ -7,6 +7,7 @@ Les widgets ne sont créés qu'à l'appel (import sûr pour ``--self-test``).
from __future__ import annotations
import tkinter as tk
from typing import Optional
import customtkinter as ctk
@@ -231,3 +232,70 @@ class HelpButton(ctk.CTkButton):
def help_button(master, palette: dict, text: str, title: str = "Aide") -> "HelpButton":
return HelpButton(master, palette, text, title=title)
class Tooltip:
"""Infobulle au survol (façon V5 ``ToolTip``), pour les éléments ambigus."""
def __init__(self, widget, text: str, delay: int = 450):
self.widget = widget
self.text = text
self.delay = delay
self._tip = None
self._after = None
widget.bind("<Enter>", self._schedule, add="+")
widget.bind("<Leave>", self.hide, add="+")
widget.bind("<ButtonPress>", self.hide, add="+")
def _schedule(self, *_):
self._cancel()
try:
self._after = self.widget.after(self.delay, self.show)
except Exception:
pass
def _cancel(self):
if self._after is not None:
try:
self.widget.after_cancel(self._after)
except Exception:
pass
self._after = None
def show(self, *_):
if self._tip is not None or not self.text:
return self._tip
try:
x = self.widget.winfo_rootx() + 16
y = self.widget.winfo_rooty() + self.widget.winfo_height() + 4
except Exception:
return None
self._tip = tw = tk.Toplevel(self.widget)
tw.wm_overrideredirect(True)
tw.wm_geometry(f"+{x}+{y}")
tk.Label(
tw,
text=self.text,
justify="left",
background="#1f2937",
foreground="#f9fafb",
relief="solid",
borderwidth=1,
wraplength=320,
padx=8,
pady=5,
).pack()
return tw
def hide(self, *_):
self._cancel()
if self._tip is not None:
try:
self._tip.destroy()
except Exception:
pass
self._tip = None
def attach_tooltip(widget, text: str, delay: int = 450) -> "Tooltip":
return Tooltip(widget, text, delay)