699 lines
24 KiB
Python
699 lines
24 KiB
Python
"""
|
|
EU-Utility - Dashboard Widgets
|
|
|
|
System Status, Quick Actions, Recent Activity, and Plugin Grid widgets.
|
|
"""
|
|
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Dict, List, Callable, Optional
|
|
|
|
try:
|
|
import psutil
|
|
HAS_PSUTIL = True
|
|
except ImportError:
|
|
HAS_PSUTIL = False
|
|
psutil = None
|
|
|
|
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."""
|
|
if not HAS_PSUTIL:
|
|
# Fallback when psutil is not available
|
|
self.cpu_value.setText("N/A")
|
|
self.ram_value.setText("N/A")
|
|
self.disk_value.setText("N/A")
|
|
self._update_services()
|
|
return
|
|
|
|
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
|