From 2298f779fdf7d828c1a84ca933b4eee9e8103212 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Wed, 29 Jul 2026 13:22:26 -0600 Subject: Offer to pip install missing plugin dependencies instead of just warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds on the previous branch's dependency check: the missing-deps QMessageBox is now a Yes/No prompt. Yes runs a blocking `sys.executable -m pip install ` (subprocess.run, output captured), shows a result dialog, and on success proceeds straight to enabling the plugin — no need to click Enable a second time. Blocking is a deliberate simplification, not an oversight: this codebase has no worker-thread/progress-dialog pattern for slow operations anywhere else, so a threaded installer would be inconsistent with everything else here. pip install is a one-time, infrequent action, unlike e.g. a serial connect that runs constantly. Co-Authored-By: Claude Sonnet 5 --- ui/main_window.py | 51 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) (limited to 'ui') diff --git a/ui/main_window.py b/ui/main_window.py index 95cdb84..c04230c 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -299,20 +299,57 @@ class MainWindow(QMainWindow): missing = self._plugin_mgr.get_missing_dependencies(plugin_id) if missing: from PyQt6.QtWidgets import QMessageBox - QMessageBox.warning( + reply = QMessageBox.question( self, "Missing Plugin Dependencies", - f"Can't enable this plugin — missing Python packages:\n\n" + f"This plugin needs packages that aren't installed:\n\n" f" {', '.join(missing)}\n\n" - f"Install with:\n pip install {' '.join(missing)}" + f"Install them now with pip?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, ) + if reply == QMessageBox.StandardButton.Yes: + if self._pip_install(missing): + self._enable_plugin_now(plugin_id) else: - ctx = self._make_plugin_context() - plugin = self._plugin_mgr.enable(plugin_id, ctx) - if plugin: - self._install_plugin(plugin) + self._enable_plugin_now(plugin_id) if self._win_settings: self._win_settings.sync_plugin_button(plugin_id) + def _enable_plugin_now(self, plugin_id: str): + ctx = self._make_plugin_context() + plugin = self._plugin_mgr.enable(plugin_id, ctx) + if plugin: + self._install_plugin(plugin) + + def _pip_install(self, requirements: list) -> bool: + """Blocking `pip install` of the given requirement strings. + Returns True on success; shows a result dialog either way.""" + import subprocess + import sys + from PyQt6.QtWidgets import QMessageBox + + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) + try: + result = subprocess.run( + [sys.executable, "-m", "pip", "install", *requirements], + capture_output=True, text=True, + ) + finally: + QApplication.restoreOverrideCursor() + + if result.returncode == 0: + QMessageBox.information( + self, "Install Complete", + f"Installed: {', '.join(requirements)}" + ) + return True + QMessageBox.critical( + self, "Install Failed", + f"pip install failed for: {', '.join(requirements)}\n\n" + f"{result.stderr.strip()[-1500:]}" + ) + return False + def plugin_disable(self, plugin_id: str): """Called by SettingsWindow when user disables a plugin.""" self._uninstall_plugin(plugin_id) -- cgit v1.2.3