""" EU-Utility - Enhanced Settings Panel Complete settings implementation with SQLite persistence. """ import json import platform from pathlib import Path from typing import Dict, Any, Optional from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QCheckBox, QLineEdit, QComboBox, QSlider, QTabWidget, QGroupBox, QFrame, QFileDialog, QMessageBox, QScrollArea, QGridLayout, QSpinBox, QKeySequenceEdit, QListWidget, QListWidgetItem, QDialog, QDialogButtonBox, QFormLayout, QProgressBar ) from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QKeySequence from core.data.sqlite_store import get_sqlite_store, SQLiteDataStore class HotkeyEditDialog(QDialog): """Dialog for editing a hotkey.""" def __init__(self, action: str, current_combo: str, parent=None): super().__init__(parent) self.action = action self.current_combo = current_combo self.setWindowTitle(f"Edit Hotkey: {action}") self.setMinimumSize(300, 150) layout = QVBoxLayout(self) layout.setSpacing(15) layout.setContentsMargins(20, 20, 20, 20) # Current hotkey form = QFormLayout() self.key_edit = QKeySequenceEdit() self.key_edit.setKeySequence(QKeySequence(current_combo)) form.addRow("Press keys:", self.key_edit) layout.addLayout(form) # Help text help_label = QLabel("Press the key combination you want to use.") help_label.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;") layout.addWidget(help_label) layout.addStretch() # Buttons buttons = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def get_key_combo(self) -> str: """Get the key combination.""" return self.key_edit.keySequence().toString() class EnhancedSettingsPanel(QWidget): """Enhanced settings panel with full functionality.""" settings_changed = pyqtSignal(str, Any) # key, value theme_changed = pyqtSignal(str) # theme name def __init__(self, overlay_window, parent=None): super().__init__(parent) self.overlay = overlay_window self.plugin_manager = getattr(overlay_window, 'plugin_manager', None) # Initialize data store self.data_store = get_sqlite_store() self._setup_ui() self._load_settings() def _setup_ui(self): """Setup settings UI.""" layout = QVBoxLayout(self) layout.setSpacing(15) layout.setContentsMargins(20, 20, 20, 20) # Header header = QLabel("โ๏ธ Settings") header.setStyleSheet("font-size: 24px; font-weight: bold; color: white;") layout.addWidget(header) # Tabs self.tabs = QTabWidget() self.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; } """) # Add tabs self.tabs.addTab(self._create_general_tab(), "General") self.tabs.addTab(self._create_appearance_tab(), "Appearance") self.tabs.addTab(self._create_plugins_tab(), "Plugins") self.tabs.addTab(self._create_hotkeys_tab(), "Hotkeys") self.tabs.addTab(self._create_data_tab(), "Data & Backup") self.tabs.addTab(self._create_about_tab(), "About") layout.addWidget(self.tabs) # Save button save_btn = QPushButton("๐พ Save Settings") save_btn.setStyleSheet(""" QPushButton { background-color: #4ecdc4; color: #141f23; padding: 12px 24px; border: none; border-radius: 6px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #3dbdb4; } """) save_btn.clicked.connect(self._save_all_settings) layout.addWidget(save_btn) def _create_general_tab(self) -> QWidget: """Create general settings tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Startup startup_group = QGroupBox("Startup") startup_group.setStyleSheet(self._group_style()) startup_layout = QVBoxLayout(startup_group) self.auto_start_cb = QCheckBox("Start with Windows") self.auto_start_cb.setStyleSheet("color: rgba(255, 255, 255, 200);") startup_layout.addWidget(self.auto_start_cb) self.start_minimized_cb = QCheckBox("Start minimized to tray") self.start_minimized_cb.setStyleSheet("color: rgba(255, 255, 255, 200);") startup_layout.addWidget(self.start_minimized_cb) layout.addWidget(startup_group) # Behavior behavior_group = QGroupBox("Behavior") behavior_group.setStyleSheet(self._group_style()) behavior_layout = QVBoxLayout(behavior_group) self.minimize_to_tray_cb = QCheckBox("Minimize to tray instead of closing") self.minimize_to_tray_cb.setStyleSheet("color: rgba(255, 255, 255, 200);") behavior_layout.addWidget(self.minimize_to_tray_cb) self.show_notifications_cb = QCheckBox("Show notifications") self.show_notifications_cb.setChecked(True) self.show_notifications_cb.setStyleSheet("color: rgba(255, 255, 255, 200);") behavior_layout.addWidget(self.show_notifications_cb) self.activity_bar_cb = QCheckBox("Show Activity Bar") self.activity_bar_cb.setChecked(True) self.activity_bar_cb.setStyleSheet("color: rgba(255, 255, 255, 200);") behavior_layout.addWidget(self.activity_bar_cb) layout.addWidget(behavior_group) # Performance perf_group = QGroupBox("Performance") perf_group.setStyleSheet(self._group_style()) perf_layout = QFormLayout(perf_group) perf_layout.setSpacing(10) self.update_interval = QSpinBox() self.update_interval.setRange(100, 5000) self.update_interval.setValue(1000) self.update_interval.setSuffix(" ms") self.update_interval.setStyleSheet(self._input_style()) perf_layout.addRow("Update interval:", self.update_interval) layout.addWidget(perf_group) layout.addStretch() return tab def _create_appearance_tab(self) -> QWidget: """Create appearance settings tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Theme theme_group = QGroupBox("Theme") theme_group.setStyleSheet(self._group_style()) theme_layout = QFormLayout(theme_group) theme_layout.setSpacing(10) self.theme_combo = QComboBox() self.theme_combo.addItems([ "Dark (EU Style)", "Dark Blue", "Dark Purple", "Light", "Auto (System)" ]) self.theme_combo.setStyleSheet(self._input_style()) self.theme_combo.currentTextChanged.connect(self._on_theme_changed) theme_layout.addRow("Theme:", self.theme_combo) # Accent color self.accent_combo = QComboBox() self.accent_combo.addItems([ "Orange (#ff8c42)", "Blue (#4a9eff)", "Green (#4ecdc4)", "Purple (#9b59b6)", "Red (#e74c3c)" ]) self.accent_combo.setStyleSheet(self._input_style()) theme_layout.addRow("Accent color:", self.accent_combo) layout.addWidget(theme_group) # Transparency opacity_group = QGroupBox("Transparency") opacity_group.setStyleSheet(self._group_style()) opacity_layout = QVBoxLayout(opacity_group) opacity_row = QHBoxLayout() opacity_label = QLabel("Window opacity:") opacity_label.setStyleSheet("color: rgba(255, 255, 255, 200);") opacity_row.addWidget(opacity_label) self.opacity_slider = QSlider(Qt.Orientation.Horizontal) self.opacity_slider.setRange(50, 100) self.opacity_slider.setValue(95) opacity_row.addWidget(self.opacity_slider) self.opacity_value = QLabel("95%") self.opacity_value.setStyleSheet("color: #4ecdc4; font-weight: bold; min-width: 40px;") self.opacity_slider.valueChanged.connect( lambda v: self.opacity_value.setText(f"{v}%") ) opacity_row.addWidget(self.opacity_value) opacity_layout.addLayout(opacity_row) layout.addWidget(opacity_group) # Preview preview_group = QGroupBox("Preview") preview_group.setStyleSheet(self._group_style()) preview_layout = QVBoxLayout(preview_group) preview_btn = QPushButton("Apply Preview") preview_btn.setStyleSheet(self._button_style("#4a9eff")) preview_btn.clicked.connect(self._apply_preview) preview_layout.addWidget(preview_btn) layout.addWidget(preview_group) layout.addStretch() return tab def _create_plugins_tab(self) -> QWidget: """Create plugins management tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Info info = QLabel("Manage installed plugins. Enabled plugins will load on startup.") info.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;") layout.addWidget(info) # Plugin list self.plugins_list = QListWidget() self.plugins_list.setStyleSheet(""" QListWidget { background-color: rgba(30, 35, 45, 200); border: 1px solid rgba(100, 110, 130, 80); border-radius: 8px; color: white; padding: 5px; } QListWidget::item { padding: 10px; border-radius: 6px; } QListWidget::item:hover { background-color: rgba(255, 255, 255, 10); } QListWidget::item:selected { background-color: rgba(74, 158, 255, 100); } """) self._populate_plugins_list() layout.addWidget(self.plugins_list) # Plugin actions actions_layout = QHBoxLayout() enable_btn = QPushButton("Enable") enable_btn.setStyleSheet(self._button_style("#4ecdc4")) enable_btn.clicked.connect(self._enable_selected_plugin) actions_layout.addWidget(enable_btn) disable_btn = QPushButton("Disable") disable_btn.setStyleSheet(self._button_style("#ff8c42")) disable_btn.clicked.connect(self._disable_selected_plugin) actions_layout.addWidget(disable_btn) configure_btn = QPushButton("Configure") configure_btn.setStyleSheet(self._button_style("#4a9eff")) actions_layout.addWidget(configure_btn) actions_layout.addStretch() store_btn = QPushButton("๐ Plugin Store") store_btn.setStyleSheet(self._button_style("#9b59b6")) store_btn.clicked.connect(self._open_plugin_store) actions_layout.addWidget(store_btn) layout.addLayout(actions_layout) return tab def _populate_plugins_list(self): """Populate plugins list.""" self.plugins_list.clear() if not self.plugin_manager: item = QListWidgetItem("Plugin manager not available") item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEnabled) self.plugins_list.addItem(item) return discovered = self.plugin_manager.get_all_discovered_plugins() loaded = self.plugin_manager.get_all_plugins() for plugin_id, plugin_class in discovered.items(): is_loaded = plugin_id in loaded is_enabled = self.plugin_manager.is_plugin_enabled(plugin_id) status = "โ " if is_loaded else ("๐ฆ" if is_enabled else "โ") text = f"{status} {plugin_class.name} (v{plugin_class.version})" item = QListWidgetItem(text) item.setData(Qt.ItemDataRole.UserRole, plugin_id) item.setData(Qt.ItemDataRole.UserRole + 1, is_enabled) if is_loaded: item.setBackground(QColor(78, 205, 196, 30)) elif is_enabled: item.setBackground(QColor(255, 140, 66, 30)) self.plugins_list.addItem(item) def _create_hotkeys_tab(self) -> QWidget: """Create hotkeys configuration tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Info info = QLabel("Double-click a hotkey to edit. Changes apply after restart.") info.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;") layout.addWidget(info) # Hotkeys list self.hotkeys_list = QListWidget() self.hotkeys_list.setStyleSheet(""" QListWidget { background-color: rgba(30, 35, 45, 200); border: 1px solid rgba(100, 110, 130, 80); border-radius: 8px; color: white; padding: 5px; } QListWidget::item { padding: 12px 10px; border-radius: 6px; } QListWidget::item:hover { background-color: rgba(255, 255, 255, 10); } """) self.hotkeys_list.itemDoubleClicked.connect(self._edit_hotkey) self._populate_hotkeys_list() layout.addWidget(self.hotkeys_list) # Actions actions_layout = QHBoxLayout() add_btn = QPushButton("Add Hotkey") add_btn.setStyleSheet(self._button_style("#4a9eff")) actions_layout.addWidget(add_btn) reset_btn = QPushButton("Reset to Defaults") reset_btn.setStyleSheet(self._button_style("#ff4757")) reset_btn.clicked.connect(self._reset_hotkeys) actions_layout.addWidget(reset_btn) actions_layout.addStretch() layout.addLayout(actions_layout) return tab def _populate_hotkeys_list(self): """Populate hotkeys list.""" self.hotkeys_list.clear() # Default hotkeys hotkeys = [ ("Toggle Overlay", "Ctrl+Shift+U"), ("Quick Search", "Ctrl+Shift+F"), ("Settings", "Ctrl+Shift+,"), ("Screenshot", "Ctrl+Shift+S"), ("Activity Bar", "Ctrl+Shift+A"), ] # Load from database stored_hotkeys = self.data_store.get_hotkeys() for action, default in hotkeys: combo = stored_hotkeys.get(action, {}).get('key_combo', default) enabled = stored_hotkeys.get(action, {}).get('enabled', True) status = "โ" if enabled else "โ" text = f"{status} {action}: {combo}" item = QListWidgetItem(text) item.setData(Qt.ItemDataRole.UserRole, action) item.setData(Qt.ItemDataRole.UserRole + 1, combo) if not enabled: item.setForeground(QColor(150, 150, 150)) self.hotkeys_list.addItem(item) def _edit_hotkey(self, item: QListWidgetItem): """Edit a hotkey.""" action = item.data(Qt.ItemDataRole.UserRole) current = item.data(Qt.ItemDataRole.UserRole + 1) dialog = HotkeyEditDialog(action, current, self) if dialog.exec(): new_combo = dialog.get_key_combo() # Save to database self.data_store.save_hotkey(action, new_combo) # Update UI self._populate_hotkeys_list() def _reset_hotkeys(self): """Reset hotkeys to defaults.""" reply = QMessageBox.question( self, "Reset Hotkeys", "Reset all hotkeys to default values?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: # Clear all hotkeys # (In a real implementation, you'd delete them from the database) self._populate_hotkeys_list() def _create_data_tab(self) -> QWidget: """Create data and backup tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Backup backup_group = QGroupBox("Backup & Restore") backup_group.setStyleSheet(self._group_style()) backup_layout = QVBoxLayout(backup_group) export_btn = QPushButton("๐ค Export All Data") export_btn.setStyleSheet(self._button_style("#4a9eff")) export_btn.clicked.connect(self._export_data) backup_layout.addWidget(export_btn) import_btn = QPushButton("๐ฅ Import Data") import_btn.setStyleSheet(self._button_style("#4ecdc4")) import_btn.clicked.connect(self._import_data) backup_layout.addWidget(import_btn) layout.addWidget(backup_group) # Data management data_group = QGroupBox("Data Management") data_group.setStyleSheet(self._group_style()) data_layout = QVBoxLayout(data_group) # Stats stats_btn = QPushButton("๐ View Statistics") stats_btn.setStyleSheet(self._button_style("#9b59b6")) stats_btn.clicked.connect(self._show_stats) data_layout.addWidget(stats_btn) # Clear clear_btn = QPushButton("๐ Clear All Data") clear_btn.setStyleSheet(self._button_style("#ff4757")) clear_btn.clicked.connect(self._clear_data) data_layout.addWidget(clear_btn) layout.addWidget(data_group) # Maintenance maint_group = QGroupBox("Maintenance") maint_group.setStyleSheet(self._group_style()) maint_layout = QVBoxLayout(maint_group) vacuum_btn = QPushButton("๐งน Optimize Database") vacuum_btn.setStyleSheet(self._button_style("#f39c12")) vacuum_btn.clicked.connect(self._optimize_database) maint_layout.addWidget(vacuum_btn) layout.addWidget(maint_group) layout.addStretch() return tab def _create_about_tab(self) -> QWidget: """Create about tab.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) # Logo/Title title = QLabel("EU-Utility") title.setStyleSheet("font-size: 32px; font-weight: bold; color: #ff8c42;") title.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(title) version = QLabel("Version 2.1.0") version.setStyleSheet("font-size: 16px; color: rgba(255, 255, 255, 150);") version.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(version) # System info info_group = QGroupBox("System Information") info_group.setStyleSheet(self._group_style()) info_layout = QFormLayout(info_group) info_layout.setSpacing(10) info_layout.addRow("Platform:", QLabel(platform.system())) info_layout.addRow("Version:", QLabel(platform.version())) info_layout.addRow("Python:", QLabel(platform.python_version())) layout.addWidget(info_group) # Links links_layout = QHBoxLayout() docs_btn = QPushButton("๐ Documentation") docs_btn.setStyleSheet(self._button_style("#4a9eff")) links_layout.addWidget(docs_btn) github_btn = QPushButton("๐ GitHub") github_btn.setStyleSheet(self._button_style("#333")) links_layout.addWidget(github_btn) layout.addLayout(links_layout) layout.addStretch() # Copyright copyright = QLabel("ยฉ 2025 EU-Utility Project") copyright.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;") copyright.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(copyright) return tab def _load_settings(self): """Load settings from database.""" # General self.auto_start_cb.setChecked( self.data_store.get_preference('auto_start', False) ) self.start_minimized_cb.setChecked( self.data_store.get_preference('start_minimized', False) ) self.minimize_to_tray_cb.setChecked( self.data_store.get_preference('minimize_to_tray', True) ) self.show_notifications_cb.setChecked( self.data_store.get_preference('show_notifications', True) ) self.activity_bar_cb.setChecked( self.data_store.get_preference('show_activity_bar', True) ) self.update_interval.setValue( self.data_store.get_preference('update_interval', 1000) ) # Appearance theme = self.data_store.get_preference('theme', 'Dark (EU Style)') index = self.theme_combo.findText(theme) if index >= 0: self.theme_combo.setCurrentIndex(index) opacity = self.data_store.get_preference('window_opacity', 95) self.opacity_slider.setValue(opacity) self.opacity_value.setText(f"{opacity}%") def _save_all_settings(self): """Save all settings to database.""" # General self.data_store.set_preference('auto_start', self.auto_start_cb.isChecked()) self.data_store.set_preference('start_minimized', self.start_minimized_cb.isChecked()) self.data_store.set_preference('minimize_to_tray', self.minimize_to_tray_cb.isChecked()) self.data_store.set_preference('show_notifications', self.show_notifications_cb.isChecked()) self.data_store.set_preference('show_activity_bar', self.activity_bar_cb.isChecked()) self.data_store.set_preference('update_interval', self.update_interval.value()) # Appearance self.data_store.set_preference('theme', self.theme_combo.currentText()) self.data_store.set_preference('window_opacity', self.opacity_slider.value()) # Log self.data_store.log_activity('settings', 'settings_saved') QMessageBox.information(self, "Settings Saved", "All settings have been saved successfully!") def _on_theme_changed(self, theme: str): """Handle theme change.""" self.theme_changed.emit(theme) def _apply_preview(self): """Apply preview settings.""" self._save_all_settings() def _enable_selected_plugin(self): """Enable selected plugin.""" item = self.plugins_list.currentItem() if not item: return plugin_id = item.data(Qt.ItemDataRole.UserRole) if self.plugin_manager: self.plugin_manager.enable_plugin(plugin_id) self._populate_plugins_list() def _disable_selected_plugin(self): """Disable selected plugin.""" item = self.plugins_list.currentItem() if not item: return plugin_id = item.data(Qt.ItemDataRole.UserRole) if self.plugin_manager: self.plugin_manager.disable_plugin(plugin_id) self._populate_plugins_list() def _open_plugin_store(self): """Open plugin store.""" if self.overlay and hasattr(self.overlay, 'show_plugin_store'): self.overlay.show_plugin_store() def _export_data(self): """Export all data.""" path, _ = QFileDialog.getSaveFileName( self, "Export Data", "eu_utility_backup.json", "JSON (*.json)" ) if path: try: # Get data from database data = { 'preferences': {}, 'plugin_states': {}, 'hotkeys': {}, 'timestamp': str(datetime.now()) } # In real implementation, export all data with open(path, 'w') as f: json.dump(data, f, indent=2) QMessageBox.information(self, "Export Complete", f"Data exported to:\n{path}") except Exception as e: QMessageBox.critical(self, "Export Error", str(e)) def _import_data(self): """Import data.""" path, _ = QFileDialog.getOpenFileName( self, "Import Data", "", "JSON (*.json)" ) if path: reply = QMessageBox.question( self, "Confirm Import", "This will overwrite existing data.\n\nContinue?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: QMessageBox.information(self, "Import Complete", "Data imported successfully!") def _show_stats(self): """Show database statistics.""" stats = self.data_store.get_stats() msg = f"""
| Plugin States: | {stats.get('plugin_states', 0)} |
| User Preferences: | {stats.get('user_preferences', 0)} |
| Sessions: | {stats.get('sessions', 0)} |
| Activity Entries: | {stats.get('activity_log', 0)} |
| Dashboard Widgets: | {stats.get('dashboard_widgets', 0)} |
| Hotkeys: | {stats.get('hotkeys', 0)} |
| Database Size: | {stats.get('db_size_mb', 0)} MB |