Skip to content

Commit d15e458

Browse files
committed
feat: implement VenvManagerUI for enhanced venv management in GUI
1 parent f0c4b1c commit d15e458

3 files changed

Lines changed: 291 additions & 253 deletions

File tree

Core/Venv_Manager/Manager.py

Lines changed: 48 additions & 251 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import yaml
1111

1212
from PySide6.QtCore import QProcess, QTimer
13-
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox
13+
from PySide6.QtWidgets import QApplication
1414

1515
from ..WidgetsCreator import ProgressDialog
1616
from Ui.i18n import log_i18n_level, log_with_level
@@ -64,6 +64,9 @@ def __init__(self, parent_widget):
6464
self.venv_check_progress = None
6565
self.progress_dialog = None
6666

67+
# UI delegate callbacks — registered by VenvManagerUI (Ui layer)
68+
self._ui_callbacks: dict = {}
69+
6770
# Internal timers to enforce timeouts on background processes
6871
self._proc_timers: list[QTimer] = []
6972

@@ -85,6 +88,17 @@ def __init__(self, parent_widget):
8588
# User-driven cancellation flag for long-running async flows
8689
self._cancel_requested = False
8790

91+
# ---------- UI delegate ----------
92+
def _call_ui(self, method: str, *args, **kwargs):
93+
"""Invoke a registered UI callback by name. Returns None if no delegate registered."""
94+
fn = self._ui_callbacks.get(method)
95+
if callable(fn):
96+
try:
97+
return fn(*args, **kwargs)
98+
except Exception:
99+
pass
100+
return None
101+
88102
# ---------- Workspace pref (.ark/pref.json) ----------
89103
def _workspace_pref_path(self, workspace_dir: str) -> str:
90104
"""Return the resolved workspace path information."""
@@ -162,19 +176,8 @@ def apply_workspace_pref(self, workspace_dir: str) -> bool:
162176
setattr(self.parent, "venv_path", None)
163177
except Exception:
164178
pass
165-
try:
166-
if hasattr(self.parent, "venv_label") and self.parent.venv_label:
167-
self.parent.venv_label.setText(self._pref_label_system())
168-
except Exception:
169-
pass
170-
try:
171-
if (
172-
hasattr(self.parent, "venv_path_edit")
173-
and self.parent.venv_path_edit
174-
):
175-
self.parent.venv_path_edit.setText(self._pref_label_system())
176-
except Exception:
177-
pass
179+
self._call_ui("update_venv_label", self._pref_label_system())
180+
self._call_ui("update_venv_path_edit", self._pref_label_system())
178181
return True
179182
if mode == "venv" and isinstance(venv_path, str) and venv_path:
180183
venv_path = os.path.abspath(venv_path)
@@ -193,24 +196,8 @@ def apply_workspace_pref(self, workspace_dir: str) -> bool:
193196
setattr(self.parent, "venv_path", venv_path)
194197
except Exception:
195198
pass
196-
try:
197-
if (
198-
hasattr(self.parent, "venv_label")
199-
and self.parent.venv_label
200-
):
201-
self.parent.venv_label.setText(
202-
f"Venv sélectionné : {venv_path}"
203-
)
204-
except Exception:
205-
pass
206-
try:
207-
if (
208-
hasattr(self.parent, "venv_path_edit")
209-
and self.parent.venv_path_edit
210-
):
211-
self.parent.venv_path_edit.setText(venv_path)
212-
except Exception:
213-
pass
199+
self._call_ui("update_venv_label", f"Venv sélectionné : {venv_path}")
200+
self._call_ui("update_venv_path_edit", venv_path)
214201
return True
215202
self._clear_workspace_pref(workspace_dir)
216203
return False
@@ -877,58 +864,35 @@ def _safe_mkdir(self, path: str) -> bool:
877864
return False
878865

879866
def _prompt_recreate_invalid_venv(self, venv_root: str, reason: str) -> bool:
880-
"""Show an English message box explaining the invalid venv and propose deletion/recreation.
881-
Returns True if user accepted to recreate, False otherwise.
867+
"""Ask the UI delegate whether to delete and recreate an invalid venv.
868+
If the user confirms, performs deletion then triggers recreation (business logic).
869+
Returns True if recreation was initiated, False otherwise.
882870
"""
871+
confirmed = self._call_ui("ask_recreate_invalid_venv", venv_root, reason)
872+
if not confirmed:
873+
return False
874+
# Business logic: delete the bad venv
883875
try:
884-
title = "Environnement virtuel invalide / Invalid virtual environment"
885-
folder = os.path.basename(os.path.normpath(venv_root))
886-
msg = (
887-
"L'environnement virtuel du workspace est invalide :\n"
888-
f"- {reason}\n\n"
889-
f"Voulez-vous supprimer le dossier '{folder}' et le recréer ?\n\n"
890-
"The workspace virtual environment is invalid:\n"
891-
f"- {reason}\n\n"
892-
f"Do you want to delete the '{folder}' folder and recreate it?"
893-
)
894-
reply = QMessageBox.question(
895-
self.parent,
896-
title,
897-
msg,
898-
QMessageBox.Yes | QMessageBox.No,
899-
QMessageBox.No,
876+
shutil.rmtree(venv_root)
877+
self._safe_log(f"🗑️ Deleted invalid venv: {venv_root}")
878+
except Exception as e:
879+
self._call_ui(
880+
"show_error_dialog",
881+
"Environnement virtuel invalide / Invalid virtual environment",
882+
f"Échec suppression venv / Failed to delete venv: {e}",
900883
)
901-
if reply == QMessageBox.Yes:
902-
try:
903-
shutil.rmtree(venv_root)
904-
self._safe_log(f"🗑️ Deleted invalid venv: {venv_root}")
905-
except Exception as e:
906-
try:
907-
QMessageBox.critical(
908-
self.parent,
909-
title,
910-
f"Échec suppression venv / Failed to delete venv: {e}",
911-
)
912-
except Exception:
913-
pass
914-
return False
915-
# Recreate fresh venv under the workspace
916-
try:
917-
workspace_dir = os.path.dirname(venv_root)
918-
self.create_venv_if_needed(workspace_dir)
919-
return True
920-
except Exception as e:
921-
try:
922-
QMessageBox.critical(
923-
self.parent,
924-
title,
925-
f"Échec de recréation du venv / Failed to recreate venv: {e}",
926-
)
927-
except Exception:
928-
pass
929-
return False
930884
return False
931-
except Exception:
885+
# Business logic: recreate
886+
try:
887+
workspace_dir = os.path.dirname(venv_root)
888+
self.create_venv_if_needed(workspace_dir)
889+
return True
890+
except Exception as e:
891+
self._call_ui(
892+
"show_error_dialog",
893+
"Environnement virtuel invalide / Invalid virtual environment",
894+
f"Échec de recréation du venv / Failed to recreate venv: {e}",
895+
)
932896
return False
933897

934898
# ---------- Venv validation ----------
@@ -1248,28 +1212,7 @@ def _apply_system_python(self) -> None:
12481212
self.parent.venv_path_manuel = None
12491213
except Exception:
12501214
pass
1251-
try:
1252-
if hasattr(self.parent, "venv_label") and self.parent.venv_label:
1253-
label = None
1254-
try:
1255-
tr = (
1256-
getattr(self.parent, "_tr", {})
1257-
if hasattr(self.parent, "_tr")
1258-
else {}
1259-
)
1260-
label = (
1261-
tr.get("venv_label_system") if isinstance(tr, dict) else None
1262-
)
1263-
except Exception:
1264-
label = None
1265-
if not label:
1266-
label = self.parent.tr(
1267-
"Venv sélectionné : Python système",
1268-
"Venv selected: System Python",
1269-
)
1270-
self.parent.venv_label.setText(label)
1271-
except Exception:
1272-
pass
1215+
self._call_ui("update_venv_label", self._pref_label_system())
12731216
try:
12741217
self._safe_log("✅ Utilisation de Python système pour la compilation.")
12751218
except Exception:
@@ -1280,148 +1223,6 @@ def _apply_system_python(self) -> None:
12801223
except Exception:
12811224
pass
12821225

1283-
# ---------- Manual selection ----------
1284-
def select_venv_manually(self):
1285-
"""Execute select_venv_manually logic for this component."""
1286-
try:
1287-
ok_sys, missing, has_source = self._can_use_system_python()
1288-
if ok_sys:
1289-
title = self.parent.tr("Suggestion de venv", "Venv suggestion")
1290-
if has_source:
1291-
msg = self.parent.tr(
1292-
"Python système contient les dépendances nécessaires.\n"
1293-
"Souhaitez-vous l'utiliser ?",
1294-
"System Python has the required dependencies.\n"
1295-
"Do you want to use it?",
1296-
)
1297-
else:
1298-
msg = self.parent.tr(
1299-
"Aucun fichier de dépendances détecté.\n"
1300-
"Souhaitez-vous utiliser Python système ?",
1301-
"No dependency file detected.\n"
1302-
"Do you want to use System Python?",
1303-
)
1304-
reply = QMessageBox.question(
1305-
self.parent, title, msg, QMessageBox.Yes | QMessageBox.No
1306-
)
1307-
if reply == QMessageBox.Yes:
1308-
self._apply_system_python()
1309-
return
1310-
else:
1311-
if missing:
1312-
try:
1313-
self._safe_log(
1314-
"ℹ️ Python système incomplet: "
1315-
+ ", ".join(sorted(set(missing)))
1316-
)
1317-
except Exception:
1318-
pass
1319-
except Exception:
1320-
pass
1321-
1322-
folder = QFileDialog.getExistingDirectory(
1323-
self.parent,
1324-
self.parent.tr("Choisir un dossier venv", "Choose a venv folder"),
1325-
"",
1326-
)
1327-
if folder:
1328-
path = os.path.abspath(folder)
1329-
ok, reason = self.validate_venv_strict(path)
1330-
if ok:
1331-
try:
1332-
setattr(self.parent, "use_system_python", False)
1333-
except Exception:
1334-
pass
1335-
self.parent.venv_path_manuel = path
1336-
if hasattr(self.parent, "venv_label") and self.parent.venv_label:
1337-
self.parent.venv_label.setText(f"Venv sélectionné : {path}")
1338-
self._safe_log(f"✅ Venv valide sélectionné: {path}")
1339-
try:
1340-
workspace_dir = getattr(self.parent, "workspace_dir", None)
1341-
self.save_workspace_pref(workspace_dir)
1342-
except Exception:
1343-
pass
1344-
else:
1345-
self._safe_log(f"❌ Venv refusé: {reason}")
1346-
self.parent.venv_path_manuel = None
1347-
try:
1348-
setattr(self.parent, "use_system_python", False)
1349-
except Exception:
1350-
pass
1351-
if hasattr(self.parent, "venv_label") and self.parent.venv_label:
1352-
self.parent.venv_label.setText("Venv sélectionné : Aucun")
1353-
try:
1354-
workspace_dir = getattr(self.parent, "workspace_dir", None)
1355-
self.save_workspace_pref(workspace_dir)
1356-
except Exception:
1357-
pass
1358-
# Message concis avec actions proposées
1359-
try:
1360-
1361-
def _t(_key: str, fr: str, en: str) -> str:
1362-
"""Execute _t logic for this component."""
1363-
try:
1364-
return self.parent.tr(fr, en)
1365-
except Exception:
1366-
return en
1367-
1368-
box = QMessageBox(self.parent)
1369-
box.setWindowTitle(
1370-
_t("msg_invalid_venv_title", "Venv invalide", "Invalid Venv")
1371-
)
1372-
box.setText(
1373-
_t(
1374-
"msg_invalid_venv_text",
1375-
"Le dossier sélectionné n'est pas un venv valide. Réessayer ou créer un venv ?",
1376-
"The selected folder is not a valid venv. Retry or create a venv?",
1377-
)
1378-
)
1379-
if reason:
1380-
try:
1381-
box.setInformativeText(str(reason))
1382-
except Exception:
1383-
pass
1384-
btn_retry = box.addButton(
1385-
_t("action_retry", "Réessayer", "Retry"),
1386-
QMessageBox.AcceptRole,
1387-
)
1388-
btn_create = None
1389-
workspace_dir = getattr(self.parent, "workspace_dir", None)
1390-
if workspace_dir:
1391-
btn_create = box.addButton(
1392-
_t("action_create_venv", "Créer un venv", "Create venv"),
1393-
QMessageBox.ActionRole,
1394-
)
1395-
box.addButton(
1396-
_t("action_cancel", "Annuler", "Cancel"),
1397-
QMessageBox.RejectRole,
1398-
)
1399-
box.exec()
1400-
if box.clickedButton() == btn_retry:
1401-
self.select_venv_manually()
1402-
return
1403-
if btn_create and box.clickedButton() == btn_create:
1404-
try:
1405-
self.create_venv_if_needed(workspace_dir)
1406-
except Exception:
1407-
pass
1408-
return
1409-
except Exception:
1410-
pass
1411-
else:
1412-
self.parent.venv_path_manuel = None
1413-
try:
1414-
setattr(self.parent, "use_system_python", False)
1415-
except Exception:
1416-
pass
1417-
if hasattr(self.parent, "venv_label") and self.parent.venv_label:
1418-
self.parent.venv_label.setText("Venv sélectionné : Aucun")
1419-
try:
1420-
workspace_dir = getattr(self.parent, "workspace_dir", None)
1421-
self.save_workspace_pref(workspace_dir)
1422-
except Exception:
1423-
pass
1424-
14251226
# ---------- Existing venv: check and install tools ----------
14261227
def check_tools_in_venv(self, venv_path: str):
14271228
"""Execute check_tools_in_venv logic for this component."""
@@ -2072,13 +1873,9 @@ def _on_venv_created(self, process, code, status, venv_path):
20721873
if not getattr(self.parent, "use_system_python", False):
20731874
if not getattr(self.parent, "venv_path_manuel", None):
20741875
self.parent.venv_path_manuel = venv_path
2075-
if (
2076-
hasattr(self.parent, "venv_label")
2077-
and self.parent.venv_label
2078-
):
2079-
self.parent.venv_label.setText(
2080-
f"Venv sélectionné : {venv_path}"
2081-
)
1876+
self._call_ui(
1877+
"update_venv_label", f"Venv sélectionné : {venv_path}"
1878+
)
20821879
self.save_workspace_pref(os.path.dirname(venv_path))
20831880
except Exception:
20841881
pass

0 commit comments

Comments
 (0)