"""Onglet « Configuration » de la GUI V6 (G4 — alignement maquette). Sous-navigation Réglages / Masquage / Partage / Règles (cf. maquette). Le sous-onglet Réglages édite un :class:`ConfigState` partagé (profil, NER local, moteurs). Les autres sous-onglets reprennent le style maquette (cartes denses). Aucune logique de détection ; ``config_defaults.py`` n'est pas modifié. """ from __future__ import annotations from pathlib import Path from tkinter import filedialog import customtkinter as ctk from gui_v6 import theme as theme_mod from gui_v6 import ui_kit from gui_v6.config_state import ConfigState, default_profile_key, list_profile_keys _SUBTABS = [("reg", "⚙️ Réglages"), ("msk", "🎭 Masquage"), ("shr", "🔄 Partage"), ("rul", "🛡️ Règles")] class ConfigTab(ctk.CTkFrame): def __init__(self, master, state: ConfigState | None = None, palette: dict | None = None, **kwargs): self._p = palette or theme_mod.get_palette(theme_mod.DEFAULT_THEME) super().__init__(master, fg_color=self._p["bg"], **kwargs) self._state = state if state is not None else ConfigState() self._sub = "reg" self._sub_buttons: dict = {} self._build() @property def state(self) -> ConfigState: return self._state def _build(self) -> None: p = self._p bar = ctk.CTkFrame(self, fg_color="transparent") bar.pack(fill="x", padx=14, pady=(14, 4)) for key, label in _SUBTABS: btn = ctk.CTkButton( bar, text=label, command=lambda k=key: self._show_sub(k), fg_color="transparent", hover_color=p["card_border"], text_color=p["primary"] if key == self._sub else p["text_dim"], font=ui_kit.font(13, "bold" if key == self._sub else "normal"), corner_radius=0, width=10, ) btn.pack(side="left", padx=3) self._sub_buttons[key] = btn self._body = ctk.CTkFrame(self, fg_color="transparent") self._body.pack(fill="both", expand=True) self._show_sub("reg") def _show_sub(self, key: str) -> None: self._sub = key p = self._p for k, btn in self._sub_buttons.items(): btn.configure(text_color=p["primary"] if k == key else p["text_dim"], font=ui_kit.font(13, "bold" if k == key else "normal")) for w in self._body.winfo_children(): w.destroy() {"reg": self._build_reglages, "msk": self._build_masquage, "shr": self._build_partage, "rul": self._build_regles}[key]() # -- Réglages (câblé ConfigState) ------------------------------------- def _build_reglages(self) -> None: p = self._p # Profil + sortie prof = ui_kit.Card(self._body, p, title="🗂️ Profil métier") prof.pack(fill="x", padx=14, pady=7) row = ctk.CTkFrame(prof, fg_color="transparent") row.pack(fill="x", padx=16, pady=(0, 10)) profiles = list_profile_keys() current = self._state.profile or default_profile_key() or (profiles[0] if profiles else "") self._state.profile = current or None ctk.CTkLabel(row, text="Profil :", text_color=p["text"], font=ui_kit.font(13)).pack(side="left", padx=(0, 8)) menu = ctk.CTkOptionMenu(row, values=profiles or ["(aucun profil)"], command=self._on_profile, fg_color=p["btn_sec_bg"], button_color=p["primary"], text_color=p["text"]) if current: menu.set(current) menu.pack(side="left") outrow = ctk.CTkFrame(prof, fg_color="transparent") outrow.pack(fill="x", padx=16, pady=(0, 14)) ui_kit.secondary_button(outrow, p, "📁 Dossier de sortie…", command=self._pick_output).pack(side="left", padx=(0, 8)) self._out_label = ctk.CTkLabel(outrow, text=str(self._state.output_dir or "(défaut anonymise/)"), text_color=p["text_muted"], font=ui_kit.font(12)) self._out_label.pack(side="left") # Moteurs NER (câblé) ner = ui_kit.Card(self._body, p, title="🧠 Moteurs NER") ner.pack(fill="x", padx=14, pady=7) self._tog_ner = ui_kit.ToggleRow(ner, p, "CamemBERT-bio ⚡", "~10 ms/doc · F1 = 0.963", value=self._state.use_local_ner, command=self._on_ner) self._tog_ner.pack(fill="x", padx=16, pady=2) self._tog_eds = ui_kit.ToggleRow(ner, p, "EDS-Pseudo", "médical français (optionnel)", value=self._state.enable_eds, command=self._on_eds) self._tog_eds.pack(fill="x", padx=16, pady=2) self._tog_gli = ui_kit.ToggleRow(ner, p, "GLiNER", "vote croisé (optionnel)", value=self._state.enable_gliner, command=self._on_gliner) self._tog_gli.pack(fill="x", padx=16, pady=(2, 14)) # Données à détecter (visuel) det = ui_kit.Card(self._body, p, title="🔍 Données à détecter") det.pack(fill="x", padx=14, pady=7) for label, hint in [ ("Noms et prénoms", "Gazetteers INSEE · CamemBERT"), ("Dates de naissance", "Uniquement la date de naissance"), ("Établissements", "Répertoire FINESS + contexte"), ("Adresses et codes postaux", ""), ("N° sécurité sociale", ""), ("Téléphones et e-mails", ""), ]: ui_kit.ToggleRow(det, p, label, hint, value=True).pack(fill="x", padx=16, pady=2) ctk.CTkFrame(det, fg_color="transparent", height=8).pack() def _build_masquage(self) -> None: p = self._p card = ui_kit.Card(self._body, p, title="🎭 Masquage") card.pack(fill="x", padx=14, pady=7) ctk.CTkLabel(card, text="Couleur, style des marqueurs et éditeur de masques PDF.", text_color=p["text_dim"], font=ui_kit.font(12), anchor="w", justify="left").pack(anchor="w", padx=16) codes = ui_kit.Card(self._body, p, title="🔒 Codes de remplacement") codes.pack(fill="x", padx=14, pady=7) for k, v in [("Nom/Prénom", "[NOM]"), ("Date naissance", "[DATE_NAISSANCE]"), ("Établissement", "[ETABLISSEMENT]"), ("Adresse", "[ADRESSE]"), ("N° sécu", "[NIR]")]: r = ctk.CTkFrame(codes, fg_color="transparent") r.pack(fill="x", padx=16, pady=1) ctk.CTkLabel(r, text=k, text_color=p["text_muted"], font=ui_kit.font(12)).pack(side="left") ctk.CTkLabel(r, text=v, text_color=p["primary"], font=ui_kit.font(12, "bold")).pack(side="right") ctk.CTkFrame(codes, fg_color="transparent", height=8).pack() ctk.CTkLabel(self._body, text="Éditeur de masques PDF (zones fixes) : intégré au lot suivant.", text_color=p["text_muted"], font=ui_kit.font(11)).pack(anchor="w", padx=18, pady=6) def _build_partage(self) -> None: p = self._p card = ui_kit.Card(self._body, p, title="🔄 Partage de configuration") card.pack(fill="x", padx=14, pady=7) ctk.CTkLabel(card, text="Exporter / importer la configuration (whitelist, blacklist, profils) par JSON.", text_color=p["text_dim"], font=ui_kit.font(12), anchor="w", justify="left").pack(anchor="w", padx=16, pady=(0, 8)) actrow = ctk.CTkFrame(card, fg_color="transparent") actrow.pack(fill="x", padx=16, pady=(0, 14)) ui_kit.secondary_button(actrow, p, "⬇ Exporter", command=lambda: None).pack(side="left", padx=(0, 8)) ui_kit.secondary_button(actrow, p, "⬆ Importer", command=lambda: None).pack(side="left") def _build_regles(self) -> None: p = self._p card = ui_kit.Card(self._body, p, title="🛡️ Règles personnalisées") card.pack(fill="x", padx=14, pady=7) ctk.CTkLabel(card, text="Règles système (lecture seule) et règles personnalisées de l'établissement.", text_color=p["text_dim"], font=ui_kit.font(12), anchor="w", justify="left").pack(anchor="w", padx=16, pady=(0, 14)) # -- callbacks -------------------------------------------------------- def _on_profile(self, value: str) -> None: self._state.profile = value def _on_ner(self) -> None: self._state.use_local_ner = self._tog_ner.get() def _on_eds(self) -> None: self._state.enable_eds = self._tog_eds.get() def _on_gliner(self) -> None: self._state.enable_gliner = self._tog_gli.get() def _pick_output(self) -> None: path = filedialog.askdirectory(title="Dossier de sortie") if path: self._state.output_dir = Path(path) self._out_label.configure(text=str(self._state.output_dir))