Skip to content

Commit f2390d5

Browse files
committed
feat(cli): add --bcasl option for standalone pipeline editor and improve startup stability
1 parent 731cfa6 commit f2390d5

8 files changed

Lines changed: 213 additions & 174 deletions

File tree

Ui/Cli/launchers.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def launch_main_application(
5959
classic_gui: bool = False,
6060
bcasl_gui: bool = False,
6161
) -> int:
62+
if bcasl_gui:
63+
os.environ["PYCOMPILER_BCASL_MODE"] = "1"
64+
6265
try:
6366
from PySide6.QtCore import Qt, QTimer
6467
from PySide6.QtGui import QColor, QPixmap
@@ -69,8 +72,12 @@ def launch_main_application(
6972
app = _get_or_create_qapp()
7073
set_app_icon(app)
7174
app_version = _get_app_version()
75+
76+
# Désactiver le splash screen pour le mode BCASL seul pour plus de réactivité
77+
effective_no_splash = no_splash or bcasl_gui
78+
7279
splash = None
73-
if not no_splash:
80+
if not effective_no_splash:
7481
try:
7582
logo_dir = os.path.join(ROOT_DIR, "images")
7683
safe_ver = "".join(
@@ -119,6 +126,18 @@ def launch_main_application(
119126
except Exception:
120127
splash = None
121128

129+
def _cleanup():
130+
try:
131+
from bcasl.Loader import ensure_bcasl_thread_stopped
132+
from Core.Globals import _latest_gui_instance
133+
target = _latest_gui_instance
134+
if target:
135+
ensure_bcasl_thread_stopped(target)
136+
except Exception:
137+
pass
138+
139+
app.aboutToQuit.connect(_cleanup)
140+
122141
if splash is not None:
123142
delay_ms = 4000
124143
try:
@@ -134,16 +153,22 @@ def _launch_main():
134153
os.environ["PYCOMPILER_UI_VARIANT"] = "ide2"
135154
window = PyCompilerArkGui()
136155
set_window_icon(window)
137-
window.show()
138-
156+
139157
if bcasl_gui:
140158
try:
141159
from bcasl.Loader import open_bc_loader_dialog
142160

143161
window.apply_workspace_selection(os.getcwd(), source="cli")
144-
open_bc_loader_dialog(window)
162+
dlg = open_bc_loader_dialog(window)
163+
if dlg:
164+
dlg.finished.connect(app.quit)
165+
else:
166+
app.quit()
145167
except Exception as e:
146168
warn(f"Failed to open BCASL dialog: {e}")
169+
app.quit()
170+
else:
171+
window.show()
147172

148173
_apply_small_screen_compaction(app, window)
149174
try:
@@ -161,16 +186,24 @@ def _launch_main():
161186
os.environ["PYCOMPILER_UI_VARIANT"] = "ide2"
162187
window = PyCompilerArkGui()
163188
set_window_icon(window)
164-
window.show()
165-
189+
166190
if bcasl_gui:
167191
try:
168192
from bcasl.Loader import open_bc_loader_dialog
169193

170194
window.apply_workspace_selection(os.getcwd(), source="cli")
171-
open_bc_loader_dialog(window)
195+
dlg = open_bc_loader_dialog(window)
196+
if dlg:
197+
# Si le dialogue est fermé, on quitte l'application entière
198+
dlg.finished.connect(app.quit)
199+
else:
200+
# Si le dialogue n'a pas pu être ouvert, on quitte
201+
QTimer.singleShot(0, app.quit)
172202
except Exception as e:
173203
warn(f"Failed to open BCASL dialog: {e}")
204+
QTimer.singleShot(0, app.quit)
205+
else:
206+
window.show()
174207

175208
_apply_small_screen_compaction(app, window)
176209

Ui/Gui/Dialogs/BcaslDialog.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
- Sections collapsibles par catégorie (tag → phase)
2121
- Spinbox priorité + ⚠️ hors-plage
2222
- ↑ / ↓ intra-section, DnD intra-section
23-
- Expert mode
2423
- Ctrl+S / Ctrl+Z / Ctrl+Y / Space / ↑↓
2524
- Format bcasl.yml : plugins en liste [{name, enabled, priority, config}]
2625
"""
@@ -269,8 +268,6 @@ def __init__(
269268
"QFrame#PluginRow { background: transparent; border: none; }"
270269
)
271270

272-
# ------------------------------------------------------------------
273-
274271
# ------------------------------------------------------------------
275272
# Accesseurs
276273

@@ -375,10 +372,6 @@ def _refresh_arrow_buttons(self) -> None:
375372
row.btn_up.setEnabled(i > 0)
376373
row.btn_down.setEnabled(i < n - 1)
377374

378-
def refresh_expert(self) -> None:
379-
for row in self._rows:
380-
row.refresh_expert()
381-
382375
def snapshot(self) -> list[dict]:
383376
return [r.snapshot() for r in self._rows]
384377

@@ -675,7 +668,12 @@ def _build_plugin_config_tabs(self) -> None:
675668
title = getattr(getattr(plugin, "meta", None), "name", None) or pid
676669
self._tabs.addTab(widget, str(title))
677670
self._plugin_ui_state[pid] = {"config": base_cfg, "on_save": on_save}
678-
except Exception:
671+
except Exception as e:
672+
try:
673+
if hasattr(self._gui, "log") and self._gui.log:
674+
self._gui.log.append(f"❌ Erreur build_config_tab pour {pid}: {e}\n")
675+
except Exception:
676+
print(f"Erreur build_config_tab pour {pid}: {e}")
679677
continue
680678

681679
# ------------------------------------------------------------------
@@ -687,15 +685,6 @@ def _install_shortcuts(self) -> None:
687685
QShortcut(QKeySequence("Ctrl+Z"), self, activated=self._do_undo)
688686
QShortcut(QKeySequence("Ctrl+Y"), self, activated=self._do_redo)
689687

690-
# ------------------------------------------------------------------
691-
# Expert mode
692-
# ------------------------------------------------------------------
693-
694-
def _on_expert_toggled(self, checked: bool) -> None:
695-
self._expert[0] = checked
696-
for section in self._sections:
697-
section.refresh_expert()
698-
699688
# ------------------------------------------------------------------
700689
# Undo / Redo
701690
# ------------------------------------------------------------------
@@ -915,20 +904,23 @@ def on_finished(self, rep) -> None:
915904
try:
916905
if rep and hasattr(self._gui, "log") and self._gui.log is not None:
917906
self._gui.log.append("BCASL - Rapport:\n")
918-
for item in rep:
919-
try:
920-
state = (
921-
"OK"
922-
if getattr(item, "success", False)
923-
else f"FAIL: {getattr(item, 'error', '')}"
924-
)
925-
dur = getattr(item, "duration_ms", 0.0)
926-
pid = getattr(item, "plugin_id", "?")
927-
self._gui.log.append(f" - {pid}: {state} ({dur:.1f} ms)\n")
928-
except Exception:
929-
pass
907+
# Vérifier si rep est itérable (liste de ExecutionItem)
908+
if isinstance(rep, (list, tuple)):
909+
for item in rep:
910+
try:
911+
state = (
912+
"OK"
913+
if getattr(item, "success", False)
914+
else f"FAIL: {getattr(item, 'error', '')}"
915+
)
916+
dur = getattr(item, "duration_ms", 0.0)
917+
pid = getattr(item, "plugin_id", "?")
918+
self._gui.log.append(f" - {pid}: {state} ({dur:.1f} ms)\n")
919+
except Exception:
920+
pass
930921
try:
931-
self._gui.log.append(rep.summary() + "\n")
922+
if hasattr(rep, "summary"):
923+
self._gui.log.append(rep.summary() + "\n")
932924
except Exception:
933925
pass
934926
try:
@@ -1009,14 +1001,16 @@ def open_bcasl_pipeline_dialog(
10091001
meta_map: "dict[str, dict[str, Any]]",
10101002
cfg: "dict[str, Any]",
10111003
plugin_instances: "dict[str, Any]",
1012-
) -> None:
1004+
) -> Optional[BcaslPipelineDialog]:
10131005
"""Ouvre le dialog BCASL Pipeline."""
10141006
dlg = BcaslPipelineDialog(gui, workspace_root, meta_map, cfg, plugin_instances)
10151007
try:
10161008
dlg.setModal(False)
10171009
dlg.show()
1010+
return dlg
10181011
except Exception:
10191012
try:
10201013
dlg.exec()
1014+
return dlg
10211015
except Exception:
1022-
pass
1016+
return None

Ui/Gui/Dialogs/VenvDialog.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ def _ask_recreate_invalid_venv(self, venv_root: str, reason: str) -> bool:
118118
Returns True if the user accepted, False otherwise.
119119
"""
120120
try:
121+
if os.environ.get("PYCOMPILER_BCASL_MODE") == "1":
122+
# En mode BCASL, on recrée silencieusement si invalide pour éviter de bloquer le lancement
123+
return True
124+
121125
title = "Environnement virtuel invalide / Invalid virtual environment"
122126
folder = os.path.basename(os.path.normpath(venv_root))
123127
msg = (
@@ -142,6 +146,10 @@ def _ask_recreate_invalid_venv(self, venv_root: str, reason: str) -> bool:
142146
def _show_error_dialog(self, title: str, text: str) -> None:
143147
"""Show a critical QMessageBox error dialog."""
144148
try:
149+
if os.environ.get("PYCOMPILER_BCASL_MODE") == "1":
150+
# Logger l'erreur au lieu d'afficher une boîte de dialogue
151+
self._ui_log("error", f"❌ {title}: {text}")
152+
return
145153
QMessageBox.critical(self.parent, title, text)
146154
except Exception:
147155
pass

Ui/Gui/Dialogs/WorkspaceDialog.py

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -110,24 +110,28 @@ def apply_workspace_selection(
110110
"""
111111
try:
112112
# Étape 1: afficher un feedback utilisateur pendant le chargement.
113-
try:
114-
loading_dialog = CompilationProcessDialog(
115-
gui_instance.tr(
116-
"Chargement de l'espace de travail", "Loading workspace"
117-
),
118-
gui_instance,
119-
)
120-
loading_dialog.set_status(
121-
gui_instance.tr(
122-
"📁 Chargement de l'espace de travail...",
123-
"📁 Loading workspace...",
113+
loading_dialog = None
114+
is_bcasl_mode = os.environ.get("PYCOMPILER_BCASL_MODE") == "1"
115+
116+
if not is_bcasl_mode:
117+
try:
118+
loading_dialog = CompilationProcessDialog(
119+
gui_instance.tr(
120+
"Chargement de l'espace de travail", "Loading workspace"
121+
),
122+
gui_instance,
124123
)
125-
)
126-
loading_dialog.btn_cancel.setEnabled(False)
127-
loading_dialog.show()
128-
QApplication.processEvents()
129-
except Exception:
130-
loading_dialog = None
124+
loading_dialog.set_status(
125+
gui_instance.tr(
126+
"📁 Chargement de l'espace de travail...",
127+
"📁 Loading workspace...",
128+
)
129+
)
130+
loading_dialog.btn_cancel.setEnabled(False)
131+
loading_dialog.show()
132+
QApplication.processEvents()
133+
except Exception:
134+
loading_dialog = None
131135

132136
# Étape 2: valider/préparer le dossier workspace (via Core).
133137
if not folder:
@@ -181,13 +185,13 @@ def apply_workspace_selection(
181185
pass
182186

183187
# Étape 6: mettre à jour les widgets UI.
184-
if hasattr(gui_instance, "label_folder"):
188+
if getattr(gui_instance, "label_folder", None):
185189
gui_instance.label_folder.setText(
186190
gui_instance.tr(
187191
f"Dossier sélectionné : {folder}", f"Selected folder: {folder}"
188192
)
189193
)
190-
if hasattr(gui_instance, "label_workspace_status"):
194+
if getattr(gui_instance, "label_workspace_status", None):
191195
try:
192196
tr_map = getattr(gui_instance, "_tr", None)
193197
if isinstance(tr_map, dict):
@@ -207,8 +211,9 @@ def apply_workspace_selection(
207211
pass
208212

209213
gui_instance.python_files.clear()
210-
if hasattr(gui_instance, "file_list"):
211-
gui_instance.file_list.clear()
214+
file_list = getattr(gui_instance, "file_list", None)
215+
if file_list is not None:
216+
file_list.clear()
212217

213218
# Scanner les fichiers (via Core)
214219
files = SetupWorkspace.list_python_files(folder)
@@ -239,9 +244,9 @@ def apply_workspace_selection(
239244
continue
240245

241246
gui_instance.python_files.append(full_path)
242-
if hasattr(gui_instance, "file_list"):
247+
if file_list is not None:
243248
relative_path = os.path.relpath(full_path, folder)
244-
gui_instance.file_list.addItem(relative_path)
249+
file_list.addItem(relative_path)
245250

246251
added_count += 1
247252
if added_count % 200 == 0:
@@ -269,7 +274,7 @@ def apply_workspace_selection(
269274

270275
# Étape 7: gérer le Venv (interactions UI)
271276
if hasattr(gui_instance, "venv_manager") and gui_instance.venv_manager:
272-
if str(source).lower() == "plugin":
277+
if str(source).lower() == "plugin" or is_bcasl_mode:
273278
gui_instance.venv_manager.setup_workspace(folder, check_tools=False)
274279
else:
275280
if not gui_instance.venv_manager.apply_workspace_pref(folder):

0 commit comments

Comments
 (0)