diff --git a/gui_v6/tabs/tab_config.py b/gui_v6/tabs/tab_config.py index 5d0ba86..d6697ed 100644 --- a/gui_v6/tabs/tab_config.py +++ b/gui_v6/tabs/tab_config.py @@ -224,6 +224,7 @@ class ConfigTab(ctk.CTkFrame): self._profiles_path = None self._pro_edit_key: str | None = None self._pro_term_lists: dict = {} + self._profile_scroll = None self._build() @@ -491,10 +492,10 @@ class ConfigTab(ctk.CTkFrame): p = self._p from gui_v6.editable_list import EditableTermList - content = ctk.CTkScrollableFrame(parent, fg_color="transparent") - content.pack(fill="both", expand=True) - self._profile_scroll = content - parent = content + # L'application fournit déjà un scroll vertical global. Un second + # CTkScrollableFrame imbriqué coupe la page Profils sous Windows et + # laisse un grand bloc vide en bas de fenêtre. + self._profile_scroll = None self._section_intro( parent, @@ -547,8 +548,25 @@ class ConfigTab(ctk.CTkFrame): ctk.CTkLabel(ident, text="Description", text_color=p["text_muted"], font=ui_kit.font(11), anchor="w").pack(fill="x", padx=12, pady=(0, 2)) self._pro_desc_entry.pack(fill="x", padx=12, pady=(0, 12)) + eng = ui_kit.Card( + right, p, title="🧠 Moteurs", + help_text=_HELP_PROFIL_MOTEURS, help_title="Moteurs du profil", + ) + eng.pack(fill="x", pady=(0, 8)) + self._pro_disable_vlm_var = ctk.BooleanVar(value=False) + self._pro_vlm_switch = ctk.CTkSwitch(eng, text="Désactiver le moteur VLM (images)", variable=self._pro_disable_vlm_var, progress_color=p["primary"], text_color=p["text"], font=ui_kit.font(12)) + self._pro_vlm_switch.pack(anchor="w", padx=12, pady=(0, 6)) + # Note honnête : reflète les moteurs réellement embarqués par ce build. + caps_pro = engine_capabilities.capabilities_map() + opt = [c.label.split(" (")[0] for c in (caps_pro["eds"], caps_pro["gliner"]) if c.available] + if opt: + moteurs_note = "CamemBERT-bio (standard) toujours actif ; " + " / ".join(opt) + " disponibles (optionnels)." + else: + moteurs_note = "CamemBERT-bio (standard) toujours actif ; EDS-Pseudo / GLiNER non embarqués dans cette version." + ctk.CTkLabel(eng, text=moteurs_note, text_color=p["text_muted"], font=ui_kit.font(11), anchor="w", wraplength=300, justify="left").pack(fill="x", padx=12, pady=(0, 12)) + mask = ui_kit.Card( - left, p, title="⬛ Masquage", + right, p, title="⬛ Masquage", help_text=_HELP_PROFIL_MASQUAGE, help_title="Masquage manuel", ) mask.pack(fill="x", pady=(0, 8)) @@ -591,32 +609,15 @@ class ConfigTab(ctk.CTkFrame): variable=self._mask_rounded_var, command=self._on_rounded_corners, ).pack(fill="x", padx=12, pady=(2, 12)) - eng = ui_kit.Card( - left, p, title="🧠 Moteurs", - help_text=_HELP_PROFIL_MOTEURS, help_title="Moteurs du profil", - ) - eng.pack(fill="x") - self._pro_disable_vlm_var = ctk.BooleanVar(value=False) - self._pro_vlm_switch = ctk.CTkSwitch(eng, text="Désactiver le moteur VLM (images)", variable=self._pro_disable_vlm_var, progress_color=p["primary"], text_color=p["text"], font=ui_kit.font(12)) - self._pro_vlm_switch.pack(anchor="w", padx=12, pady=(0, 6)) - # Note honnête : reflète les moteurs réellement embarqués par ce build. - caps_pro = engine_capabilities.capabilities_map() - opt = [c.label.split(" (")[0] for c in (caps_pro["eds"], caps_pro["gliner"]) if c.available] - if opt: - moteurs_note = "CamemBERT-bio (standard) toujours actif ; " + " / ".join(opt) + " disponibles (optionnels)." - else: - moteurs_note = "CamemBERT-bio (standard) toujours actif ; EDS-Pseudo / GLiNER non embarqués dans cette version." - ctk.CTkLabel(eng, text=moteurs_note, text_color=p["text_muted"], font=ui_kit.font(11), anchor="w", wraplength=300, justify="left").pack(fill="x", padx=12, pady=(0, 12)) - words = ui_kit.Card( - right, p, title="📝 Mots du profil", + parent, p, title="📝 Mots du profil", help_text=_HELP_PROFIL_MOTS, help_title="Mots du profil", ) - words.pack(fill="both", expand=True) + words.pack(fill="x", pady=(8, 0)) self._pro_term_lists = { - "blacklist": EditableTermList(words, p, title="À masquer", height=104), - "whitelist": EditableTermList(words, p, title="À conserver", height=104), - "stopwords": EditableTermList(words, p, title="À ignorer", height=88), + "blacklist": EditableTermList(words, p, title="À masquer", height=78), + "whitelist": EditableTermList(words, p, title="À conserver", height=78), + "stopwords": EditableTermList(words, p, title="À ignorer", height=66), } for tl in self._pro_term_lists.values(): tl.pack(fill="x", padx=12, pady=(0, 8)) diff --git a/tests/unit/test_gui_v6_profiles.py b/tests/unit/test_gui_v6_profiles.py index a0741c5..359ed57 100644 --- a/tests/unit/test_gui_v6_profiles.py +++ b/tests/unit/test_gui_v6_profiles.py @@ -162,8 +162,8 @@ def test_profils_editor_creates_and_persists(ctk_root, tmp_path, monkeypatch): tab.destroy() -def test_profils_panel_has_dedicated_mouse_scroll(ctk_root, tmp_path, monkeypatch): - """Retour Dom : le formulaire Profils doit pouvoir défiler à la molette.""" +def test_profils_panel_uses_outer_scroll_without_nested_scroll(ctk_root, tmp_path, monkeypatch): + """Retour Dom : le formulaire Profils ne doit pas être coupé par un scroll imbriqué.""" import customtkinter as ctk from gui_v6.tabs import tab_config @@ -173,8 +173,12 @@ def test_profils_panel_has_dedicated_mouse_scroll(ctk_root, tmp_path, monkeypatc tab._show_sub("pro") tab.update_idletasks() - assert isinstance(tab._profile_scroll, ctk.CTkScrollableFrame) - assert hasattr(tab._profile_scroll, "_parent_canvas") + assert tab._profile_scroll is None + assert not any(isinstance(child, ctk.CTkScrollableFrame) for child in tab._panels["pro"].winfo_children()) + texts = " | ".join(_all_texts(tab._panels["pro"])) + assert "Masque manuel obligatoire" in texts + assert "Désactiver le moteur VLM" in texts + assert "Règles d'anonymisation portées par ce profil" in texts tab.destroy()