EU-Utility/core/tray_icon.py

162 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, QApplication
from PyQt6.QtGui import QAction
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()