""" 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 datetime import datetime 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 |