""" EU-Utility - Settings UI Plugin Settings menu for configuring EU-Utility. """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QCheckBox, QLineEdit, QComboBox, QSlider, QTabWidget, QGroupBox, QListWidget, QListWidgetItem, QFrame, QFileDialog, QScrollArea ) from PyQt6.QtCore import Qt, QTimer from core.settings import get_settings from plugins.base_plugin import BasePlugin class SettingsPlugin(BasePlugin): """EU-Utility settings and configuration.""" name = "Settings" version = "1.0.0" author = "ImpulsiveFPS" description = "Configure EU-Utility preferences" hotkey = "ctrl+shift+comma" def initialize(self): """Setup settings.""" self.settings = get_settings() def get_ui(self): """Create settings UI.""" widget = QWidget() widget.setStyleSheet("background: transparent;") layout = QVBoxLayout(widget) layout.setSpacing(10) layout.setContentsMargins(0, 0, 0, 0) # Title title = QLabel("Settings") title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") layout.addWidget(title) # Tabs tabs = QTabWidget() tabs.setStyleSheet(""" QTabBar::tab { background-color: rgba(35, 40, 55, 200); color: rgba(255,255,255,150); padding: 10px 20px; border-top-left-radius: 6px; border-top-right-radius: 6px; } QTabBar::tab:selected { background-color: #ff8c42; color: white; font-weight: bold; } """) # General tab general_tab = self._create_general_tab() tabs.addTab(general_tab, "General") # Plugins tab plugins_tab = self._create_plugins_tab() tabs.addTab(plugins_tab, "Plugins") # Hotkeys tab hotkeys_tab = self._create_hotkeys_tab() tabs.addTab(hotkeys_tab, "Hotkeys") # Overlay tab overlay_tab = self._create_overlay_tab() tabs.addTab(overlay_tab, "Overlays") # Data tab data_tab = self._create_data_tab() tabs.addTab(data_tab, "Data") layout.addWidget(tabs) # Save/Reset buttons btn_layout = QHBoxLayout() save_btn = QPushButton("Save Settings") save_btn.setStyleSheet(""" QPushButton { background-color: #4caf50; color: white; padding: 10px 20px; border: none; border-radius: 4px; font-weight: bold; } """) save_btn.clicked.connect(self._save_settings) btn_layout.addWidget(save_btn) reset_btn = QPushButton("Reset to Default") reset_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); color: white; padding: 10px 20px; border: none; border-radius: 4px; } """) reset_btn.clicked.connect(self._reset_settings) btn_layout.addWidget(reset_btn) btn_layout.addStretch() layout.addLayout(btn_layout) return widget def _create_general_tab(self): """Create general settings tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Appearance appear_group = QGroupBox("Appearance") appear_group.setStyleSheet(self._group_style()) appear_layout = QVBoxLayout(appear_group) # Theme theme_layout = QHBoxLayout() theme_layout.addWidget(QLabel("Theme:")) self.theme_combo = QComboBox() self.theme_combo.addItems(["Dark (EU Style)", "Light", "Auto"]) self.theme_combo.setCurrentText(self.settings.get('theme', 'Dark (EU Style)')) theme_layout.addWidget(self.theme_combo) theme_layout.addStretch() appear_layout.addLayout(theme_layout) # Opacity opacity_layout = QHBoxLayout() opacity_layout.addWidget(QLabel("Overlay Opacity:")) self.opacity_slider = QSlider(Qt.Orientation.Horizontal) self.opacity_slider.setMinimum(50) self.opacity_slider.setMaximum(100) self.opacity_slider.setValue(int(self.settings.get('overlay_opacity', 0.9) * 100)) opacity_layout.addWidget(self.opacity_slider) self.opacity_label = QLabel(f"{self.opacity_slider.value()}%") opacity_layout.addWidget(self.opacity_label) opacity_layout.addStretch() appear_layout.addLayout(opacity_layout) # Icon size icon_layout = QHBoxLayout() icon_layout.addWidget(QLabel("Icon Size:")) self.icon_combo = QComboBox() self.icon_combo.addItems(["Small (20px)", "Medium (24px)", "Large (32px)"]) icon_layout.addWidget(self.icon_combo) icon_layout.addStretch() appear_layout.addLayout(icon_layout) layout.addWidget(appear_group) # Behavior behavior_group = QGroupBox("Behavior") behavior_group.setStyleSheet(self._group_style()) behavior_layout = QVBoxLayout(behavior_group) self.auto_start_cb = QCheckBox("Start with Windows") self.auto_start_cb.setChecked(self.settings.get('auto_start', False)) behavior_layout.addWidget(self.auto_start_cb) self.minimize_cb = QCheckBox("Minimize to tray on close") self.minimize_cb.setChecked(self.settings.get('minimize_to_tray', True)) behavior_layout.addWidget(self.minimize_cb) self.tooltips_cb = QCheckBox("Show tooltips") self.tooltips_cb.setChecked(self.settings.get('show_tooltips', True)) behavior_layout.addWidget(self.tooltips_cb) layout.addWidget(behavior_group) layout.addStretch() return tab def _create_plugins_tab(self): """Create plugins management tab with dependency visualization.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Info label info = QLabel("Manage plugins. Hover over dependency icons to see requirements. Changes take effect immediately.") info.setStyleSheet("color: rgba(255,255,255,150);") layout.addWidget(info) # Dependency legend legend_layout = QHBoxLayout() legend_layout.addWidget(QLabel("Legend:")) # Required by others indicator req_label = QLabel("⚠️ Required") req_label.setStyleSheet("color: #ffd93d; font-size: 11px;") req_label.setToolTip("This plugin is required by other enabled plugins") legend_layout.addWidget(req_label) # Has dependencies indicator dep_label = QLabel("🔗 Has deps") dep_label.setStyleSheet("color: #4ecdc4; font-size: 11px;") dep_label.setToolTip("This plugin requires other plugins to function") legend_layout.addWidget(dep_label) # Auto-enabled indicator auto_label = QLabel("🔄 Auto") auto_label.setStyleSheet("color: #ff8c42; font-size: 11px;") auto_label.setToolTip("Auto-enabled due to dependency") legend_layout.addWidget(auto_label) legend_layout.addStretch() layout.addLayout(legend_layout) # Scroll area for plugin list scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setStyleSheet("background: transparent; border: none;") scroll_content = QWidget() plugins_layout = QVBoxLayout(scroll_content) plugins_layout.setSpacing(8) plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.plugin_checkboxes = {} self.plugin_dependency_labels = {} self.plugin_rows = {} # Get all discovered plugins from plugin manager if hasattr(self.overlay, 'plugin_manager'): plugin_manager = self.overlay.plugin_manager all_plugins = plugin_manager.get_all_discovered_plugins() # Build dependency maps self._build_dependency_maps(all_plugins) # Sort by name sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name) for plugin_id, plugin_class in sorted_plugins: plugin_row = self._create_plugin_row(plugin_id, plugin_class, plugin_manager) plugins_layout.addLayout(plugin_row) # Separator sep = QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet("background-color: rgba(100, 110, 130, 40);") sep.setFixedHeight(1) plugins_layout.addWidget(sep) plugins_layout.addStretch() scroll.setWidget(scroll_content) layout.addWidget(scroll) # Buttons btn_layout = QHBoxLayout() enable_all_btn = QPushButton("Enable All") enable_all_btn.setStyleSheet(""" QPushButton { background-color: #4caf50; color: white; padding: 8px 16px; border: none; border-radius: 4px; } """) enable_all_btn.clicked.connect(self._enable_all_plugins) btn_layout.addWidget(enable_all_btn) disable_all_btn = QPushButton("Disable All") disable_all_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); color: white; padding: 8px 16px; border: none; border-radius: 4px; } """) disable_all_btn.clicked.connect(self._disable_all_plugins) btn_layout.addWidget(disable_all_btn) # Dependency info button deps_info_btn = QPushButton("📋 Dependency Report") deps_info_btn.setStyleSheet(""" QPushButton { background-color: #4a9eff; color: white; padding: 8px 16px; border: none; border-radius: 4px; } """) deps_info_btn.clicked.connect(self._show_dependency_report) btn_layout.addWidget(deps_info_btn) btn_layout.addStretch() layout.addLayout(btn_layout) return tab def _build_dependency_maps(self, all_plugins): """Build maps of plugin dependencies.""" self.plugin_deps = {} # plugin_id -> list of plugin_ids it depends on self.plugin_dependents = {} # plugin_id -> list of plugin_ids that depend on it for plugin_id, plugin_class in all_plugins.items(): deps = getattr(plugin_class, 'dependencies', {}) plugin_deps_list = deps.get('plugins', []) self.plugin_deps[plugin_id] = plugin_deps_list # Build reverse map for dep_id in plugin_deps_list: if dep_id not in self.plugin_dependents: self.plugin_dependents[dep_id] = [] self.plugin_dependents[dep_id].append(plugin_id) def _create_plugin_row(self, plugin_id, plugin_class, plugin_manager): """Create a plugin row with dependency indicators.""" row = QHBoxLayout() row.setSpacing(10) # Checkbox cb = QCheckBox(plugin_class.name) is_enabled = plugin_manager.is_plugin_enabled(plugin_id) is_auto_enabled = plugin_manager.is_auto_enabled(plugin_id) if hasattr(plugin_manager, 'is_auto_enabled') else False cb.setChecked(is_enabled) cb.setStyleSheet(""" QCheckBox { color: white; spacing: 8px; } QCheckBox::indicator { width: 18px; height: 18px; } QCheckBox::indicator:disabled { background-color: #ff8c42; } """) # Disable checkbox if auto-enabled if is_auto_enabled: cb.setEnabled(False) cb.setText(f"{plugin_class.name} (auto)") # Connect to enable/disable cb.stateChanged.connect( lambda state, pid=plugin_id: self._toggle_plugin(pid, state == Qt.CheckState.Checked.value) ) self.plugin_checkboxes[plugin_id] = cb row.addWidget(cb) # Dependency indicators indicators_layout = QHBoxLayout() indicators_layout.setSpacing(4) # Check if this plugin has dependencies deps = self.plugin_deps.get(plugin_id, []) if deps: deps_btn = QPushButton("🔗") deps_btn.setFixedSize(24, 24) deps_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #4ecdc4; border: none; font-size: 12px; } QPushButton:hover { background-color: rgba(78, 205, 196, 30); border-radius: 4px; } """) deps_btn.setToolTip(self._format_dependencies_tooltip(plugin_id, deps)) indicators_layout.addWidget(deps_btn) # Check if other plugins depend on this one dependents = self.plugin_dependents.get(plugin_id, []) enabled_dependents = [d for d in dependents if plugin_manager.is_plugin_enabled(d)] if enabled_dependents: req_btn = QPushButton("⚠️") req_btn.setFixedSize(24, 24) req_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #ffd93d; border: none; font-size: 12px; } QPushButton:hover { background-color: rgba(255, 217, 61, 30); border-radius: 4px; } """) req_btn.setToolTip(self._format_dependents_tooltip(plugin_id, enabled_dependents)) indicators_layout.addWidget(req_btn) # Check if auto-enabled if is_auto_enabled: auto_btn = QPushButton("🔄") auto_btn.setFixedSize(24, 24) auto_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #ff8c42; border: none; font-size: 12px; } QPushButton:hover { background-color: rgba(255, 140, 66, 30); border-radius: 4px; } """) # Find what enabled this plugin enabler = self._find_enabler(plugin_id, plugin_manager) auto_btn.setToolTip(f"Auto-enabled by: {enabler or 'dependency resolution'}") indicators_layout.addWidget(auto_btn) row.addLayout(indicators_layout) # Version version_label = QLabel(f"v{plugin_class.version}") version_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;") row.addWidget(version_label) # Description desc_label = QLabel(f"- {plugin_class.description}") desc_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;") desc_label.setWordWrap(True) row.addWidget(desc_label, 1) row.addStretch() # Store row reference for updates self.plugin_rows[plugin_id] = row return row def _format_dependencies_tooltip(self, plugin_id, deps): """Format tooltip for dependencies.""" lines = ["This plugin requires:"] for dep_id in deps: dep_name = dep_id.split('.')[-1].replace('_', ' ').title() lines.append(f" • {dep_name}") lines.append("") lines.append("These will be auto-enabled when you enable this plugin.") return "\n".join(lines) def _format_dependents_tooltip(self, plugin_id, dependents): """Format tooltip for plugins that depend on this one.""" lines = ["Required by enabled plugins:"] for dep_id in dependents: dep_name = dep_id.split('.')[-1].replace('_', ' ').title() lines.append(f" • {dep_name}") lines.append("") lines.append("Disable these first to disable this plugin.") return "\n".join(lines) def _find_enabler(self, plugin_id, plugin_manager): """Find which plugin auto-enabled this one.""" # Check all enabled plugins to see which one depends on this for other_id, other_class in plugin_manager.get_all_discovered_plugins().items(): if plugin_manager.is_plugin_enabled(other_id): deps = getattr(other_class, 'dependencies', {}).get('plugins', []) if plugin_id in deps: return other_class.name return None def _show_dependency_report(self): """Show a dialog with full dependency report.""" from PyQt6.QtWidgets import QDialog, QTextEdit, QVBoxLayout, QPushButton dialog = QDialog() dialog.setWindowTitle("Plugin Dependency Report") dialog.setMinimumSize(600, 400) dialog.setStyleSheet(""" QDialog { background-color: #1a1f2e; } QTextEdit { background-color: #232837; color: white; border: 1px solid rgba(100, 110, 130, 80); padding: 10px; } QPushButton { background-color: #4a9eff; color: white; padding: 8px 16px; border: none; border-radius: 4px; } """) layout = QVBoxLayout(dialog) text_edit = QTextEdit() text_edit.setReadOnly(True) text_edit.setHtml(self._generate_dependency_report()) layout.addWidget(text_edit) close_btn = QPushButton("Close") close_btn.clicked.connect(dialog.close) layout.addWidget(close_btn) dialog.exec() def _create_hotkeys_tab(self): """Create hotkeys configuration tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) hotkeys_group = QGroupBox("Global Hotkeys") hotkeys_group.setStyleSheet(self._group_style()) hotkeys_layout = QVBoxLayout(hotkeys_group) self.hotkey_inputs = {} hotkeys = [ ("Toggle Overlay", "hotkey_toggle", "ctrl+shift+u"), ("Universal Search", "hotkey_search", "ctrl+shift+f"), ("Calculator", "hotkey_calculator", "ctrl+shift+c"), ("Spotify", "hotkey_music", "ctrl+shift+m"), ("Game Reader", "hotkey_scan", "ctrl+shift+r"), ("Skill Scanner", "hotkey_skills", "ctrl+shift+s"), ] for label, key, default in hotkeys: row = QHBoxLayout() row.addWidget(QLabel(label + ":")) input_field = QLineEdit() input_field.setText(self.settings.get(key, default)) input_field.setStyleSheet(""" QLineEdit { background-color: rgba(30, 35, 45, 200); color: white; border: 1px solid rgba(100, 110, 130, 80); padding: 5px; min-width: 150px; } """) self.hotkey_inputs[key] = input_field row.addWidget(input_field) row.addStretch() hotkeys_layout.addLayout(row) layout.addWidget(hotkeys_group) layout.addStretch() return tab def _create_overlay_tab(self): """Create overlay widgets configuration tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) overlays_group = QGroupBox("In-Game Overlays") overlays_group.setStyleSheet(self._group_style()) overlays_layout = QVBoxLayout(overlays_group) overlays = [ ("Spotify Player", "spotify", True), ("Mission Tracker", "mission", False), ("Skill Gains", "skillgain", False), ("DPP Tracker", "dpp", False), ] for name, key, enabled in overlays: cb = QCheckBox(name) cb.setChecked(enabled) overlays_layout.addWidget(cb) # Reset positions reset_pos_btn = QPushButton("↺ Reset All Positions") reset_pos_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); color: white; padding: 8px; border: none; border-radius: 4px; } """) overlays_layout.addWidget(reset_pos_btn) layout.addWidget(overlays_group) layout.addStretch() return tab def _create_data_tab(self): """Create data management tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) data_group = QGroupBox("Data Management") data_group.setStyleSheet(self._group_style()) data_layout = QVBoxLayout(data_group) # Export export_btn = QPushButton("📤 Export All Data") export_btn.setStyleSheet(""" QPushButton { background-color: #4a9eff; color: white; padding: 10px; border: none; border-radius: 4px; font-weight: bold; } """) export_btn.clicked.connect(self._export_data) data_layout.addWidget(export_btn) # Import import_btn = QPushButton("📥 Import Data") import_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); color: white; padding: 10px; border: none; border-radius: 4px; } """) import_btn.clicked.connect(self._import_data) data_layout.addWidget(import_btn) # Clear clear_btn = QPushButton("Clear All Data") clear_btn.setStyleSheet(""" QPushButton { background-color: #f44336; color: white; padding: 10px; border: none; border-radius: 4px; } """) clear_btn.clicked.connect(self._clear_data) data_layout.addWidget(clear_btn) # Retention retention_layout = QHBoxLayout() retention_layout.addWidget(QLabel("Data retention:")) self.retention_combo = QComboBox() self.retention_combo.addItems(["7 days", "30 days", "90 days", "Forever"]) retention_layout.addWidget(self.retention_combo) retention_layout.addStretch() data_layout.addLayout(retention_layout) layout.addWidget(data_group) layout.addStretch() return tab def _group_style(self): """Get group box style.""" return """ QGroupBox { color: rgba(255,255,255,200); border: 1px solid rgba(100, 110, 130, 80); border-radius: 6px; margin-top: 10px; font-weight: bold; font-size: 12px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } """ def _save_settings(self): """Save all settings.""" # General self.settings.set('theme', self.theme_combo.currentText()) self.settings.set('overlay_opacity', self.opacity_slider.value() / 100) self.settings.set('auto_start', self.auto_start_cb.isChecked()) self.settings.set('minimize_to_tray', self.minimize_cb.isChecked()) self.settings.set('show_tooltips', self.tooltips_cb.isChecked()) # Hotkeys for key, input_field in self.hotkey_inputs.items(): self.settings.set(key, input_field.text()) print("Settings saved!") def _reset_settings(self): """Reset to defaults.""" self.settings.reset() print("Settings reset to defaults!") def _export_data(self): """Export all data.""" from PyQt6.QtWidgets import QFileDialog filepath, _ = QFileDialog.getSaveFileName( None, "Export EU-Utility Data", "eu_utility_backup.json", "JSON (*.json)" ) if filepath: import shutil data_dir = Path("data") if data_dir.exists(): # Create export import json export_data = {} for f in data_dir.glob("*.json"): with open(f, 'r') as file: export_data[f.stem] = json.load(file) with open(filepath, 'w') as file: json.dump(export_data, file, indent=2) def _import_data(self): """Import data.""" pass def _clear_data(self): """Clear all data.""" pass def _toggle_plugin(self, plugin_id: str, enable: bool): """Enable or disable a plugin with dependency handling.""" if not hasattr(self.overlay, 'plugin_manager'): return plugin_manager = self.overlay.plugin_manager if enable: # Get dependencies that will be auto-enabled deps_to_enable = self._get_missing_dependencies(plugin_id, plugin_manager) if deps_to_enable: # Show confirmation dialog from PyQt6.QtWidgets import QMessageBox dep_names = [pid.split('.')[-1].replace('_', ' ').title() for pid in deps_to_enable] msg = f"Enabling this plugin will also enable:\n\n" msg += "\n".join(f" • {name}" for name in dep_names) msg += "\n\nContinue?" reply = QMessageBox.question( None, "Enable Dependencies", msg, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply != QMessageBox.StandardButton.Yes: # Uncheck the box self.plugin_checkboxes[plugin_id].setChecked(False) return success = plugin_manager.enable_plugin(plugin_id) if success: print(f"[Settings] Enabled plugin: {plugin_id}") # Refresh UI to show auto-enabled plugins self._refresh_plugin_list() else: print(f"[Settings] Failed to enable plugin: {plugin_id}") else: # Check if other enabled plugins depend on this one dependents = self.plugin_dependents.get(plugin_id, []) enabled_dependents = [d for d in dependents if plugin_manager.is_plugin_enabled(d)] if enabled_dependents: # Show warning from PyQt6.QtWidgets import QMessageBox dep_names = [pid.split('.')[-1].replace('_', ' ').title() for pid in enabled_dependents] msg = f"Cannot disable: This plugin is required by:\n\n" msg += "\n".join(f" • {name}" for name in dep_names) msg += "\n\nDisable those plugins first." QMessageBox.warning(None, "Dependency Warning", msg) # Recheck the box self.plugin_checkboxes[plugin_id].setChecked(True) return success = plugin_manager.disable_plugin(plugin_id) if success: print(f"[Settings] Disabled plugin: {plugin_id}") self._refresh_plugin_list() def _get_missing_dependencies(self, plugin_id: str, plugin_manager) -> list: """Get list of dependencies that need to be enabled.""" deps = self.plugin_deps.get(plugin_id, []) missing = [] for dep_id in deps: if not plugin_manager.is_plugin_enabled(dep_id): missing.append(dep_id) return missing def _refresh_plugin_list(self): """Refresh the plugin list UI.""" if not hasattr(self.overlay, 'plugin_manager'): return plugin_manager = self.overlay.plugin_manager for plugin_id, cb in self.plugin_checkboxes.items(): is_enabled = plugin_manager.is_plugin_enabled(plugin_id) is_auto_enabled = plugin_manager.is_auto_enabled(plugin_id) if hasattr(plugin_manager, 'is_auto_enabled') else False cb.setChecked(is_enabled) if is_auto_enabled: cb.setEnabled(False) # Update text to show auto status plugin_class = plugin_manager.get_all_discovered_plugins().get(plugin_id) if plugin_class: cb.setText(f"{plugin_class.name} (auto)") else: cb.setEnabled(True) plugin_class = plugin_manager.get_all_discovered_plugins().get(plugin_id) if plugin_class: cb.setText(plugin_class.name) def _generate_dependency_report(self) -> str: """Generate HTML dependency report.""" if not hasattr(self.overlay, 'plugin_manager'): return "

No plugin manager available

" plugin_manager = self.overlay.plugin_manager all_plugins = plugin_manager.get_all_discovered_plugins() html = [""] html.append("

📋 Plugin Dependency Report

") html.append("
") # Summary section total = len(all_plugins) enabled = sum(1 for pid in all_plugins if plugin_manager.is_plugin_enabled(pid)) html.append(f"

Total Plugins: {total} | Enabled: {enabled}

") html.append("
") # Plugins with dependencies html.append("

🔗 Plugins with Dependencies

") html.append("") html.append("
") # Plugins required by others html.append("

⚠️ Plugins Required by Others

") html.append("") html.append("
") # Dependency chain visualization html.append("

🔄 Dependency Chains

") html.append("") html.append("") return "\n".join(html) def _get_dependency_chain(self, plugin_id: str, visited=None) -> list: """Get the dependency chain for a plugin.""" if visited is None: visited = set() if plugin_id in visited: return [plugin_id] # Circular dependency visited.add(plugin_id) chain = [plugin_id] # Get what this plugin depends on deps = self.plugin_deps.get(plugin_id, []) for dep_id in deps: if dep_id not in visited: chain.extend(self._get_dependency_chain(dep_id, visited)) return chain def _enable_all_plugins(self): """Enable all plugins with dependency resolution.""" if not hasattr(self.overlay, 'plugin_manager'): return plugin_manager = self.overlay.plugin_manager all_plugins = plugin_manager.get_all_discovered_plugins() # Sort plugins so dependencies are enabled first sorted_plugins = self._sort_plugins_by_dependencies(all_plugins) enabled_count = 0 for plugin_id in sorted_plugins: cb = self.plugin_checkboxes.get(plugin_id) if cb: cb.setChecked(True) success = plugin_manager.enable_plugin(plugin_id) if success: enabled_count += 1 self._refresh_plugin_list() print(f"[Settings] Enabled {enabled_count} plugins") def _sort_plugins_by_dependencies(self, all_plugins: dict) -> list: """Sort plugins so dependencies come before dependents.""" plugin_ids = list(all_plugins.keys()) # Build dependency graph graph = {pid: set(self.plugin_deps.get(pid, [])) for pid in plugin_ids} # Topological sort sorted_list = [] visited = set() temp_mark = set() def visit(pid): if pid in temp_mark: return # Circular dependency, skip if pid in visited: return temp_mark.add(pid) for dep in graph.get(pid, set()): if dep in graph: visit(dep) temp_mark.remove(pid) visited.add(pid) sorted_list.append(pid) for pid in plugin_ids: visit(pid) return sorted_list def _disable_all_plugins(self): """Disable all plugins in reverse dependency order.""" if not hasattr(self.overlay, 'plugin_manager'): return plugin_manager = self.overlay.plugin_manager all_plugins = plugin_manager.get_all_discovered_plugins() # Sort so dependents are disabled first (reverse of enable order) sorted_plugins = self._sort_plugins_by_dependencies(all_plugins) sorted_plugins.reverse() disabled_count = 0 for plugin_id in sorted_plugins: cb = self.plugin_checkboxes.get(plugin_id) if cb: cb.setChecked(False) success = plugin_manager.disable_plugin(plugin_id) if success: disabled_count += 1 self._refresh_plugin_list() print(f"[Settings] Disabled {disabled_count} plugins")