556 lines
19 KiB
Python
556 lines
19 KiB
Python
"""
|
|
EU-Utility - Widget Gallery
|
|
|
|
Display available widgets, create widget instances, and manage widget configuration.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional, Callable
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QScrollArea, QFrame, QGridLayout, QDialog, QComboBox,
|
|
QSpinBox, QFormLayout, QDialogButtonBox, QMessageBox,
|
|
QGraphicsDropShadowEffect, QSizePolicy
|
|
)
|
|
from PyQt6.QtCore import Qt, pyqtSignal, QSize
|
|
from PyQt6.QtGui import QColor
|
|
|
|
from core.icon_manager import get_icon_manager
|
|
from core.data.sqlite_store import get_sqlite_store
|
|
from core.widgets.dashboard_widgets import (
|
|
DashboardWidget, WIDGET_TYPES, create_widget,
|
|
SystemStatusWidget, QuickActionsWidget, RecentActivityWidget, PluginGridWidget
|
|
)
|
|
|
|
|
|
class WidgetGalleryItem(QFrame):
|
|
"""Widget gallery item showing available widget."""
|
|
|
|
add_clicked = pyqtSignal(str)
|
|
|
|
def __init__(self, widget_type: str, widget_class: type, parent=None):
|
|
super().__init__(parent)
|
|
self.widget_type = widget_type
|
|
self.widget_class = widget_class
|
|
self.icon_manager = get_icon_manager()
|
|
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup gallery item UI."""
|
|
self.setFixedSize(200, 140)
|
|
self.setStyleSheet("""
|
|
WidgetGalleryItem {
|
|
background-color: rgba(35, 40, 55, 200);
|
|
border: 1px solid rgba(100, 110, 130, 80);
|
|
border-radius: 12px;
|
|
}
|
|
WidgetGalleryItem:hover {
|
|
border: 1px solid #4a9eff;
|
|
background-color: rgba(45, 50, 70, 200);
|
|
}
|
|
""")
|
|
|
|
# Shadow
|
|
shadow = QGraphicsDropShadowEffect()
|
|
shadow.setBlurRadius(15)
|
|
shadow.setColor(QColor(0, 0, 0, 60))
|
|
shadow.setOffset(0, 3)
|
|
self.setGraphicsEffect(shadow)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
layout.setSpacing(8)
|
|
|
|
# Icon
|
|
icon_widget = QLabel()
|
|
try:
|
|
icon_name = getattr(self.widget_class, 'icon_name', 'box')
|
|
icon_pixmap = self.icon_manager.get_pixmap(icon_name, size=32)
|
|
icon_widget.setPixmap(icon_pixmap)
|
|
except:
|
|
icon_widget.setText("📦")
|
|
icon_widget.setStyleSheet("font-size: 24px;")
|
|
icon_widget.setFixedSize(32, 32)
|
|
icon_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(icon_widget, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
# Name
|
|
name = QLabel(getattr(self.widget_class, 'name', 'Widget'))
|
|
name.setStyleSheet("color: white; font-size: 13px; font-weight: bold;")
|
|
name.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(name)
|
|
|
|
# Description
|
|
desc = QLabel(getattr(self.widget_class, 'description', '')[:50] + "...")
|
|
desc.setStyleSheet("color: rgba(255, 255, 255, 120); font-size: 10px;")
|
|
desc.setWordWrap(True)
|
|
desc.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(desc)
|
|
|
|
layout.addStretch()
|
|
|
|
# Add button
|
|
add_btn = QPushButton("+ Add to Dashboard")
|
|
add_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #4a9eff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #3a8eef;
|
|
}
|
|
""")
|
|
add_btn.clicked.connect(self._on_add_clicked)
|
|
layout.addWidget(add_btn)
|
|
|
|
def _on_add_clicked(self):
|
|
"""Handle add button click."""
|
|
self.add_clicked.emit(self.widget_type)
|
|
|
|
|
|
class WidgetConfigDialog(QDialog):
|
|
"""Dialog for configuring widget position and size."""
|
|
|
|
def __init__(self, widget_type: str, widget_class: type, parent=None):
|
|
super().__init__(parent)
|
|
self.widget_type = widget_type
|
|
self.widget_class = widget_class
|
|
self.config = {}
|
|
|
|
self.setWindowTitle(f"Configure {widget_class.name}")
|
|
self.setMinimumSize(300, 200)
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup dialog UI."""
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(15)
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
# Form
|
|
form = QFormLayout()
|
|
form.setSpacing(10)
|
|
|
|
# Position
|
|
self.row_spin = QSpinBox()
|
|
self.row_spin.setRange(0, 10)
|
|
self.row_spin.setValue(0)
|
|
form.addRow("Row:", self.row_spin)
|
|
|
|
self.col_spin = QSpinBox()
|
|
self.col_spin.setRange(0, 3)
|
|
self.col_spin.setValue(0)
|
|
form.addRow("Column:", self.col_spin)
|
|
|
|
# Size (if widget supports variable size)
|
|
size = getattr(self.widget_class, 'size', (1, 1))
|
|
|
|
self.width_spin = QSpinBox()
|
|
self.width_spin.setRange(1, 3)
|
|
self.width_spin.setValue(size[0])
|
|
form.addRow("Width (cols):", self.width_spin)
|
|
|
|
self.height_spin = QSpinBox()
|
|
self.height_spin.setRange(1, 3)
|
|
self.height_spin.setValue(size[1])
|
|
form.addRow("Height (rows):", self.height_spin)
|
|
|
|
layout.addLayout(form)
|
|
|
|
# Info label
|
|
info = QLabel(f"Default size: {size[0]}x{size[1]} cols x rows")
|
|
info.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px; font-style: italic;")
|
|
layout.addWidget(info)
|
|
|
|
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_config(self) -> Dict:
|
|
"""Get widget configuration."""
|
|
return {
|
|
'widget_type': self.widget_type,
|
|
'position': {
|
|
'row': self.row_spin.value(),
|
|
'col': self.col_spin.value()
|
|
},
|
|
'size': {
|
|
'width': self.width_spin.value(),
|
|
'height': self.height_spin.value()
|
|
}
|
|
}
|
|
|
|
|
|
class WidgetGallery(QFrame):
|
|
"""Widget Gallery for browsing and adding widgets."""
|
|
|
|
widget_added = pyqtSignal(str, dict) # widget_type, config
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.icon_manager = get_icon_manager()
|
|
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup gallery UI."""
|
|
self.setStyleSheet("""
|
|
WidgetGallery {
|
|
background-color: rgba(25, 30, 40, 250);
|
|
border: 1px solid rgba(100, 110, 130, 80);
|
|
border-radius: 12px;
|
|
}
|
|
""")
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
layout.setSpacing(15)
|
|
|
|
# Header
|
|
header_layout = QHBoxLayout()
|
|
|
|
header = QLabel("🎨 Widget Gallery")
|
|
header.setStyleSheet("font-size: 20px; font-weight: bold; color: white;")
|
|
header_layout.addWidget(header)
|
|
|
|
header_layout.addStretch()
|
|
|
|
# Close button
|
|
close_btn = QPushButton("✕")
|
|
close_btn.setFixedSize(28, 28)
|
|
close_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: rgba(255, 255, 255, 10);
|
|
color: rgba(255, 255, 255, 150);
|
|
border: none;
|
|
border-radius: 14px;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: rgba(255, 71, 71, 150);
|
|
color: white;
|
|
}
|
|
""")
|
|
close_btn.clicked.connect(self.hide)
|
|
header_layout.addWidget(close_btn)
|
|
|
|
layout.addLayout(header_layout)
|
|
|
|
# Description
|
|
desc = QLabel("Click on a widget to add it to your dashboard.")
|
|
desc.setStyleSheet("color: rgba(255, 255, 255, 120); font-size: 12px;")
|
|
layout.addWidget(desc)
|
|
|
|
# Widget grid
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
scroll.setStyleSheet("background: transparent; border: none;")
|
|
|
|
grid_widget = QWidget()
|
|
grid_layout = QGridLayout(grid_widget)
|
|
grid_layout.setSpacing(15)
|
|
grid_layout.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
|
|
|
# Add widget items
|
|
cols = 3
|
|
for i, (widget_type, widget_class) in enumerate(WIDGET_TYPES.items()):
|
|
item = WidgetGalleryItem(widget_type, widget_class)
|
|
item.add_clicked.connect(self._on_widget_add)
|
|
row = i // cols
|
|
col = i % cols
|
|
grid_layout.addWidget(item, row, col)
|
|
|
|
grid_layout.setColumnStretch(cols, 1)
|
|
grid_layout.setRowStretch((len(WIDGET_TYPES) // cols) + 1, 1)
|
|
|
|
scroll.setWidget(grid_widget)
|
|
layout.addWidget(scroll)
|
|
|
|
def _on_widget_add(self, widget_type: str):
|
|
"""Handle widget add request."""
|
|
widget_class = WIDGET_TYPES.get(widget_type)
|
|
if not widget_class:
|
|
return
|
|
|
|
# Show config dialog
|
|
dialog = WidgetConfigDialog(widget_type, widget_class, self)
|
|
if dialog.exec():
|
|
config = dialog.get_config()
|
|
self.widget_added.emit(widget_type, config)
|
|
|
|
# Log activity
|
|
store = get_sqlite_store()
|
|
store.log_activity('ui', 'widget_added', f"Type: {widget_type}")
|
|
|
|
|
|
class DashboardWidgetManager(QWidget):
|
|
"""Manager for dashboard widgets with drag-drop and configuration."""
|
|
|
|
widget_created = pyqtSignal(str, QWidget) # widget_id, widget
|
|
|
|
def __init__(self, plugin_manager=None, parent=None):
|
|
super().__init__(parent)
|
|
self.plugin_manager = plugin_manager
|
|
self.widgets: Dict[str, DashboardWidget] = {}
|
|
self.widget_configs: Dict[str, dict] = {}
|
|
|
|
self._setup_ui()
|
|
self._load_saved_widgets()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup widget manager UI."""
|
|
self.setStyleSheet("background: transparent;")
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(15)
|
|
|
|
# Controls bar
|
|
controls_layout = QHBoxLayout()
|
|
|
|
self.gallery_btn = QPushButton("🎨 Widget Gallery")
|
|
self.gallery_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #4a9eff;
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #3a8eef;
|
|
}
|
|
""")
|
|
self.gallery_btn.clicked.connect(self._show_gallery)
|
|
controls_layout.addWidget(self.gallery_btn)
|
|
|
|
controls_layout.addStretch()
|
|
|
|
# Reset button
|
|
reset_btn = QPushButton("↺ Reset")
|
|
reset_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: rgba(255, 255, 255, 10);
|
|
color: rgba(255, 255, 255, 150);
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: rgba(255, 71, 71, 100);
|
|
color: white;
|
|
}
|
|
""")
|
|
reset_btn.clicked.connect(self._reset_widgets)
|
|
controls_layout.addWidget(reset_btn)
|
|
|
|
layout.addLayout(controls_layout)
|
|
|
|
# Widget grid container
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
scroll.setStyleSheet("""
|
|
QScrollArea {
|
|
background: transparent;
|
|
border: none;
|
|
}
|
|
QScrollBar:vertical {
|
|
background: rgba(0, 0, 0, 50);
|
|
width: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
QScrollBar::handle:vertical {
|
|
background: rgba(255, 255, 255, 30);
|
|
border-radius: 4px;
|
|
}
|
|
""")
|
|
|
|
self.grid_widget = QWidget()
|
|
self.grid_layout = QGridLayout(self.grid_widget)
|
|
self.grid_layout.setSpacing(15)
|
|
self.grid_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
scroll.setWidget(self.grid_widget)
|
|
layout.addWidget(scroll)
|
|
|
|
# Widget gallery (hidden by default)
|
|
self.gallery = WidgetGallery(self)
|
|
self.gallery.hide()
|
|
self.gallery.widget_added.connect(self._add_widget_from_gallery)
|
|
layout.addWidget(self.gallery)
|
|
|
|
def _show_gallery(self):
|
|
"""Show the widget gallery."""
|
|
if self.gallery.isVisible():
|
|
self.gallery.hide()
|
|
self.gallery_btn.setText("🎨 Widget Gallery")
|
|
else:
|
|
self.gallery.show()
|
|
self.gallery_btn.setText("✕ Hide Gallery")
|
|
|
|
def _add_widget_from_gallery(self, widget_type: str, config: dict):
|
|
"""Add a widget from the gallery."""
|
|
widget_id = f"{widget_type}_{len(self.widgets)}"
|
|
self.add_widget(widget_type, widget_id, config)
|
|
|
|
# Save to database
|
|
self._save_widget_config(widget_id, widget_type, config)
|
|
|
|
def add_widget(self, widget_type: str, widget_id: str, config: dict = None) -> Optional[QWidget]:
|
|
"""Add a widget to the dashboard."""
|
|
# Create widget
|
|
if widget_type == 'plugin_grid':
|
|
widget = create_widget(widget_type, plugin_manager=self.plugin_manager, parent=self)
|
|
else:
|
|
widget = create_widget(widget_type, parent=self)
|
|
|
|
if not widget:
|
|
return None
|
|
|
|
# Store widget
|
|
self.widgets[widget_id] = widget
|
|
self.widget_configs[widget_id] = config or {}
|
|
|
|
# Get position and size from config
|
|
pos = config.get('position', {'row': 0, 'col': 0}) if config else {'row': 0, 'col': 0}
|
|
size = config.get('size', {'width': 1, 'height': 1}) if config else {'width': 1, 'height': 1}
|
|
|
|
# Add to grid
|
|
self.grid_layout.addWidget(
|
|
widget,
|
|
pos.get('row', 0),
|
|
pos.get('col', 0),
|
|
size.get('height', 1),
|
|
size.get('width', 1)
|
|
)
|
|
|
|
self.widget_created.emit(widget_id, widget)
|
|
return widget
|
|
|
|
def remove_widget(self, widget_id: str) -> bool:
|
|
"""Remove a widget from the dashboard."""
|
|
if widget_id not in self.widgets:
|
|
return False
|
|
|
|
widget = self.widgets[widget_id]
|
|
self.grid_layout.removeWidget(widget)
|
|
widget.deleteLater()
|
|
|
|
del self.widgets[widget_id]
|
|
del self.widget_configs[widget_id]
|
|
|
|
# Remove from database
|
|
store = get_sqlite_store()
|
|
store.delete_widget(widget_id)
|
|
|
|
return True
|
|
|
|
def _save_widget_config(self, widget_id: str, widget_type: str, config: dict):
|
|
"""Save widget configuration to database."""
|
|
store = get_sqlite_store()
|
|
pos = config.get('position', {'row': 0, 'col': 0})
|
|
size = config.get('size', {'width': 1, 'height': 1})
|
|
|
|
store.save_widget_config(
|
|
widget_id=widget_id,
|
|
widget_type=widget_type,
|
|
row=pos.get('row', 0),
|
|
col=pos.get('col', 0),
|
|
width=size.get('width', 1),
|
|
height=size.get('height', 1),
|
|
config=config
|
|
)
|
|
|
|
def _load_saved_widgets(self):
|
|
"""Load saved widget configurations."""
|
|
store = get_sqlite_store()
|
|
configs = store.load_widget_configs()
|
|
|
|
# Clear default grid first
|
|
while self.grid_layout.count():
|
|
item = self.grid_layout.takeAt(0)
|
|
if item.widget():
|
|
item.widget().deleteLater()
|
|
|
|
self.widgets.clear()
|
|
self.widget_configs.clear()
|
|
|
|
# Load saved widgets
|
|
for config in configs:
|
|
widget_type = config.get('widget_type')
|
|
widget_id = config.get('widget_id')
|
|
|
|
if widget_type and widget_id:
|
|
self.add_widget(widget_type, widget_id, config)
|
|
|
|
# Add default widgets if none loaded
|
|
if not self.widgets:
|
|
self._add_default_widgets()
|
|
|
|
def _add_default_widgets(self):
|
|
"""Add default widgets."""
|
|
defaults = [
|
|
('system_status', {'position': {'row': 0, 'col': 0}, 'size': {'width': 2, 'height': 1}}),
|
|
('quick_actions', {'position': {'row': 0, 'col': 2}, 'size': {'width': 2, 'height': 1}}),
|
|
('recent_activity', {'position': {'row': 1, 'col': 0}, 'size': {'width': 1, 'height': 2}}),
|
|
('plugin_grid', {'position': {'row': 1, 'col': 1}, 'size': {'width': 3, 'height': 2}}),
|
|
]
|
|
|
|
for i, (widget_type, config) in enumerate(defaults):
|
|
widget_id = f"{widget_type}_default_{i}"
|
|
self.add_widget(widget_type, widget_id, config)
|
|
self._save_widget_config(widget_id, widget_type, config)
|
|
|
|
def _reset_widgets(self):
|
|
"""Reset to default widgets."""
|
|
reply = QMessageBox.question(
|
|
self, "Reset Widgets",
|
|
"This will remove all custom widgets and reset to defaults.\n\nContinue?",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
# Clear all widgets
|
|
for widget_id in list(self.widgets.keys()):
|
|
self.remove_widget(widget_id)
|
|
|
|
# Add defaults
|
|
self._add_default_widgets()
|
|
|
|
# Log
|
|
store = get_sqlite_store()
|
|
store.log_activity('ui', 'widgets_reset')
|
|
|
|
def get_widget(self, widget_id: str) -> Optional[DashboardWidget]:
|
|
"""Get a widget by ID."""
|
|
return self.widgets.get(widget_id)
|
|
|
|
def get_all_widgets(self) -> Dict[str, DashboardWidget]:
|
|
"""Get all widgets."""
|
|
return self.widgets.copy()
|
|
|
|
def set_plugin_manager(self, plugin_manager):
|
|
"""Set the plugin manager."""
|
|
self.plugin_manager = plugin_manager
|
|
|
|
# Update plugin grid widgets
|
|
for widget in self.widgets.values():
|
|
if isinstance(widget, PluginGridWidget):
|
|
widget.set_plugin_manager(plugin_manager)
|