feat(gui): onglet Profils éditable (création/modification/persistance)
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>
This commit is contained in:
101
gui_v6/editable_list.py
Normal file
101
gui_v6/editable_list.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""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)")
|
||||
Reference in New Issue
Block a user