Retour Dom : remplacer la page vitrine par un vrai éditeur de profils. - gui_v6/profile_editor.py : couche logique (build_profile_spec, profile_is_editable runtime vs defaut, list_profile_choices, slug_for_copy, save/set_default/delete) au-dessus de profile_defaults — persistance dans config/profiles.yml. - gui_v6/editable_list.py : EditableTermList (tableau scrollable de termes, ajout/suppression, pas de pastilles) — reste lisible à 50+ termes. - tab_config : sous-onglet « 👤 Profils » réintroduit comme éditeur — menu déroulant « Profil à modifier », boutons Nouveau / Dupliquer / Enregistrer / Annuler / Définir par défaut, sections Identité, Masquage (require_manual_mask, template), Moteurs (force_disable_vlm), Mots (à masquer/conserver/ignorer éditables), Règles « à venir ». Profils défaut = lecture seule (dupliquer pour modifier). Confirmation non bloquante (pas de modale). - Réglages : bouton « ✏️ Modifier le profil… » → ouvre Profils sur le profil actif. Pas de pastilles inline. Persiste : label, description, require_manual_mask, force_disable_vlm, preferred_manual_mask_template, param_lists (3 listes). 260 tests unit OK (0 régression), self-test OK, nav 5 sous-onglets + thème OK. Préserve 1bbe70a/d30f7b7. Aucun build/push sans GO Dom. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
4.0 KiB
Python
102 lines
4.0 KiB
Python
"""Liste de termes éditable et scrollable (pas de pastilles).
|
||
|
||
Utilisée dans l'éditeur de profils pour « mots à masquer / à conserver / à
|
||
ignorer ». Reste lisible avec 50+ termes (zone scrollable + ajout/suppression).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import customtkinter as ctk
|
||
|
||
from gui_v6 import ui_kit
|
||
|
||
|
||
class EditableTermList(ctk.CTkFrame):
|
||
def __init__(self, master, palette: dict, *, title: str, initial=None, height: int = 150, **kwargs):
|
||
super().__init__(master, fg_color="transparent", **kwargs)
|
||
self._palette = palette
|
||
self._title = title
|
||
self._terms: list[str] = [str(t) for t in (initial or [])]
|
||
self._editable = True
|
||
self._build(height)
|
||
self._render()
|
||
|
||
# -- API testable --------------------------------------------------------
|
||
def terms(self) -> list[str]:
|
||
return list(self._terms)
|
||
|
||
def set_terms(self, terms) -> None:
|
||
self._terms = [str(t) for t in (terms or [])]
|
||
self._render()
|
||
|
||
def set_editable(self, editable: bool) -> None:
|
||
self._editable = bool(editable)
|
||
state = "normal" if self._editable else "disabled"
|
||
self._entry.configure(state=state)
|
||
self._add_btn.configure(state=state)
|
||
self._render()
|
||
|
||
def add_term(self, term: str) -> bool:
|
||
term = str(term).strip()
|
||
if not term or term in self._terms:
|
||
return False
|
||
self._terms.append(term)
|
||
self._render()
|
||
return True
|
||
|
||
def remove_term(self, term: str) -> None:
|
||
if term in self._terms:
|
||
self._terms.remove(term)
|
||
self._render()
|
||
|
||
# -- UI ------------------------------------------------------------------
|
||
def _build(self, height: int) -> None:
|
||
p = self._palette
|
||
ctk.CTkLabel(self, text=self._title, text_color=p["text"], font=ui_kit.font(12, "bold"), anchor="w").pack(
|
||
fill="x", pady=(0, 2)
|
||
)
|
||
row = ctk.CTkFrame(self, fg_color="transparent")
|
||
row.pack(fill="x", pady=(0, 4))
|
||
self._entry = ctk.CTkEntry(
|
||
row, placeholder_text="Ajouter un terme…", fg_color=p["btn_sec_bg"],
|
||
border_color=p["btn_sec_border"], text_color=p["text"], height=28,
|
||
)
|
||
self._entry.pack(side="left", fill="x", expand=True, padx=(0, 6))
|
||
self._entry.bind("<Return>", lambda _e: self._on_add())
|
||
self._add_btn = ui_kit.secondary_button(row, p, "+ Ajouter", command=self._on_add)
|
||
self._add_btn.pack(side="right")
|
||
self._list = ctk.CTkScrollableFrame(self, fg_color=p["divider"], height=height)
|
||
self._list.pack(fill="both", expand=True)
|
||
self._count = ctk.CTkLabel(self, text="", text_color=p["text_muted"], font=ui_kit.font(10), anchor="w")
|
||
self._count.pack(fill="x", pady=(2, 0))
|
||
|
||
def _on_add(self) -> None:
|
||
if not self._editable:
|
||
return
|
||
if self.add_term(self._entry.get()):
|
||
self._entry.delete(0, "end")
|
||
|
||
def _render(self) -> None:
|
||
p = self._palette
|
||
for child in self._list.winfo_children():
|
||
child.destroy()
|
||
if not self._terms:
|
||
ctk.CTkLabel(self._list, text="Aucun terme.", text_color=p["text_muted"], font=ui_kit.font(11)).pack(
|
||
anchor="w", padx=8, pady=6
|
||
)
|
||
for term in self._terms:
|
||
line = ctk.CTkFrame(self._list, fg_color="transparent")
|
||
line.pack(fill="x", pady=1)
|
||
ctk.CTkLabel(line, text=term, text_color=p["text"], font=ui_kit.font(12), anchor="w").pack(
|
||
side="left", fill="x", expand=True, padx=(6, 4)
|
||
)
|
||
btn = ctk.CTkButton(
|
||
line, text="×", width=26, height=24, corner_radius=6,
|
||
fg_color=p["btn_sec_bg"], hover_color=p["card_border"], text_color=p["text"],
|
||
command=lambda t=term: self.remove_term(t),
|
||
)
|
||
if not self._editable:
|
||
btn.configure(state="disabled")
|
||
btn.pack(side="right", padx=(0, 4))
|
||
self._count.configure(text=f"{len(self._terms)} terme(s)")
|