EU-Utility/core/tray_icon.py

161 lines
5.2 KiB
Python

"""
EU-Utility - System Tray Icon
=============================
System tray implementation with right-click menu.
Replaces the floating icon with a proper tray icon.
"""
from PyQt6.QtWidgets import QSystemTrayIcon, QMenu, QAction, QApplication
from PyQt6.QtCore import QTimer, pyqtSignal, QObject
from PyQt6.QtGui import QIcon, QColor, QPainter, QFont, QFontMetrics
from core.logger import get_logger
logger = get_logger(__name__)
class TrayIcon(QObject):
"""
System tray icon for EU-Utility.
Features:
- Right-click context menu
- Show/hide dashboard
- Toggle activity bar
- Settings access
- Quit option
"""
show_dashboard = pyqtSignal()
toggle_activity_bar = pyqtSignal()
open_settings = pyqtSignal()
quit_app = pyqtSignal()
def __init__(self, app: QApplication, parent=None):
super().__init__(parent)
self.app = app
self.tray_icon = None
self.menu = None
self._create_icon()
self._setup_menu()
self._setup_visibility_timer()
def _create_icon(self):
"""Create the tray icon with EU logo."""
# Create a simple colored circle icon with "EU" text
pixmap = QIcon.fromTheme("applications-system")
# If no system icon, create custom
if pixmap.isNull():
from PyQt6.QtGui import QPixmap, QPainter, QColor, QFont
px = QPixmap(64, 64)
px.fill(QColor(255, 140, 66)) # Orange background
painter = QPainter(px)
painter.setPen(QColor(255, 255, 255))
font = QFont("Segoe UI", 24, QFont.Weight.Bold)
painter.setFont(font)
painter.drawText(px.rect(), Qt.AlignmentFlag.AlignCenter, "EU")
painter.end()
pixmap = QIcon(px)
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(pixmap)
self.tray_icon.setToolTip("EU-Utility")
# Show the icon
self.tray_icon.show()
# Connect activation (double-click)
self.tray_icon.activated.connect(self._on_activated)
def _setup_menu(self):
"""Setup the right-click context menu."""
self.menu = QMenu()
self.menu.setStyleSheet("""
QMenu {
background: rgba(35, 35, 35, 0.98);
color: white;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 8px;
}
QMenu::item {
padding: 10px 24px;
border-radius: 6px;
}
QMenu::item:selected {
background: rgba(255, 140, 66, 0.3);
}
QMenu::separator {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 8px 16px;
}
""")
# Dashboard action
self.dashboard_action = QAction("📊 Dashboard", self)
self.dashboard_action.triggered.connect(self.show_dashboard)
self.menu.addAction(self.dashboard_action)
# Activity Bar toggle
self.activity_bar_action = QAction("🎮 Activity Bar", self)
self.activity_bar_action.setCheckable(True)
self.activity_bar_action.triggered.connect(self.toggle_activity_bar)
self.menu.addAction(self.activity_bar_action)
self.menu.addSeparator()
# Settings
self.settings_action = QAction("⚙️ Settings", self)
self.settings_action.triggered.connect(self.open_settings)
self.menu.addAction(self.settings_action)
self.menu.addSeparator()
# Quit
self.quit_action = QAction("❌ Quit", self)
self.quit_action.triggered.connect(self.quit_app)
self.menu.addAction(self.quit_action)
# Set menu
self.tray_icon.setContextMenu(self.menu)
def _setup_visibility_timer(self):
"""Setup timer to update menu state."""
self.update_timer = QTimer(self)
self.update_timer.timeout.connect(self._update_menu_state)
self.update_timer.start(1000) # Update every second
def _update_menu_state(self):
"""Update menu checkbox states."""
# This will be connected to actual state
pass
def _on_activated(self, reason):
"""Handle tray icon activation."""
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
self.show_dashboard.emit()
def show_notification(self, title: str, message: str, duration: int = 3000):
"""Show a system notification."""
if self.tray_icon and self.tray_icon.supportsMessages():
self.tray_icon.showMessage(
title,
message,
QSystemTrayIcon.MessageIcon.Information,
duration
)
def set_activity_bar_checked(self, checked: bool):
"""Update activity bar menu item state."""
self.activity_bar_action.setChecked(checked)
def is_visible(self) -> bool:
"""Check if tray icon is visible."""
return self.tray_icon and self.tray_icon.isVisible()