1515
1616from __future__ import annotations
1717
18- from dataclasses import dataclass , field
1918from pathlib import Path
2019from typing import (
21- Any ,
22- Callable ,
23- Dict ,
24- Iterable ,
25- List ,
2620 Optional ,
27- Sequence ,
28- Tuple ,
29- Union ,
3021 NamedTuple ,
3122)
32- import fnmatch
33- import json
3423import getpass
3524import re
3625import platform
37- import io
3826
3927# Simple redaction of obvious secrets in logs
4028_REDACT_PATTERNS = [
4129 re .compile (r"(password\s*[:=]\s*)([^\s]+)" , re .IGNORECASE ),
4230 re .compile (r"(authorization\s*[:]\s*bearer\s+)([A-Za-z0-9\-_.]+)" , re .IGNORECASE ),
4331 re .compile (r"(token\s*[:=]\s*)([A-Za-z0-9\-_.]{12,})" , re .IGNORECASE ),
4432]
33+
4534# Qt toolkits
4635try :
4736 from PySide6 import QtCore as _QtC , QtWidgets as _QtW # type: ignore
4837except Exception : # pragma: no cover
4938 _QtW = None # type: ignore
5039 _QtC = None # type: ignore
5140
41+ # Import des classes de Core.dialogs
42+ from Core .dialogs import ProgressDialog , CompilationProcessDialog , _invoke_in_main_thread
43+
5244
5345def _redact_secrets (text : str ) -> str :
5446 if not text :
@@ -100,6 +92,7 @@ def show_msgbox(
10092):
10193 """
10294 Show a message box if a Qt toolkit is available; fallback to console output otherwise.
95+ Executes in the main Qt thread to ensure theme inheritance and proper UI integration.
10396
10497 kind: 'info' | 'warning' | 'error' | 'question'
10598 Returns:
@@ -116,165 +109,49 @@ def show_msgbox(
116109 else False
117110 )
118111 return None
119- try :
120- parent = parent or _qt_active_parent ()
121- mb = _QtW .QMessageBox (parent )
122- mb .setWindowTitle (str (title ))
123- mb .setText (str (text ))
124- if kind == "warning" :
125- mb .setIcon (_QtW .QMessageBox .Warning )
126- elif kind == "error" :
127- mb .setIcon (_QtW .QMessageBox .Critical )
128- elif kind == "question" :
129- mb .setIcon (_QtW .QMessageBox .Question )
130- else :
131- mb .setIcon (_QtW .QMessageBox .Information )
132-
133- if kind == "question" :
134- yes = _QtW .QMessageBox .Yes
135- no = _QtW .QMessageBox .No
136- mb .setStandardButtons (yes | no )
137- if default and str (default ).lower () == "no" :
138- mb .setDefaultButton (no )
112+
113+ def _show_in_main_thread ():
114+ try :
115+ parent_widget = parent or _qt_active_parent ()
116+ mb = _QtW .QMessageBox (parent_widget )
117+ mb .setWindowTitle (str (title ))
118+ mb .setText (str (text ))
119+ if kind == "warning" :
120+ mb .setIcon (_QtW .QMessageBox .Warning )
121+ elif kind == "error" :
122+ mb .setIcon (_QtW .QMessageBox .Critical )
123+ elif kind == "question" :
124+ mb .setIcon (_QtW .QMessageBox .Question )
139125 else :
140- mb .setDefaultButton (yes )
141- res = mb .exec_ () if hasattr (mb , "exec_" ) else mb .exec ()
142- return res == yes
143- else :
144- ok = _QtW .QMessageBox .Ok
145- mb .setStandardButtons (ok )
146- mb .setDefaultButton (ok )
147- _ = mb .exec_ () if hasattr (mb , "exec_" ) else mb .exec ()
148- return None
149- except Exception :
150- print (f"[MSGBOX:{ kind } ] { title } : { text } " )
151- if kind == "question" :
152- return (
153- True
154- if (default and str (default ).lower () in ("yes" , "ok" , "true" , "1" ))
155- else False
156- )
157- return None
158-
159-
160- class ProgressHandle :
161- """Gestion d'une progression avec Qt (QProgressDialog) si disponible, sinon fallback console.
162-
163- Utilisation typique:
164- ph = create_progress("Installation", "Téléchargement...", maximum=100)
165- for i in range(101):
166- ph.update(i, f"Étape {i}/100")
167- ph.close()
168- """
169-
170- def __init__ (
171- self ,
172- title : str = "" ,
173- text : str = "" ,
174- maximum : int = 0 ,
175- cancelable : bool = False ,
176- ) -> None :
177- self ._title = str (title )
178- self ._text = str (text )
179- self .maximum = int (maximum ) if maximum else 0
180- self .value = 0
181- self ._cancelable = bool (cancelable )
182- self ._canceled = False
183- self ._dlg = None
184- self ._last_pct = - 1
185- if _QtW is not None :
186- try :
187- parent = _qt_active_parent ()
188- dlg = _QtW .QProgressDialog (parent )
189- dlg .setWindowTitle (self ._title or "Progression" )
190- dlg .setLabelText (self ._text or self ._title )
191- if self ._cancelable :
192- dlg .setCancelButtonText ("Annuler" )
193- else :
194- dlg .setCancelButton (None )
195- dlg .setAutoClose (False )
196- dlg .setAutoReset (False )
197- if self .maximum > 0 :
198- dlg .setRange (0 , self .maximum )
126+ mb .setIcon (_QtW .QMessageBox .Information )
127+
128+ if kind == "question" :
129+ yes = _QtW .QMessageBox .Yes
130+ no = _QtW .QMessageBox .No
131+ mb .setStandardButtons (yes | no )
132+ if default and str (default ).lower () == "no" :
133+ mb .setDefaultButton (no )
199134 else :
200- # Indéterminée (busy)
201- dlg .setRange (0 , 0 )
202- dlg .setMinimumDuration (0 )
203- try :
204- dlg .canceled .connect (self ._on_canceled ) # type: ignore[attr-defined]
205- except Exception :
206- pass
207- dlg .show ()
208- self ._dlg = dlg
209- except Exception :
210- self ._dlg = None
211-
212- def _on_canceled (self ) -> None : # Qt signal
213- self ._canceled = True
214-
215- @property
216- def canceled (self ) -> bool :
217- return self ._canceled
218-
219- def set_maximum (self , maximum : int ) -> None :
220- self .maximum = int (maximum ) if maximum else 0
221- if self ._dlg is not None :
222- try :
223- if self .maximum > 0 :
224- self ._dlg .setRange (0 , self .maximum )
225- else :
226- self ._dlg .setRange (0 , 0 )
227- except Exception :
228- pass
229-
230- def update (self , value : Optional [int ] = None , text : Optional [str ] = None ) -> None :
231- if value is not None :
232- self .value = max (0 , int (value ))
233- if text is not None :
234- self ._text = str (text )
235- if self ._dlg is not None :
236- try :
237- if text is not None :
238- self ._dlg .setLabelText (self ._text )
239- if self .maximum > 0 :
240- self ._dlg .setValue (min (self .value , self .maximum ))
241- _ = _QtW .QApplication .processEvents () # type: ignore[attr-defined]
242- except Exception :
243- pass
244- else :
245- # Fallback console simple (évite le spam)
246- if self .maximum > 0 :
247- pct = int (min (100 , max (0 , (self .value * 100 ) / self .maximum )))
248- if pct != self ._last_pct :
249- print (f"[PROGRESS] { self ._title } : { pct } % - { self ._text } " )
250- self ._last_pct = pct
135+ mb .setDefaultButton (yes )
136+ res = mb .exec_ () if hasattr (mb , "exec_" ) else mb .exec ()
137+ return res == yes
251138 else :
252- print (f"[PROGRESS] { self ._title } : { self ._text } " )
253-
254- def step (self , delta : int = 1 ) -> None :
255- self .update (self .value + int (delta ))
256-
257- def close (self ) -> None :
258- if self ._dlg is not None :
259- try :
260- # Valeur finale si bornée
261- if self .maximum > 0 :
262- self ._dlg .setValue (self .maximum )
263- self ._dlg .reset ()
264- self ._dlg .close ()
265- except Exception :
266- pass
267- finally :
268- self ._dlg = None
269-
270-
271- def create_progress (
272- title : str , text : str = "" , maximum : int = 0 , cancelable : bool = False
273- ) -> ProgressHandle :
274- """Crée un handle de progression (Qt si dispo, sinon console)."""
275- return ProgressHandle (
276- title = title , text = text , maximum = maximum , cancelable = cancelable
277- )
139+ ok = _QtW .QMessageBox .Ok
140+ mb .setStandardButtons (ok )
141+ mb .setDefaultButton (ok )
142+ _ = mb .exec_ () if hasattr (mb , "exec_" ) else mb .exec ()
143+ return None
144+ except Exception :
145+ print (f"[MSGBOX:{ kind } ] { title } : { text } " )
146+ if kind == "question" :
147+ return (
148+ True
149+ if (default and str (default ).lower () in ("yes" , "ok" , "true" , "1" ))
150+ else False
151+ )
152+ return None
153+
154+ return _invoke_in_main_thread (_show_in_main_thread )
278155
279156
280157class InstallAuth (NamedTuple ):
@@ -345,8 +222,8 @@ def sys_msgbox_for_installing(
345222
346223
347224class Dialog :
225+ """Dialog class for plugins - uses Core.dialogs classes for all UI operations."""
348226
349- # methode de boîtes de Dialog pour permettre une interaction Ui avec les Plugins
350227 def show_msgbox (
351228 self , kind : str , title : str , text : str , * , default : Optional [str ] = None
352229 ) -> Optional [bool ]:
@@ -396,11 +273,21 @@ def sys_msgbox_for_installing(
396273
397274 def progress (
398275 self , title : str , text : str = "" , maximum : int = 0 , cancelable : bool = False
399- ) -> "ProgressHandle" :
400- """Crée et retourne un handle de progression utilisable pour suivre une tâche.
401- - maximum=0 => progression indéterminée (busy)
402- - cancelable=True => bouton Annuler (si Qt disponible)
276+ ) -> ProgressDialog :
277+ """Crée et retourne un ProgressDialog de Core pour suivre une tâche.
278+
279+ Utilise directement Core.dialogs.ProgressDialog pour assurer:
280+ - L'héritage du thème de l'application
281+ - L'intégration visuelle avec l'application principale
282+ - La sécurité des threads
283+
284+ Args:
285+ title: Titre du dialog
286+ text: Texte initial du dialog
287+ maximum: Valeur maximale (0 = indéterminé)
288+ cancelable: Si True, affiche un bouton Annuler
289+
290+ Returns:
291+ ProgressDialog instance from Core.dialogs
403292 """
404- return create_progress (
405- title = title , text = text , maximum = maximum , cancelable = cancelable
406- )
293+ return ProgressDialog (title = title , cancelable = cancelable )
0 commit comments