summaryrefslogtreecommitdiff
path: root/ui/windows/settings_window.py
diff options
context:
space:
mode:
authorChristian Kolset <ckolset@colostate.edu>2026-07-29 13:21:05 -0600
committerChristian Kolset <ckolset@colostate.edu>2026-07-29 13:21:05 -0600
commit45ff7227fabb97273d4645601733f90f5f744eb1 (patch)
tree97dd17bf24d2a36c490856484bb4de8a2a8e8798 /ui/windows/settings_window.py
parentb407a079000f6a4b04ecf38eca17c57719e7a7ec (diff)
Check plugin dependencies before loading, warn instead of silent console error
manifest.json already had an unused "requires" field (motion_capture's manifest lists opencv-python>=4.8.0) — PluginManifest never parsed it, so a missing dependency just crashed the import inside _load_plugin(), caught by the broad except and only ever printed to the console. Adds PluginManifest.requires, PluginManager.missing_requirements()/ get_missing_dependencies(), and an early check in _load_plugin() that skips the risky import entirely when a requirement is missing. main_window.plugin_enable() now checks this before calling PluginManager.enable() and shows a QMessageBox with the missing packages and a pip install command instead of failing silently. Checks by distribution name via importlib.metadata (what pip installed it as), not import name — those differ for packages like opencv-python (imports as cv2) or pyserial (imports as serial), so importlib.util. find_spec() would give false negatives. Also fixes a button-state bug this surfaces: SettingsWindow's plugin toggle optimistically flipped to "Disable" the instant Enable was clicked, before knowing whether enabling actually succeeds — pre-existing, but now trivially reproducible (any plugin missing a dependency). Enable no longer flips the button immediately; main_window calls the new sync_plugin_button() once the real outcome is known. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'ui/windows/settings_window.py')
-rw-r--r--ui/windows/settings_window.py12
1 files changed, 11 insertions, 1 deletions
diff --git a/ui/windows/settings_window.py b/ui/windows/settings_window.py
index 1630efa..553a3a7 100644
--- a/ui/windows/settings_window.py
+++ b/ui/windows/settings_window.py
@@ -50,6 +50,7 @@ class SettingsWindow(QWidget):
self.registry = registry
self.engine = engine
self._plugin_mgr = plugin_manager
+ self._plugin_buttons: dict = {} # plugin_id -> QPushButton
self.cfg = dict(self._defaults)
if current:
self.cfg.update(current)
@@ -233,6 +234,7 @@ class SettingsWindow(QWidget):
toggle.clicked.connect(
lambda _, pid=manifest.plugin_id, btn=toggle: self._toggle_plugin(pid, btn)
)
+ self._plugin_buttons[manifest.plugin_id] = toggle
hdr.addWidget(toggle)
cl.addLayout(hdr)
@@ -261,8 +263,16 @@ class SettingsWindow(QWidget):
self.plugin_disable_requested.emit(plugin_id)
btn.setText("Enable")
else:
+ # Don't flip to "Disable" yet — enabling can fail (missing
+ # dependencies, bad plugin code). main_window confirms the
+ # real outcome via sync_plugin_button() once enable() returns.
self.plugin_enable_requested.emit(plugin_id)
- btn.setText("Disable")
+
+ def sync_plugin_button(self, plugin_id: str):
+ """Refresh one plugin's toggle button to match its actual enabled state."""
+ btn = self._plugin_buttons.get(plugin_id)
+ if btn is not None and self._plugin_mgr is not None:
+ btn.setText("Disable" if self._plugin_mgr.is_enabled(plugin_id) else "Enable")
# ── Actions ───────────────────────────────────────────────────────────