716 lines
24 KiB
Python
716 lines
24 KiB
Python
"""
|
|
EU-Utility - Enhanced Activity Bar
|
|
|
|
Windows 11-style taskbar with pinned plugins, app drawer, and search.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Callable
|
|
from dataclasses import dataclass, asdict
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QFrame, QLineEdit, QMenu, QDialog, QSlider, QComboBox,
|
|
QCheckBox, QSpinBox, QApplication, QSizePolicy, QScrollArea,
|
|
QGridLayout, QMessageBox, QGraphicsDropShadowEffect
|
|
)
|
|
from PyQt6.QtCore import Qt, QPoint, QSize, QTimer, pyqtSignal, QPropertyAnimation, QEasingCurve
|
|
from PyQt6.QtGui import QMouseEvent, QPainter, QColor, QFont, QIcon, QPixmap, QDrag
|
|
from PyQt6.QtCore import QMimeData, QByteArray
|
|
|
|
from core.data.sqlite_store import get_sqlite_store
|
|
|
|
|
|
@dataclass
|
|
class ActivityBarConfig:
|
|
"""Activity bar configuration."""
|
|
enabled: bool = True
|
|
position: str = "bottom"
|
|
icon_size: int = 32
|
|
auto_hide: bool = False
|
|
auto_hide_delay: int = 3000
|
|
pinned_plugins: List[str] = None
|
|
|
|
def __post_init__(self):
|
|
if self.pinned_plugins is None:
|
|
self.pinned_plugins = []
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'enabled': self.enabled,
|
|
'position': self.position,
|
|
'icon_size': self.icon_size,
|
|
'auto_hide': self.auto_hide,
|
|
'auto_hide_delay': self.auto_hide_delay,
|
|
'pinned_plugins': self.pinned_plugins
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data):
|
|
return cls(
|
|
enabled=data.get('enabled', True),
|
|
position=data.get('position', 'bottom'),
|
|
icon_size=data.get('icon_size', 32),
|
|
auto_hide=data.get('auto_hide', False),
|
|
auto_hide_delay=data.get('auto_hide_delay', 3000),
|
|
pinned_plugins=data.get('pinned_plugins', [])
|
|
)
|
|
|
|
|
|
class DraggablePluginButton(QPushButton):
|
|
"""Plugin button that supports drag-to-pin."""
|
|
|
|
drag_started = pyqtSignal(str)
|
|
|
|
def __init__(self, plugin_id: str, plugin_name: str, icon_text: str, parent=None):
|
|
super().__init__(parent)
|
|
self.plugin_id = plugin_id
|
|
self.plugin_name = plugin_name
|
|
self.icon_text = icon_text
|
|
self.setText(icon_text)
|
|
self.setFixedSize(40, 40)
|
|
self.setToolTip(plugin_name)
|
|
self._setup_style()
|
|
|
|
def _setup_style(self):
|
|
"""Setup button style."""
|
|
self.setStyleSheet("""
|
|
DraggablePluginButton {
|
|
background: transparent;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 16px;
|
|
}
|
|
DraggablePluginButton:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
DraggablePluginButton:pressed {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
""")
|
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
|
|
def mousePressEvent(self, event):
|
|
"""Start drag on middle click or with modifier."""
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
self.drag_start_pos = event.pos()
|
|
super().mousePressEvent(event)
|
|
|
|
def mouseMoveEvent(self, event):
|
|
"""Handle drag."""
|
|
if not (event.buttons() & Qt.MouseButton.LeftButton):
|
|
return
|
|
|
|
if not hasattr(self, 'drag_start_pos'):
|
|
return
|
|
|
|
# Check if dragged far enough
|
|
if (event.pos() - self.drag_start_pos).manhattanLength() < 10:
|
|
return
|
|
|
|
# Start drag
|
|
drag = QDrag(self)
|
|
mime_data = QMimeData()
|
|
mime_data.setText(self.plugin_id)
|
|
mime_data.setData('application/x-plugin-id', QByteArray(self.plugin_id.encode()))
|
|
drag.setMimeData(mime_data)
|
|
|
|
# Create drag pixmap
|
|
pixmap = QPixmap(40, 40)
|
|
pixmap.fill(Qt.GlobalColor.transparent)
|
|
painter = QPainter(pixmap)
|
|
painter.setBrush(QColor(255, 140, 66, 200))
|
|
painter.drawRoundedRect(0, 0, 40, 40, 8, 8)
|
|
painter.setPen(Qt.GlobalColor.white)
|
|
painter.setFont(QFont("Segoe UI", 14))
|
|
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, self.icon_text)
|
|
painter.end()
|
|
drag.setPixmap(pixmap)
|
|
drag.setHotSpot(QPoint(20, 20))
|
|
|
|
self.drag_started.emit(self.plugin_id)
|
|
drag.exec(Qt.DropAction.MoveAction)
|
|
|
|
|
|
class PinnedPluginsArea(QFrame):
|
|
"""Area for pinned plugins with drop support."""
|
|
|
|
plugin_pinned = pyqtSignal(str)
|
|
plugin_unpinned = pyqtSignal(str)
|
|
plugin_reordered = pyqtSignal(list) # New order of plugin IDs
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.pinned_plugins: List[str] = []
|
|
self.buttons: Dict[str, DraggablePluginButton] = {}
|
|
|
|
self.setAcceptDrops(True)
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup UI."""
|
|
self.setStyleSheet("background: transparent; border: none;")
|
|
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(4)
|
|
layout.addStretch()
|
|
|
|
def add_plugin(self, plugin_id: str, plugin_name: str, icon_text: str = "◆"):
|
|
"""Add a pinned plugin."""
|
|
if plugin_id in self.pinned_plugins:
|
|
return
|
|
|
|
self.pinned_plugins.append(plugin_id)
|
|
|
|
btn = DraggablePluginButton(plugin_id, plugin_name, icon_text)
|
|
btn.clicked.connect(lambda: self._on_plugin_clicked(plugin_id))
|
|
|
|
# Insert before stretch
|
|
layout = self.layout()
|
|
layout.insertWidget(layout.count() - 1, btn)
|
|
|
|
self.buttons[plugin_id] = btn
|
|
|
|
# Log
|
|
store = get_sqlite_store()
|
|
store.log_activity('ui', 'plugin_pinned', f"Plugin: {plugin_id}")
|
|
|
|
def remove_plugin(self, plugin_id: str):
|
|
"""Remove a pinned plugin."""
|
|
if plugin_id not in self.pinned_plugins:
|
|
return
|
|
|
|
self.pinned_plugins.remove(plugin_id)
|
|
|
|
if plugin_id in self.buttons:
|
|
btn = self.buttons[plugin_id]
|
|
self.layout().removeWidget(btn)
|
|
btn.deleteLater()
|
|
del self.buttons[plugin_id]
|
|
|
|
# Log
|
|
store = get_sqlite_store()
|
|
store.log_activity('ui', 'plugin_unpinned', f"Plugin: {plugin_id}")
|
|
|
|
def set_plugins(self, plugins: List[tuple]):
|
|
"""Set all pinned plugins."""
|
|
# Clear existing
|
|
for plugin_id in list(self.pinned_plugins):
|
|
self.remove_plugin(plugin_id)
|
|
|
|
# Add new
|
|
for plugin_id, plugin_name, icon_text in plugins:
|
|
self.add_plugin(plugin_id, plugin_name, icon_text)
|
|
|
|
def _on_plugin_clicked(self, plugin_id: str):
|
|
"""Handle plugin click."""
|
|
parent = self.window()
|
|
if parent and hasattr(parent, 'show_plugin'):
|
|
parent.show_plugin(plugin_id)
|
|
|
|
def dragEnterEvent(self, event):
|
|
"""Accept drag events."""
|
|
if event.mimeData().hasText():
|
|
event.acceptProposedAction()
|
|
|
|
def dropEvent(self, event):
|
|
"""Handle drop."""
|
|
plugin_id = event.mimeData().text()
|
|
self.plugin_pinned.emit(plugin_id)
|
|
event.acceptProposedAction()
|
|
|
|
|
|
class AppDrawer(QFrame):
|
|
"""App drawer popup with all plugins."""
|
|
|
|
plugin_launched = pyqtSignal(str)
|
|
plugin_pin_requested = pyqtSignal(str)
|
|
|
|
def __init__(self, plugin_manager, parent=None):
|
|
super().__init__(parent)
|
|
self.plugin_manager = plugin_manager
|
|
self.search_text = ""
|
|
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup drawer UI."""
|
|
self.setWindowFlags(
|
|
Qt.WindowType.FramelessWindowHint |
|
|
Qt.WindowType.WindowStaysOnTopHint |
|
|
Qt.WindowType.Tool
|
|
)
|
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
self.setFixedSize(420, 500)
|
|
|
|
# Frosted glass effect
|
|
self.setStyleSheet("""
|
|
AppDrawer {
|
|
background: rgba(32, 32, 32, 0.95);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 16px;
|
|
}
|
|
""")
|
|
|
|
# Shadow
|
|
shadow = QGraphicsDropShadowEffect()
|
|
shadow.setBlurRadius(30)
|
|
shadow.setColor(QColor(0, 0, 0, 100))
|
|
shadow.setOffset(0, 8)
|
|
self.setGraphicsEffect(shadow)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
layout.setSpacing(15)
|
|
|
|
# Header
|
|
header = QLabel("All Plugins")
|
|
header.setStyleSheet("color: white; font-size: 18px; font-weight: bold;")
|
|
layout.addWidget(header)
|
|
|
|
# Search box
|
|
self.search_box = QLineEdit()
|
|
self.search_box.setPlaceholderText("🔍 Search plugins...")
|
|
self.search_box.setStyleSheet("""
|
|
QLineEdit {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 10px;
|
|
padding: 10px 15px;
|
|
font-size: 14px;
|
|
}
|
|
QLineEdit:focus {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
border: 1px solid rgba(255, 140, 66, 0.5);
|
|
}
|
|
""")
|
|
self.search_box.textChanged.connect(self._on_search)
|
|
layout.addWidget(self.search_box)
|
|
|
|
# Plugin grid
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
scroll.setStyleSheet("background: transparent; border: none;")
|
|
|
|
self.grid_widget = QWidget()
|
|
self.grid_layout = QGridLayout(self.grid_widget)
|
|
self.grid_layout.setSpacing(10)
|
|
self.grid_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
scroll.setWidget(self.grid_widget)
|
|
layout.addWidget(scroll)
|
|
|
|
self._refresh_plugins()
|
|
|
|
def _refresh_plugins(self):
|
|
"""Refresh 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:
|
|
return
|
|
|
|
all_plugins = self.plugin_manager.get_all_discovered_plugins()
|
|
|
|
# Filter by search
|
|
filtered = []
|
|
for plugin_id, plugin_class in all_plugins.items():
|
|
name = plugin_class.name.lower()
|
|
desc = plugin_class.description.lower()
|
|
search = self.search_text.lower()
|
|
|
|
if not search or search in name or search in desc:
|
|
filtered.append((plugin_id, plugin_class))
|
|
|
|
# Create items
|
|
cols = 3
|
|
for i, (plugin_id, plugin_class) in enumerate(filtered):
|
|
item = self._create_plugin_item(plugin_id, plugin_class)
|
|
row = i // cols
|
|
col = i % cols
|
|
self.grid_layout.addWidget(item, row, col)
|
|
|
|
self.grid_layout.setColumnStretch(cols, 1)
|
|
self.grid_layout.setRowStretch((len(filtered) // cols) + 1, 1)
|
|
|
|
def _create_plugin_item(self, plugin_id: str, plugin_class) -> QFrame:
|
|
"""Create a plugin item."""
|
|
frame = QFrame()
|
|
frame.setFixedSize(110, 110)
|
|
frame.setStyleSheet("""
|
|
QFrame {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 12px;
|
|
}
|
|
QFrame:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
""")
|
|
frame.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
|
|
layout = QVBoxLayout(frame)
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
|
layout.setSpacing(6)
|
|
|
|
# Icon
|
|
icon = QLabel(getattr(plugin_class, 'icon', '📦'))
|
|
icon.setStyleSheet("font-size: 28px;")
|
|
icon.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(icon)
|
|
|
|
# Name
|
|
name = QLabel(plugin_class.name)
|
|
name.setStyleSheet("color: white; font-size: 11px; font-weight: bold;")
|
|
name.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
name.setWordWrap(True)
|
|
layout.addWidget(name)
|
|
|
|
# Click handler
|
|
frame.mousePressEvent = lambda event, pid=plugin_id: self._on_plugin_clicked(pid)
|
|
|
|
# Context menu
|
|
frame.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
frame.customContextMenuRequested.connect(
|
|
lambda pos, pid=plugin_id: self._show_context_menu(pos, pid)
|
|
)
|
|
|
|
return frame
|
|
|
|
def _on_plugin_clicked(self, plugin_id: str):
|
|
"""Handle plugin click."""
|
|
self.plugin_launched.emit(plugin_id)
|
|
self.hide()
|
|
|
|
def _show_context_menu(self, pos, plugin_id: str):
|
|
"""Show context menu."""
|
|
menu = QMenu(self)
|
|
menu.setStyleSheet("""
|
|
QMenu {
|
|
background: rgba(40, 40, 40, 0.95);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
}
|
|
QMenu::item {
|
|
padding: 8px 24px;
|
|
border-radius: 4px;
|
|
}
|
|
QMenu::item:selected {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
""")
|
|
|
|
pin_action = menu.addAction("📌 Pin to Taskbar")
|
|
pin_action.triggered.connect(lambda: self.plugin_pin_requested.emit(plugin_id))
|
|
|
|
menu.exec(self.mapToGlobal(pos))
|
|
|
|
def _on_search(self, text: str):
|
|
"""Handle search."""
|
|
self.search_text = text
|
|
self._refresh_plugins()
|
|
|
|
|
|
class EnhancedActivityBar(QFrame):
|
|
"""Enhanced activity bar with drag-to-pin and search."""
|
|
|
|
plugin_requested = pyqtSignal(str)
|
|
search_requested = pyqtSignal(str)
|
|
settings_requested = pyqtSignal()
|
|
|
|
def __init__(self, plugin_manager, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.plugin_manager = plugin_manager
|
|
self.config = self._load_config()
|
|
|
|
self._setup_ui()
|
|
self._apply_config()
|
|
|
|
# Load pinned plugins
|
|
self._load_pinned_plugins()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup activity bar UI."""
|
|
self.setWindowFlags(
|
|
Qt.WindowType.FramelessWindowHint |
|
|
Qt.WindowType.WindowStaysOnTopHint |
|
|
Qt.WindowType.Tool |
|
|
Qt.WindowType.WindowDoesNotAcceptFocus
|
|
)
|
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
|
|
self.setFixedHeight(56)
|
|
|
|
# Main layout
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(12, 4, 12, 4)
|
|
layout.setSpacing(8)
|
|
|
|
# Style
|
|
self.setStyleSheet("""
|
|
EnhancedActivityBar {
|
|
background: rgba(30, 30, 35, 0.9);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 28px;
|
|
}
|
|
""")
|
|
|
|
# Start button
|
|
self.start_btn = QPushButton("⊞")
|
|
self.start_btn.setFixedSize(40, 40)
|
|
self.start_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 18px;
|
|
}
|
|
QPushButton:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
""")
|
|
self.start_btn.setToolTip("Open App Drawer")
|
|
self.start_btn.clicked.connect(self._toggle_drawer)
|
|
layout.addWidget(self.start_btn)
|
|
|
|
# Search box
|
|
self.search_box = QLineEdit()
|
|
self.search_box.setFixedSize(180, 36)
|
|
self.search_box.setPlaceholderText("Search...")
|
|
self.search_box.setStyleSheet("""
|
|
QLineEdit {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 18px;
|
|
padding: 0 14px;
|
|
font-size: 13px;
|
|
}
|
|
QLineEdit:focus {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
border: 1px solid rgba(255, 140, 66, 0.5);
|
|
}
|
|
""")
|
|
self.search_box.returnPressed.connect(self._on_search)
|
|
layout.addWidget(self.search_box)
|
|
|
|
# Separator
|
|
separator = QFrame()
|
|
separator.setFixedSize(1, 24)
|
|
separator.setStyleSheet("background: rgba(255, 255, 255, 0.1);")
|
|
layout.addWidget(separator)
|
|
|
|
# Pinned plugins area
|
|
self.pinned_area = PinnedPluginsArea()
|
|
self.pinned_area.plugin_pinned.connect(self._on_plugin_pinned)
|
|
self.pinned_area.setAcceptDrops(True)
|
|
layout.addWidget(self.pinned_area)
|
|
|
|
# Spacer
|
|
layout.addStretch()
|
|
|
|
# Clock
|
|
self.clock_label = QLabel("12:00")
|
|
self.clock_label.setStyleSheet("color: rgba(255, 255, 255, 0.7); font-size: 12px;")
|
|
self.clock_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(self.clock_label)
|
|
|
|
# Settings button
|
|
self.settings_btn = QPushButton("⚙️")
|
|
self.settings_btn.setFixedSize(36, 36)
|
|
self.settings_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: transparent;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
}
|
|
QPushButton:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
}
|
|
""")
|
|
self.settings_btn.setToolTip("Settings")
|
|
self.settings_btn.clicked.connect(self.settings_requested.emit)
|
|
layout.addWidget(self.settings_btn)
|
|
|
|
# Clock timer
|
|
self.clock_timer = QTimer(self)
|
|
self.clock_timer.timeout.connect(self._update_clock)
|
|
self.clock_timer.start(60000)
|
|
self._update_clock()
|
|
|
|
# Auto-hide timer
|
|
self.hide_timer = QTimer(self)
|
|
self.hide_timer.timeout.connect(self.hide)
|
|
|
|
# Drawer
|
|
self.drawer = None
|
|
|
|
# Enable drag-drop
|
|
self.setAcceptDrops(True)
|
|
|
|
def _toggle_drawer(self):
|
|
"""Toggle app drawer."""
|
|
if self.drawer is None:
|
|
self.drawer = AppDrawer(self.plugin_manager, self)
|
|
self.drawer.plugin_launched.connect(self.plugin_requested.emit)
|
|
self.drawer.plugin_pin_requested.connect(self._pin_plugin)
|
|
|
|
if self.drawer.isVisible():
|
|
self.drawer.hide()
|
|
else:
|
|
# Position drawer
|
|
bar_pos = self.pos()
|
|
if self.config.position == "bottom":
|
|
self.drawer.move(bar_pos.x(), bar_pos.y() - self.drawer.height() - 10)
|
|
else:
|
|
self.drawer.move(bar_pos.x(), bar_pos.y() + self.height() + 10)
|
|
self.drawer.show()
|
|
self.drawer.raise_()
|
|
|
|
def _on_search(self):
|
|
"""Handle search."""
|
|
text = self.search_box.text().strip()
|
|
if text:
|
|
self.search_requested.emit(text)
|
|
|
|
# Log
|
|
store = get_sqlite_store()
|
|
store.log_activity('ui', 'search', f"Query: {text}")
|
|
|
|
def _on_plugin_pinned(self, plugin_id: str):
|
|
"""Handle plugin pin."""
|
|
self._pin_plugin(plugin_id)
|
|
|
|
def _pin_plugin(self, plugin_id: str):
|
|
"""Pin a plugin to the activity bar."""
|
|
if not self.plugin_manager:
|
|
return
|
|
|
|
all_plugins = self.plugin_manager.get_all_discovered_plugins()
|
|
|
|
if plugin_id not in all_plugins:
|
|
return
|
|
|
|
plugin_class = all_plugins[plugin_id]
|
|
|
|
if plugin_id not in self.config.pinned_plugins:
|
|
self.config.pinned_plugins.append(plugin_id)
|
|
self._save_config()
|
|
|
|
icon_text = getattr(plugin_class, 'icon', '◆')
|
|
self.pinned_area.add_plugin(plugin_id, plugin_class.name, icon_text)
|
|
|
|
def _unpin_plugin(self, plugin_id: str):
|
|
"""Unpin a plugin."""
|
|
if plugin_id in self.config.pinned_plugins:
|
|
self.config.pinned_plugins.remove(plugin_id)
|
|
self._save_config()
|
|
|
|
self.pinned_area.remove_plugin(plugin_id)
|
|
|
|
def _load_pinned_plugins(self):
|
|
"""Load pinned plugins from config."""
|
|
if not self.plugin_manager:
|
|
return
|
|
|
|
all_plugins = self.plugin_manager.get_all_discovered_plugins()
|
|
|
|
plugins = []
|
|
for plugin_id in self.config.pinned_plugins:
|
|
if plugin_id in all_plugins:
|
|
plugin_class = all_plugins[plugin_id]
|
|
icon_text = getattr(plugin_class, 'icon', '◆')
|
|
plugins.append((plugin_id, plugin_class.name, icon_text))
|
|
|
|
self.pinned_area.set_plugins(plugins)
|
|
|
|
def _update_clock(self):
|
|
"""Update clock display."""
|
|
from datetime import datetime
|
|
self.clock_label.setText(datetime.now().strftime("%H:%M"))
|
|
|
|
def _apply_config(self):
|
|
"""Apply configuration."""
|
|
screen = QApplication.primaryScreen().geometry()
|
|
|
|
if self.config.position == "bottom":
|
|
self.move((screen.width() - 700) // 2, screen.height() - 70)
|
|
else:
|
|
self.move((screen.width() - 700) // 2, 20)
|
|
|
|
self.setFixedWidth(700)
|
|
|
|
def _load_config(self) -> ActivityBarConfig:
|
|
"""Load configuration."""
|
|
config_path = Path("config/activity_bar.json")
|
|
if config_path.exists():
|
|
try:
|
|
data = json.loads(config_path.read_text())
|
|
return ActivityBarConfig.from_dict(data)
|
|
except:
|
|
pass
|
|
return ActivityBarConfig()
|
|
|
|
def _save_config(self):
|
|
"""Save configuration."""
|
|
config_path = Path("config/activity_bar.json")
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
config_path.write_text(json.dumps(self.config.to_dict(), indent=2))
|
|
|
|
def enterEvent(self, event):
|
|
"""Mouse entered."""
|
|
self.hide_timer.stop()
|
|
super().enterEvent(event)
|
|
|
|
def leaveEvent(self, event):
|
|
"""Mouse left."""
|
|
if self.config.auto_hide:
|
|
self.hide_timer.start(self.config.auto_hide_delay)
|
|
super().leaveEvent(event)
|
|
|
|
def mousePressEvent(self, event: QMouseEvent):
|
|
"""Start dragging."""
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
self._dragging = True
|
|
self._drag_offset = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
|
event.accept()
|
|
|
|
def mouseMoveEvent(self, event: QMouseEvent):
|
|
"""Drag window."""
|
|
if getattr(self, '_dragging', False):
|
|
new_pos = event.globalPosition().toPoint() - self._drag_offset
|
|
self.move(new_pos)
|
|
|
|
def mouseReleaseEvent(self, event: QMouseEvent):
|
|
"""Stop dragging."""
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
self._dragging = False
|
|
|
|
|
|
# Global instance
|
|
_activity_bar_instance = None
|
|
|
|
|
|
def get_activity_bar(plugin_manager=None) -> Optional[EnhancedActivityBar]:
|
|
"""Get or create global activity bar instance."""
|
|
global _activity_bar_instance
|
|
if _activity_bar_instance is None and plugin_manager:
|
|
_activity_bar_instance = EnhancedActivityBar(plugin_manager)
|
|
return _activity_bar_instance
|