""" EU-Utility - Dashboard Widgets System Status, Quick Actions, Recent Activity, and Plugin Grid widgets. """ import os import psutil from datetime import datetime from typing import Dict, List, Callable, Optional from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QProgressBar, QFrame, QGridLayout, QSizePolicy, QScrollArea, QGraphicsDropShadowEffect ) from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QColor, QPixmap from core.icon_manager import get_icon_manager from core.eu_styles import get_color from core.data.sqlite_store import get_sqlite_store class DashboardWidget(QFrame): """Base class for dashboard widgets.""" name = "Widget" description = "Base widget" icon_name = "target" size = (1, 1) # Grid size (cols, rows) def __init__(self, parent=None): super().__init__(parent) self.icon_manager = get_icon_manager() self._setup_frame() self._setup_ui() def _setup_frame(self): """Setup widget frame styling.""" self.setFrameStyle(QFrame.Shape.NoFrame) self.setStyleSheet(""" DashboardWidget { background-color: rgba(30, 35, 45, 200); border: 1px solid rgba(100, 150, 200, 60); border-radius: 12px; } DashboardWidget:hover { border: 1px solid rgba(100, 180, 255, 100); } """) # Shadow effect shadow = QGraphicsDropShadowEffect() shadow.setBlurRadius(20) shadow.setColor(QColor(0, 0, 0, 80)) shadow.setOffset(0, 4) self.setGraphicsEffect(shadow) def _setup_ui(self): """Setup widget UI. Override in subclass.""" layout = QVBoxLayout(self) layout.setContentsMargins(12, 12, 12, 12) header = QLabel(self.name) header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 12px; font-weight: bold;") layout.addWidget(header) content = QLabel("Widget Content") content.setStyleSheet("color: rgba(255, 255, 255, 150);") layout.addWidget(content) layout.addStretch() class SystemStatusWidget(DashboardWidget): """System Status widget showing CPU, RAM, and service status.""" name = "System Status" description = "Monitor system resources and service status" icon_name = "activity" size = (2, 1) def __init__(self, parent=None): self.services = {} super().__init__(parent) # Update timer self.timer = QTimer(self) self.timer.timeout.connect(self._update_stats) self.timer.start(2000) # Update every 2 seconds self._update_stats() def _setup_ui(self): """Setup system status UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) layout.setSpacing(10) # Header header_layout = QHBoxLayout() icon_label = QLabel() icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=18) icon_label.setPixmap(icon_pixmap) icon_label.setFixedSize(18, 18) header_layout.addWidget(icon_label) header = QLabel(self.name) header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 13px; font-weight: bold;") header_layout.addWidget(header) header_layout.addStretch() self.status_indicator = QLabel("●") self.status_indicator.setStyleSheet("color: #4ecdc4; font-size: 12px;") header_layout.addWidget(self.status_indicator) layout.addLayout(header_layout) # Stats grid stats_layout = QGridLayout() stats_layout.setSpacing(10) # CPU cpu_label = QLabel("CPU") cpu_label.setStyleSheet("color: rgba(255, 255, 255, 120); font-size: 11px;") stats_layout.addWidget(cpu_label, 0, 0) self.cpu_bar = QProgressBar() self.cpu_bar.setRange(0, 100) self.cpu_bar.setValue(0) self.cpu_bar.setTextVisible(False) self.cpu_bar.setFixedHeight(6) self.cpu_bar.setStyleSheet(""" QProgressBar { background-color: rgba(255, 255, 255, 30); border-radius: 3px; } QProgressBar::chunk { background-color: #4ecdc4; border-radius: 3px; } """) stats_layout.addWidget(self.cpu_bar, 0, 1) self.cpu_value = QLabel("0%") self.cpu_value.setStyleSheet("color: #4ecdc4; font-size: 11px; font-weight: bold;") stats_layout.addWidget(self.cpu_value, 0, 2) # RAM ram_label = QLabel("RAM") ram_label.setStyleSheet("color: rgba(255, 255, 255, 120); font-size: 11px;") stats_layout.addWidget(ram_label, 1, 0) self.ram_bar = QProgressBar() self.ram_bar.setRange(0, 100) self.ram_bar.setValue(0) self.ram_bar.setTextVisible(False) self.ram_bar.setFixedHeight(6) self.ram_bar.setStyleSheet(""" QProgressBar { background-color: rgba(255, 255, 255, 30); border-radius: 3px; } QProgressBar::chunk { background-color: #ff8c42; border-radius: 3px; } """) stats_layout.addWidget(self.ram_bar, 1, 1) self.ram_value = QLabel("0%") self.ram_value.setStyleSheet("color: #ff8c42; font-size: 11px; font-weight: bold;") stats_layout.addWidget(self.ram_value, 1, 2) # Disk disk_label = QLabel("Disk") disk_label.setStyleSheet("color: rgba(255, 255, 255, 120); font-size: 11px;") stats_layout.addWidget(disk_label, 2, 0) self.disk_bar = QProgressBar() self.disk_bar.setRange(0, 100) self.disk_bar.setValue(0) self.disk_bar.setTextVisible(False) self.disk_bar.setFixedHeight(6) self.disk_bar.setStyleSheet(""" QProgressBar { background-color: rgba(255, 255, 255, 30); border-radius: 3px; } QProgressBar::chunk { background-color: #4a9eff; border-radius: 3px; } """) stats_layout.addWidget(self.disk_bar, 2, 1) self.disk_value = QLabel("0%") self.disk_value.setStyleSheet("color: #4a9eff; font-size: 11px; font-weight: bold;") stats_layout.addWidget(self.disk_value, 2, 2) stats_layout.setColumnStretch(1, 1) layout.addLayout(stats_layout) # Service status services_label = QLabel("Services") services_label.setStyleSheet("color: rgba(255, 255, 255, 120); font-size: 11px; margin-top: 5px;") layout.addWidget(services_label) self.services_layout = QHBoxLayout() self.services_layout.setSpacing(8) layout.addLayout(self.services_layout) layout.addStretch() def _update_stats(self): """Update system statistics.""" try: # CPU cpu_percent = psutil.cpu_percent(interval=0.1) self.cpu_bar.setValue(int(cpu_percent)) self.cpu_value.setText(f"{cpu_percent:.1f}%") # RAM ram = psutil.virtual_memory() self.ram_bar.setValue(ram.percent) self.ram_value.setText(f"{ram.percent}%") # Disk disk = psutil.disk_usage('/') disk_percent = (disk.used / disk.total) * 100 self.disk_bar.setValue(int(disk_percent)) self.disk_value.setText(f"{disk_percent:.1f}%") # Update services self._update_services() except Exception as e: print(f"[SystemStatus] Error updating stats: {e}") def _update_services(self): """Update service status indicators.""" # Clear existing while self.services_layout.count(): item = self.services_layout.takeAt(0) if item.widget(): item.widget().deleteLater() # Core services services = [ ("Overlay", True), ("Plugins", True), ("Hotkeys", True), ("Data Store", True), ] for name, status in services: service_widget = QLabel(f"{'●' if status else '○'} {name}") color = "#4ecdc4" if status else "#ff4757" service_widget.setStyleSheet(f"color: {color}; font-size: 10px;") self.services_layout.addWidget(service_widget) self.services_layout.addStretch() def set_service(self, name: str, status: bool): """Set a service status.""" self.services[name] = status self._update_services() class QuickActionsWidget(DashboardWidget): """Quick Actions widget with functional buttons.""" name = "Quick Actions" description = "One-click access to common actions" icon_name = "zap" size = (2, 1) action_triggered = pyqtSignal(str) def __init__(self, parent=None): self.actions = [] super().__init__(parent) def _setup_ui(self): """Setup quick actions UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) layout.setSpacing(10) # Header header_layout = QHBoxLayout() icon_label = QLabel() icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=18) icon_label.setPixmap(icon_pixmap) icon_label.setFixedSize(18, 18) header_layout.addWidget(icon_label) header = QLabel(self.name) header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 13px; font-weight: bold;") header_layout.addWidget(header) header_layout.addStretch() layout.addLayout(header_layout) # Actions grid self.actions_grid = QGridLayout() self.actions_grid.setSpacing(8) layout.addLayout(self.actions_grid) layout.addStretch() # Default actions self.set_actions([ {'id': 'search', 'name': 'Search', 'icon': 'search'}, {'id': 'screenshot', 'name': 'Screenshot', 'icon': 'camera'}, {'id': 'settings', 'name': 'Settings', 'icon': 'settings'}, {'id': 'plugins', 'name': 'Plugins', 'icon': 'grid'}, ]) def set_actions(self, actions: List[Dict]): """Set the quick actions.""" self.actions = actions self._render_actions() def _render_actions(self): """Render action buttons.""" # Clear existing while self.actions_grid.count(): item = self.actions_grid.takeAt(0) if item.widget(): item.widget().deleteLater() # Add buttons cols = 4 for i, action in enumerate(self.actions): btn = QPushButton() btn.setFixedSize(48, 48) # Try to get icon icon_name = action.get('icon', 'circle') try: icon_pixmap = self.icon_manager.get_pixmap(icon_name, size=24) btn.setIcon(QPixmap(icon_pixmap)) btn.setIconSize(Qt.QSize(24, 24)) except: btn.setText(action['name'][0]) btn.setStyleSheet(""" QPushButton { background-color: rgba(255, 255, 255, 10); border: 1px solid rgba(255, 255, 255, 20); border-radius: 10px; color: white; font-size: 16px; font-weight: bold; } QPushButton:hover { background-color: rgba(255, 140, 66, 150); border: 1px solid rgba(255, 140, 66, 200); } QPushButton:pressed { background-color: rgba(255, 140, 66, 200); } """) btn.setToolTip(action['name']) action_id = action.get('id', action['name']) btn.clicked.connect(lambda checked, aid=action_id: self._on_action_clicked(aid)) row = i // cols col = i % cols self.actions_grid.addWidget(btn, row, col) def _on_action_clicked(self, action_id: str): """Handle action button click.""" self.action_triggered.emit(action_id) # Log activity store = get_sqlite_store() store.log_activity('ui', 'quick_action', f"Action: {action_id}") class RecentActivityWidget(DashboardWidget): """Recent Activity widget showing real data feed.""" name = "Recent Activity" description = "Shows recent system and plugin activity" icon_name = "clock" size = (1, 2) def __init__(self, parent=None): super().__init__(parent) # Update timer self.timer = QTimer(self) self.timer.timeout.connect(self._refresh_activity) self.timer.start(5000) # Refresh every 5 seconds self._refresh_activity() def _setup_ui(self): """Setup recent activity UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) layout.setSpacing(10) # Header header_layout = QHBoxLayout() icon_label = QLabel() icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=18) icon_label.setPixmap(icon_pixmap) icon_label.setFixedSize(18, 18) header_layout.addWidget(icon_label) header = QLabel(self.name) header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 13px; font-weight: bold;") header_layout.addWidget(header) header_layout.addStretch() layout.addLayout(header_layout) # Activity list self.activity_container = QWidget() self.activity_layout = QVBoxLayout(self.activity_container) self.activity_layout.setSpacing(6) self.activity_layout.setContentsMargins(0, 0, 0, 0) self.activity_layout.setAlignment(Qt.AlignmentFlag.AlignTop) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setStyleSheet("background: transparent; border: none;") scroll.setWidget(self.activity_container) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) layout.addWidget(scroll) def _refresh_activity(self): """Refresh the activity list.""" # Clear existing while self.activity_layout.count(): item = self.activity_layout.takeAt(0) if item.widget(): item.widget().deleteLater() # Get recent activity from database store = get_sqlite_store() activities = store.get_recent_activity(limit=10) if not activities: # Show placeholder placeholder = QLabel("No recent activity") placeholder.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px; font-style: italic;") self.activity_layout.addWidget(placeholder) else: for activity in activities: item = self._create_activity_item(activity) self.activity_layout.addWidget(item) self.activity_layout.addStretch() def _create_activity_item(self, activity: Dict) -> QFrame: """Create an activity item widget.""" frame = QFrame() frame.setStyleSheet(""" QFrame { background-color: rgba(255, 255, 255, 5); border-radius: 6px; } QFrame:hover { background-color: rgba(255, 255, 255, 10); } """) layout = QHBoxLayout(frame) layout.setContentsMargins(8, 6, 8, 6) layout.setSpacing(8) # Icon based on category category_icons = { 'plugin': '🔌', 'ui': '🖱', 'system': '⚙️', 'error': '❌', 'success': '✅', } icon = category_icons.get(activity.get('category', ''), '•') icon_label = QLabel(icon) icon_label.setStyleSheet("font-size: 12px;") layout.addWidget(icon_label) # Action text action_text = activity.get('action', 'Unknown') action_label = QLabel(action_text) action_label.setStyleSheet("color: rgba(255, 255, 255, 180); font-size: 11px;") layout.addWidget(action_label, 1) # Timestamp timestamp = activity.get('timestamp', '') if timestamp: try: dt = datetime.fromisoformat(timestamp) time_str = dt.strftime("%H:%M") except: time_str = timestamp[:5] if len(timestamp) >= 5 else timestamp time_label = QLabel(time_str) time_label.setStyleSheet("color: rgba(255, 255, 255, 80); font-size: 10px;") layout.addWidget(time_label) return frame class PluginGridWidget(DashboardWidget): """Plugin Grid showing actual plugin cards.""" name = "Installed Plugins" description = "Grid of installed plugins with status" icon_name = "grid" size = (2, 2) plugin_clicked = pyqtSignal(str) def __init__(self, plugin_manager=None, parent=None): self.plugin_manager = plugin_manager super().__init__(parent) # Update timer self.timer = QTimer(self) self.timer.timeout.connect(self._refresh_plugins) self.timer.start(3000) # Refresh every 3 seconds self._refresh_plugins() def _setup_ui(self): """Setup plugin grid UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(15, 15, 15, 15) layout.setSpacing(10) # Header with stats header_layout = QHBoxLayout() icon_label = QLabel() icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=18) icon_label.setPixmap(icon_pixmap) icon_label.setFixedSize(18, 18) header_layout.addWidget(icon_label) header = QLabel(self.name) header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 13px; font-weight: bold;") header_layout.addWidget(header) header_layout.addStretch() self.stats_label = QLabel("0 plugins") self.stats_label.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;") header_layout.addWidget(self.stats_label) layout.addLayout(header_layout) # Plugin grid scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) scroll.setStyleSheet("background: transparent; border: none;") scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.grid_widget = QWidget() self.grid_layout = QGridLayout(self.grid_widget) self.grid_layout.setSpacing(8) self.grid_layout.setContentsMargins(0, 0, 0, 0) scroll.setWidget(self.grid_widget) layout.addWidget(scroll) def _refresh_plugins(self): """Refresh the plugin grid.""" # Clear existing while self.grid_layout.count(): item = self.grid_layout.takeAt(0) if item.widget(): item.widget().deleteLater() if not self.plugin_manager: placeholder = QLabel("Plugin manager not available") placeholder.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;") self.grid_layout.addWidget(placeholder, 0, 0) self.stats_label.setText("No plugins") return # Get plugins discovered = self.plugin_manager.get_all_discovered_plugins() loaded = self.plugin_manager.get_all_plugins() self.stats_label.setText(f"{len(loaded)}/{len(discovered)} enabled") if not discovered: placeholder = QLabel("No plugins installed") placeholder.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;") self.grid_layout.addWidget(placeholder, 0, 0) return # Create plugin cards cols = 2 for i, (plugin_id, plugin_class) in enumerate(discovered.items()): card = self._create_plugin_card(plugin_id, plugin_class, plugin_id in loaded) row = i // cols col = i % cols self.grid_layout.addWidget(card, row, col) def _create_plugin_card(self, plugin_id: str, plugin_class, is_loaded: bool) -> QFrame: """Create a plugin card.""" card = QFrame() card.setStyleSheet(""" QFrame { background-color: rgba(255, 255, 255, 8); border: 1px solid rgba(255, 255, 255, 15); border-radius: 8px; } QFrame:hover { background-color: rgba(255, 255, 255, 12); border: 1px solid rgba(255, 255, 255, 25); } """) card.setFixedHeight(70) card.setCursor(Qt.CursorShape.PointingHandCursor) layout = QHBoxLayout(card) layout.setContentsMargins(10, 8, 10, 8) layout.setSpacing(10) # Icon icon = QLabel(getattr(plugin_class, 'icon', '📦')) icon.setStyleSheet("font-size: 20px;") layout.addWidget(icon) # Info info_layout = QVBoxLayout() info_layout.setSpacing(2) name = QLabel(plugin_class.name) name.setStyleSheet("color: white; font-size: 12px; font-weight: bold;") info_layout.addWidget(name) version = QLabel(f"v{plugin_class.version}") version.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 10px;") info_layout.addWidget(version) layout.addLayout(info_layout, 1) # Status indicator status_color = "#4ecdc4" if is_loaded else "#ff8c42" status_text = "●" if is_loaded else "○" status = QLabel(status_text) status.setStyleSheet(f"color: {status_color}; font-size: 14px;") status.setToolTip("Enabled" if is_loaded else "Disabled") layout.addWidget(status) # Click handler card.mousePressEvent = lambda event, pid=plugin_id: self.plugin_clicked.emit(pid) return card def set_plugin_manager(self, plugin_manager): """Set the plugin manager.""" self.plugin_manager = plugin_manager self._refresh_plugins() # Widget factory WIDGET_TYPES = { 'system_status': SystemStatusWidget, 'quick_actions': QuickActionsWidget, 'recent_activity': RecentActivityWidget, 'plugin_grid': PluginGridWidget, } def create_widget(widget_type: str, **kwargs) -> Optional[DashboardWidget]: """Create a widget by type.""" widget_class = WIDGET_TYPES.get(widget_type) if widget_class: return widget_class(**kwargs) return None