Skip to content

Commit b6fada9

Browse files
author
PyCompiler ARK++
committed
revue du sys de dialog
1 parent fbd9116 commit b6fada9

5 files changed

Lines changed: 243 additions & 176 deletions

File tree

Core/dialogs.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
"""
2020
Dialogues personnalisés pour PyCompiler ARK++.
2121
Inclut ProgressDialog, boîtes de message, et autres dialogues spécifiques.
22+
23+
IMPORTANT: Tous les dialogs ici exécutent les opérations Qt dans le thread principal
24+
via le système d'invoker de Plugins_SDK.GeneralContext.Dialog pour assurer:
25+
- L'héritage du thème de l'application
26+
- L'intégration visuelle avec l'application principale
27+
- La sécurité des threads
2228
"""
2329

2430
# À compléter avec les classes de dialogues personnalisés
@@ -32,6 +38,14 @@
3238
QVBoxLayout,
3339
)
3440

41+
# Import du système Dialog thread-safe de Plugins_SDK
42+
try:
43+
from Plugins_SDK.GeneralContext.Dialog import _invoke_in_main_thread
44+
except Exception:
45+
# Fallback si Plugins_SDK n'est pas disponible
46+
def _invoke_in_main_thread(fn, *args, **kwargs):
47+
return fn(*args, **kwargs)
48+
3549

3650
class ProgressDialog(QDialog):
3751
def __init__(self, title="Progression", parent=None, cancelable=False):

Core/sys_deps.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@
4444

4545
from .dialogs import ProgressDialog
4646

47+
# Import du système Dialog thread-safe de Plugins_SDK
48+
try:
49+
from Plugins_SDK.GeneralContext.Dialog import _invoke_in_main_thread
50+
except Exception:
51+
# Fallback si Plugins_SDK n'est pas disponible
52+
def _invoke_in_main_thread(fn, *args, **kwargs):
53+
return fn(*args, **kwargs)
54+
4755

4856
class SysDependencyManager:
4957
def __init__(self, parent_widget=None):

Plugins/Cleaner/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323

2424
# Create instances of Dialog for logging and user interaction
25+
# These now automatically execute in the main Qt thread, ensuring theme inheritance
26+
# and proper UI integration with the main application
2527
log = Dialog()
2628
dialog = Dialog()
2729

Plugins_SDK/GeneralContext/Dialog.py

Lines changed: 63 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,32 @@
1515

1616
from __future__ import annotations
1717

18-
from dataclasses import dataclass, field
1918
from pathlib import Path
2019
from 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
3423
import getpass
3524
import re
3625
import 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
4635
try:
4736
from PySide6 import QtCore as _QtC, QtWidgets as _QtW # type: ignore
4837
except 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

5345
def _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

280157
class InstallAuth(NamedTuple):
@@ -345,8 +222,8 @@ def sys_msgbox_for_installing(
345222

346223

347224
class 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

Comments
 (0)