EU-Utility/premium/ui/tray.py

292 lines
9.5 KiB
Python

"""
System Tray Integration for EU-Utility
=======================================
Provides system tray icon with menu for controlling the application.
"""
import sys
from typing import Callable, Optional
from PyQt6.QtWidgets import (
QSystemTrayIcon, QMenu, QAction, QWidget, QApplication,
QMessageBox, QInputDialog
)
from PyQt6.QtCore import pyqtSignal, QObject
from PyQt6.QtGui import QIcon, QAction as QActionGui
class TrayIcon(QObject):
"""System tray icon with menu.
Signals:
toggle_overlay: Emitted when overlay toggle is clicked
show_settings: Emitted when settings is clicked
show_dashboard: Emitted when dashboard is clicked
quit: Emitted when quit is clicked
"""
toggle_overlay = pyqtSignal()
show_settings = pyqtSignal()
show_dashboard = pyqtSignal()
quit_requested = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.tray_icon: Optional[QSystemTrayIcon] = None
self.menu: Optional[QMenu] = None
self._setup_tray()
def _setup_tray(self):
"""Setup the system tray icon."""
# Create tray icon
self.tray_icon = QSystemTrayIcon(self.parent())
# Try to use a custom icon, fallback to system icon
icon = self._load_icon()
self.tray_icon.setIcon(icon)
self.tray_icon.setToolTip("EU-Utility - Entropia Universe Overlay")
# Create menu
self.menu = QMenu()
self._build_menu()
self.tray_icon.setContextMenu(self.menu)
# Handle left click
self.tray_icon.activated.connect(self._on_activated)
def _load_icon(self) -> QIcon:
"""Load tray icon."""
from pathlib import Path
# Try custom icon first
icon_paths = [
Path(__file__).parent.parent.parent / "assets" / "icon.ico",
Path(__file__).parent.parent.parent / "assets" / "icon.png",
]
for path in icon_paths:
if path.exists():
return QIcon(str(path))
# Fallback to system icon
return QIcon.fromTheme("applications-games")
def _build_menu(self):
"""Build the context menu."""
# Toggle Overlay
toggle_action = QActionGui("Toggle Overlay (Ctrl+Shift+B)", self)
toggle_action.triggered.connect(self.toggle_overlay.emit)
self.menu.addAction(toggle_action)
self.menu.addSeparator()
# Dashboard
dashboard_action = QActionGui("Open Dashboard", self)
dashboard_action.triggered.connect(self.show_dashboard.emit)
self.menu.addAction(dashboard_action)
# Settings
settings_action = QActionGui("Settings...", self)
settings_action.triggered.connect(self.show_settings.emit)
self.menu.addAction(settings_action)
self.menu.addSeparator()
# Status
self.status_action = QActionGui("Status: Running", self)
self.status_action.setEnabled(False)
self.menu.addAction(self.status_action)
self.menu.addSeparator()
# Quit
quit_action = QActionGui("Quit", self)
quit_action.triggered.connect(self.quit_requested.emit)
self.menu.addAction(quit_action)
def _on_activated(self, reason: QSystemTrayIcon.ActivationReason):
"""Handle tray icon activation."""
if reason == QSystemTrayIcon.ActivationReason.Trigger:
# Left click - toggle overlay
self.toggle_overlay.emit()
elif reason == QSystemTrayIcon.ActivationReason.Context:
# Right click - show menu (handled automatically)
pass
def show(self):
"""Show the tray icon."""
if self.tray_icon:
self.tray_icon.show()
self.tray_icon.showMessage(
"EU-Utility",
"Application is running in system tray.\n"
"Press Ctrl+Shift+B to toggle overlay.",
QSystemTrayIcon.MessageIcon.Information,
3000
)
def hide(self):
"""Hide the tray icon."""
if self.tray_icon:
self.tray_icon.hide()
def update_status(self, status: str):
"""Update the status menu item."""
if hasattr(self, 'status_action'):
self.status_action.setText(f"Status: {status}")
def notify(self, title: str, message: str, icon=QSystemTrayIcon.MessageIcon.Information):
"""Show a tray notification."""
if self.tray_icon and self.tray_icon.isVisible():
self.tray_icon.showMessage(title, message, icon, 3000)
class SettingsWindow(QWidget):
"""Desktop settings window."""
def __init__(self, config: dict, parent=None):
super().__init__(parent)
self.config = config
self._setup_ui()
def _setup_ui(self):
"""Setup the settings UI."""
from PyQt6.QtWidgets import (
QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QCheckBox, QComboBox, QSpinBox,
QGroupBox, QTabWidget, QFileDialog
)
self.setWindowTitle("EU-Utility Settings")
self.setMinimumSize(500, 400)
layout = QVBoxLayout(self)
# Tabs
tabs = QTabWidget()
layout.addWidget(tabs)
# General tab
general_tab = QWidget()
general_layout = QVBoxLayout(general_tab)
# Game Path
path_group = QGroupBox("Game Path")
path_layout = QHBoxLayout(path_group)
self.path_edit = QLineEdit(self.config.get('game_path', ''))
path_layout.addWidget(self.path_edit)
browse_btn = QPushButton("Browse...")
browse_btn.clicked.connect(self._browse_game_path)
path_layout.addWidget(browse_btn)
general_layout.addWidget(path_group)
# Overlay Settings
overlay_group = QGroupBox("Overlay Settings")
overlay_layout = QVBoxLayout(overlay_group)
self.overlay_checkbox = QCheckBox("Enable Overlay")
self.overlay_checkbox.setChecked(self.config.get('overlay_enabled', True))
overlay_layout.addWidget(self.overlay_checkbox)
# Hotkey
hotkey_layout = QHBoxLayout()
hotkey_layout.addWidget(QLabel("Toggle Hotkey:"))
self.hotkey_edit = QLineEdit(self.config.get('hotkey', 'ctrl+shift+b'))
hotkey_layout.addWidget(self.hotkey_edit)
overlay_layout.addLayout(hotkey_layout)
# Overlay Mode
mode_layout = QHBoxLayout()
mode_layout.addWidget(QLabel("Overlay Mode:"))
self.mode_combo = QComboBox()
self.mode_combo.addItems([
'overlay_toggle',
'overlay_game',
'overlay_always',
'overlay_temp'
])
self.mode_combo.setCurrentText(self.config.get('overlay_mode', 'overlay_toggle'))
mode_layout.addWidget(self.mode_combo)
overlay_layout.addLayout(mode_layout)
general_layout.addWidget(overlay_group)
# Theme Settings
theme_group = QGroupBox("Appearance")
theme_layout = QVBoxLayout(theme_group)
theme_layout.addWidget(QLabel("Theme:"))
self.theme_combo = QComboBox()
self.theme_combo.addItems(['dark', 'light'])
self.theme_combo.setCurrentText(self.config.get('ui', {}).get('theme', 'dark'))
theme_layout.addWidget(self.theme_combo)
general_layout.addWidget(theme_group)
general_layout.addStretch()
tabs.addTab(general_tab, "General")
# Plugins tab
plugins_tab = QWidget()
plugins_layout = QVBoxLayout(plugins_tab)
plugins_layout.addWidget(QLabel("Plugin settings will appear here"))
plugins_layout.addStretch()
tabs.addTab(plugins_tab, "Plugins")
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
save_btn = QPushButton("Save")
save_btn.clicked.connect(self._save_settings)
button_layout.addWidget(save_btn)
cancel_btn = QPushButton("Cancel")
cancel_btn.clicked.connect(self.close)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
def _browse_game_path(self):
"""Browse for game path."""
from PyQt6.QtWidgets import QFileDialog
path = QFileDialog.getExistingDirectory(
self,
"Select Entropia Universe Directory",
self.path_edit.text() or "C:/"
)
if path:
self.path_edit.setText(path)
def _save_settings(self):
"""Save settings to config file."""
self.config['game_path'] = self.path_edit.text()
self.config['overlay_enabled'] = self.overlay_checkbox.isChecked()
self.config['hotkey'] = self.hotkey_edit.text()
self.config['overlay_mode'] = self.mode_combo.currentText()
if 'ui' not in self.config:
self.config['ui'] = {}
self.config['ui']['theme'] = self.theme_combo.currentText()
# Save to file
import json
from pathlib import Path
config_path = Path.home() / '.eu-utility' / 'config.json'
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, 'w') as f:
json.dump(self.config, f, indent=2)
self.close()