""" 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 - dynamically discovers hotkeys from plugins.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Info label info = QLabel("Hotkeys are advertised by plugins. Changes apply on next restart.") info.setStyleSheet("color: rgba(255,255,255,150);") layout.addWidget(info) # Scroll area for hotkeys scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setStyleSheet("background: transparent; border: none;") scroll_content = QWidget() hotkeys_layout = QVBoxLayout(scroll_content) hotkeys_layout.setSpacing(10) hotkeys_layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.hotkey_inputs = {} # Collect hotkeys from all plugins plugin_hotkeys = self._collect_plugin_hotkeys() # Group by plugin for plugin_name, hotkeys in sorted(plugin_hotkeys.items()): # Plugin group group = QGroupBox(plugin_name) group.setStyleSheet(self._group_style()) group_layout = QVBoxLayout(group) for hotkey_info in hotkeys: row = QHBoxLayout() # Description desc = hotkey_info.get('description', hotkey_info['action']) desc_label = QLabel(f"{desc}:") desc_label.setStyleSheet("color: white; min-width: 150px;") row.addWidget(desc_label) # Hotkey input input_field = QLineEdit() input_field.setText(hotkey_info['current']) input_field.setPlaceholderText(hotkey_info['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; } """) # Store reference with config key config_key = hotkey_info['config_key'] self.hotkey_inputs[config_key] = { 'input': input_field, 'default': hotkey_info['default'], 'plugin': plugin_name, 'action': hotkey_info['action'] } row.addWidget(input_field) # Reset button reset_btn = QPushButton("↺") reset_btn.setFixedSize(28, 28) reset_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); color: white; border: none; border-radius: 4px; font-size: 12px; } QPushButton:hover { background-color: rgba(255,255,255,40); } """) reset_btn.setToolTip(f"Reset to default: {hotkey_info['default']}") reset_btn.clicked.connect(lambda checked, inp=input_field, default=hotkey_info['default']: inp.setText(default)) row.addWidget(reset_btn) row.addStretch() group_layout.addLayout(row) hotkeys_layout.addWidget(group) # Core hotkeys section (always present) core_group = QGroupBox("Core System") core_group.setStyleSheet(self._group_style()) core_layout = QVBoxLayout(core_group) core_hotkeys = [ ("Toggle Overlay", "hotkey_toggle", "ctrl+shift+u", "Show/hide the EU-Utility overlay"), ("Universal Search", "hotkey_search", "ctrl+shift+f", "Quick search across all plugins"), ] for label, config_key, default, description in core_hotkeys: row = QHBoxLayout() desc_label = QLabel(f"{label}:") desc_label.setStyleSheet("color: white; min-width: 150px;") row.addWidget(desc_label) input_field = QLineEdit() current = self.settings.get(config_key, default) input_field.setText(current) input_field.setPlaceholderText(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[config_key] = { 'input': input_field, 'default': default, 'plugin': 'Core', 'action': label } row.addWidget(input_field) reset_btn = QPushButton("↺") reset_btn.setFixedSize(28, 28) reset_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); color: white; border: none; border-radius: 4px; font-size: 12px; } QPushButton:hover { background-color: rgba(255,255,255,40); } """) reset_btn.setToolTip(f"Reset to default: {default}") reset_btn.clicked.connect(lambda checked, inp=input_field, default=default: inp.setText(default)) row.addWidget(reset_btn) row.addStretch() core_layout.addLayout(row) hotkeys_layout.addWidget(core_group) hotkeys_layout.addStretch() scroll.setWidget(scroll_content) layout.addWidget(scroll) return tab def _collect_plugin_hotkeys(self) -> dict: """Collect hotkeys from all discovered plugins. Returns: Dict mapping plugin name to list of hotkey info dicts """ plugin_hotkeys = {} if not hasattr(self.overlay, 'plugin_manager'): return plugin_hotkeys plugin_manager = self.overlay.plugin_manager all_plugins = plugin_manager.get_all_discovered_plugins() for plugin_id, plugin_class in all_plugins.items(): hotkeys = getattr(plugin_class, 'hotkeys', None) if not hotkeys: # Try legacy single hotkey attribute single_hotkey = getattr(plugin_class, 'hotkey', None) if single_hotkey: hotkeys = [{ 'action': 'toggle', 'description': f"Toggle {plugin_class.name}", 'default': single_hotkey, 'config_key': f"hotkey_{plugin_id.split('.')[-1]}" }] if hotkeys: plugin_name = plugin_class.name plugin_hotkeys[plugin_name] = [] for i, hk in enumerate(hotkeys): # Support both dict format and simple string if isinstance(hk, dict): hotkey_info = { 'action': hk.get('action', f'action_{i}'), 'description': hk.get('description', hk.get('action', f'Action {i}')), 'default': hk.get('default', ''), 'config_key': hk.get('config_key', f"hotkey_{plugin_id.split('.')[-1]}_{i}") } else: # Simple string format - legacy hotkey_info = { 'action': f'hotkey_{i}', 'description': f"Hotkey {i+1}", 'default': str(hk), 'config_key': f"hotkey_{plugin_id.split('.')[-1]}_{i}" } # Get current value from settings hotkey_info['current'] = self.settings.get(hotkey_info['config_key'], hotkey_info['default']) plugin_hotkeys[plugin_name].append(hotkey_info) return plugin_hotkeys 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 - new structure with dict values for config_key, hotkey_data in self.hotkey_inputs.items(): if isinstance(hotkey_data, dict): input_field = hotkey_data['input'] self.settings.set(config_key, input_field.text()) else: # Legacy format - direct QLineEdit reference self.settings.set(config_key, hotkey_data.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("Total Plugins: {total} | Enabled: {enabled}
") html.append("