3737from PySide6 .QtGui import QColor , QShortcut , QKeySequence , QPalette
3838from PySide6 .QtWidgets import (
3939 QAbstractItemView ,
40+ QApplication ,
4041 QCheckBox ,
4142 QDialog ,
4243 QFrame ,
44+ QGroupBox ,
4345 QHBoxLayout ,
4446 QLabel ,
4547 QListWidget ,
5456 QWidget ,
5557)
5658
59+ # ---------------------------------------------------------------------------
60+ # Helpers Thème
61+ # ---------------------------------------------------------------------------
62+
63+ def _is_dark () -> bool :
64+ """Détermine si le thème actuel est sombre."""
65+ try :
66+ from Ui .Gui .UiConnection import _is_qss_dark
67+ app = QApplication .instance ()
68+ return _is_qss_dark (app .styleSheet () if app else "" )
69+ except Exception :
70+ return False
71+
72+
73+ def _get_bcasl_colors () -> dict [str , str ]:
74+ """Retourne les couleurs adaptées au thème (soumis au thème)."""
75+ app = QApplication .instance ()
76+ pal = app .palette () if app else QPalette ()
77+
78+ # Récupérer les couleurs système pour une intégration parfaite
79+ # PlaceholderText est excellent pour les labels secondaires (gris dynamique)
80+ secondary_text = pal .color (QPalette .PlaceholderText ).name ()
81+ # AlternateBase est fait pour le contraste de fond dans les listes/groupes
82+ alt_bg = pal .color (QPalette .AlternateBase ).name ()
83+
84+ if _is_dark ():
85+ return {
86+ "warn_bg" : "#504010" , # Ambre sombre mais distinct du fond ("noir")
87+ "warn_border" : "#D4A017" , # Orange/Or
88+ "section_bg" : alt_bg ,
89+ "section_label" : secondary_text ,
90+ }
91+ return {
92+ "warn_bg" : "#FFF3CD" , # Jaune pâle
93+ "warn_border" : "orange" ,
94+ "section_bg" : alt_bg ,
95+ "section_label" : secondary_text ,
96+ }
97+
98+
99+ def _apply_themed_icon (widget : QPushButton , icon_name : str , size : int = 18 ) -> None :
100+ """Applique une icône SVG thémée au widget."""
101+ try :
102+ from Ui .Gui .UiConnection import themed_svg_icon
103+ from PySide6 .QtCore import QSize
104+ icon_path = str (Path (__file__ ).parent .parent .parent .parent / "icons" / icon_name )
105+ icon = themed_svg_icon (icon_path , size = size )
106+ if icon :
107+ widget .setIcon (icon )
108+ widget .setIconSize (QSize (size , size ))
109+ except Exception :
110+ pass
111+
112+
57113# ---------------------------------------------------------------------------
58114# Constantes
59115# ---------------------------------------------------------------------------
69125 100 : ("Défaut" , 60 , 199 ),
70126}
71127
72- _WARN_BG = "#FFF3CD" # fond jaune pâle
73- _WARN_BORDER = "orange" # bordure spinbox hors-plage
74- _SECTION_BG = "#F5F5F5" # fond section (clair)
75-
76128# Tag → score de phase
77129_TAG_PRIORITY_MAP : dict [str , int ] = {}
78130try :
@@ -209,15 +261,17 @@ def __init__(
209261 row .addWidget (self .spin )
210262
211263 # Boutons ↑ ↓
212- self .btn_up = QPushButton ("↑" )
213- self .btn_up .setFixedWidth (28 )
264+ self .btn_up = QPushButton ()
265+ self .btn_up .setFixedWidth (32 )
214266 self .btn_up .setToolTip ("Monter dans la section" )
267+ _apply_themed_icon (self .btn_up , "chevron-up.svg" , size = 18 )
215268 self .btn_up .clicked .connect (lambda : self .sig_move_up .emit (self .pid ))
216269 row .addWidget (self .btn_up )
217270
218- self .btn_down = QPushButton ("↓" )
219- self .btn_down .setFixedWidth (28 )
271+ self .btn_down = QPushButton ()
272+ self .btn_down .setFixedWidth (32 )
220273 self .btn_down .setToolTip ("Descendre dans la section" )
274+ _apply_themed_icon (self .btn_down , "chevron-down.svg" , size = 18 )
221275 self .btn_down .clicked .connect (lambda : self .sig_move_down .emit (self .pid ))
222276 row .addWidget (self .btn_down )
223277
@@ -237,7 +291,8 @@ def _refresh_warning(self) -> None:
237291
238292 if expert or not out_of_range :
239293 self .lbl_warn .setVisible (False )
240- self .setStyleSheet ("" )
294+ # Utilise un fond transparent pour laisser voir le fond de la section
295+ self .setStyleSheet ("QFrame#PluginRow { background: transparent; border: none; }" )
241296 self .spin .setStyleSheet ("" )
242297 else :
243298 self .lbl_warn .setVisible (True )
@@ -246,8 +301,9 @@ def _refresh_warning(self) -> None:
246301 "L'ordre d'exécution peut être inattendu.\n "
247302 "Utilisez le mode Expert pour désactiver les avertissements."
248303 )
249- self .setStyleSheet (f"QFrame#PluginRow {{ background: { _WARN_BG } ; }}" )
250- self .spin .setStyleSheet (f"QSpinBox {{ border: 2px solid { _WARN_BORDER } ; }}" )
304+ colors = _get_bcasl_colors ()
305+ self .setStyleSheet (f"QFrame#PluginRow {{ background: { colors ['warn_bg' ]} ; border: 1px solid { colors ['warn_border' ]} ; border-radius: 4px; }}" )
306+ self .spin .setStyleSheet (f"QSpinBox {{ border: 2px solid { colors ['warn_border' ]} ; }}" )
251307
252308 def refresh_expert (self ) -> None :
253309 """Appelé quand expert mode change."""
@@ -281,8 +337,8 @@ def restore(self, snap: dict[str, Any]) -> None:
281337# Widget : section collapsible
282338# ---------------------------------------------------------------------------
283339
284- class _SectionWidget (QFrame ):
285- """Section collapsible représentant une catégorie de plugins ."""
340+ class _SectionWidget (QGroupBox ):
341+ """Section collapsible utilisant QGroupBox pour une meilleure intégration thématique ."""
286342
287343 sig_changed = Signal ()
288344
@@ -302,45 +358,27 @@ def __init__(
302358 self ._max = max_prio
303359 self ._expert_ref = expert_ref
304360 self ._rows : list [_PluginRow ] = []
305- self ._collapsed = False
306361
307- self .setFrameShape (QFrame .StyledPanel )
308- self .setStyleSheet (f"QFrame {{ background: { _SECTION_BG } ; border-radius: 4px; }}" )
309-
310- outer = QVBoxLayout (self )
311- outer .setContentsMargins (4 , 4 , 4 , 4 )
312- outer .setSpacing (2 )
313-
314- # En-tête
315- header = QFrame ()
316- header_lay = QHBoxLayout (header )
317- header_lay .setContentsMargins (4 , 2 , 4 , 2 )
318-
319- self ._toggle_btn = QPushButton ("▼" )
320- self ._toggle_btn .setFlat (True )
321- self ._toggle_btn .setFixedWidth (24 )
322- self ._toggle_btn .clicked .connect (self ._toggle_collapse )
323- header_lay .addWidget (self ._toggle_btn )
324-
325- self ._title = QLabel (f"<b>{ name } </b> <small style='color:#888'>({ min_prio } –{ max_prio } )</small>" )
326- self ._title .setTextFormat (Qt .RichText )
327- header_lay .addWidget (self ._title )
328- header_lay .addStretch (1 )
329- outer .addWidget (header )
330-
331- # Zone de contenu (plugins)
332- self ._content = QWidget ()
333- self ._content_lay = QVBoxLayout (self ._content )
334- self ._content_lay .setContentsMargins (8 , 2 , 4 , 4 )
362+ # Titre natif du GroupBox
363+ self .setTitle (f"{ name } ({ min_prio } –{ max_prio } )" )
364+
365+ # Rendre le GroupBox collapsible via la checkbox native
366+ self .setCheckable (True )
367+ self .setChecked (True )
368+ self .toggled .connect (self ._on_toggled )
369+
370+ self ._content_lay = QVBoxLayout (self )
371+ self ._content_lay .setContentsMargins (8 , 12 , 8 , 8 )
335372 self ._content_lay .setSpacing (3 )
336- outer .addWidget (self ._content )
337373
338374 # ------------------------------------------------------------------
339375
340- def _toggle_collapse (self ) -> None :
341- self ._collapsed = not self ._collapsed
342- self ._content .setVisible (not self ._collapsed )
343- self ._toggle_btn .setText ("▶" if self ._collapsed else "▼" )
376+ def _on_toggled (self , checked : bool ) -> None :
377+ """Cache/affiche les lignes de plugins quand on toggle le GroupBox."""
378+ for i in range (self ._content_lay .count ()):
379+ item = self ._content_lay .itemAt (i )
380+ if item and item .widget ():
381+ item .widget ().setVisible (checked )
344382
345383 def add_row (self , row : _PluginRow ) -> None :
346384 self ._rows .append (row )
@@ -349,6 +387,10 @@ def add_row(self, row: _PluginRow) -> None:
349387 row .sig_move_down .connect (self ._on_move_down )
350388 row .sig_enabled .connect (lambda _pid , _v : self .sig_changed .emit ())
351389 row .sig_priority .connect (lambda _pid , _v : self .sig_changed .emit ())
390+ # S'assurer que la visibilité suit l'état actuel du toggle
391+ row .setVisible (self .isChecked ())
392+ # Mettre à jour l'état des boutons ↑/↓ pour toute la section
393+ self ._refresh_arrow_buttons ()
352394
353395 def _on_move_up (self , pid : str ) -> None :
354396 idx = self ._find_idx (pid )
@@ -479,9 +521,6 @@ def _build_ui(self) -> None:
479521 lbl .setTextFormat (Qt .RichText )
480522 title_row .addWidget (lbl )
481523 title_row .addStretch (1 )
482- btn_save_top = QPushButton (self ._gui .tr ("Enregistrer" , "Save" ))
483- btn_save_top .clicked .connect (self ._do_save )
484- title_row .addWidget (btn_save_top )
485524 main .addLayout (title_row )
486525
487526 # Tabs : Pipeline + onglets plugins
@@ -496,7 +535,12 @@ def _build_ui(self) -> None:
496535 scroll = QScrollArea ()
497536 scroll .setWidgetResizable (True )
498537 scroll .setFrameShape (QFrame .NoFrame )
538+ scroll .setStyleSheet ("QScrollArea { background: transparent; }" )
539+
499540 container = QWidget ()
541+ container .setObjectName ("PipelineContainer" )
542+ container .setStyleSheet ("QWidget#PipelineContainer { background: transparent; }" )
543+
500544 self ._pipeline_lay = QVBoxLayout (container )
501545 self ._pipeline_lay .setSpacing (8 )
502546 self ._pipeline_lay .setContentsMargins (4 , 4 , 4 , 4 )
@@ -521,10 +565,12 @@ def _build_ui(self) -> None:
521565 bottom .addStretch (1 )
522566
523567 btn_cancel = QPushButton (self ._gui .tr ("Annuler" , "Cancel" ))
568+ _apply_themed_icon (btn_cancel , "x-circle.svg" )
524569 btn_cancel .clicked .connect (self .reject )
525570 bottom .addWidget (btn_cancel )
526571
527572 btn_save = QPushButton (self ._gui .tr ("Enregistrer dans bcasl.yml" , "Save to bcasl.yml" ))
573+ _apply_themed_icon (btn_save , "save.svg" )
528574 btn_save .setDefault (True )
529575 btn_save .clicked .connect (self ._do_save )
530576 bottom .addWidget (btn_save )
0 commit comments