EU-Utility/plugins/settings/plugin.py

562 lines
19 KiB
Python

"""
EU-Utility - Settings UI Plugin
Settings menu for configuring EU-Utility.
"""
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QCheckBox, QLineEdit, QComboBox,
QSlider, QTabWidget, QGroupBox, QListWidget,
QListWidgetItem, QFrame, QFileDialog, QScrollArea
)
from PyQt6.QtCore import Qt, QTimer
from core.settings import get_settings
from plugins.base_plugin import BasePlugin
class SettingsPlugin(BasePlugin):
"""EU-Utility settings and configuration."""
name = "Settings"
version = "1.0.0"
author = "ImpulsiveFPS"
description = "Configure EU-Utility preferences"
hotkey = "ctrl+shift+comma"
def initialize(self):
"""Setup settings."""
self.settings = get_settings()
def get_ui(self):
"""Create settings UI."""
widget = QWidget()
widget.setStyleSheet("background: transparent;")
layout = QVBoxLayout(widget)
layout.setSpacing(10)
layout.setContentsMargins(0, 0, 0, 0)
# Title
title = QLabel("Settings")
title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
layout.addWidget(title)
# Tabs
tabs = QTabWidget()
tabs.setStyleSheet("""
QTabBar::tab {
background-color: rgba(35, 40, 55, 200);
color: rgba(255,255,255,150);
padding: 10px 20px;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
QTabBar::tab:selected {
background-color: #ff8c42;
color: white;
font-weight: bold;
}
""")
# General tab
general_tab = self._create_general_tab()
tabs.addTab(general_tab, "General")
# Plugins tab
plugins_tab = self._create_plugins_tab()
tabs.addTab(plugins_tab, "Plugins")
# Hotkeys tab
hotkeys_tab = self._create_hotkeys_tab()
tabs.addTab(hotkeys_tab, "Hotkeys")
# Overlay tab
overlay_tab = self._create_overlay_tab()
tabs.addTab(overlay_tab, "Overlays")
# Data tab
data_tab = self._create_data_tab()
tabs.addTab(data_tab, "Data")
layout.addWidget(tabs)
# Save/Reset buttons
btn_layout = QHBoxLayout()
save_btn = QPushButton("Save Settings")
save_btn.setStyleSheet("""
QPushButton {
background-color: #4caf50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
font-weight: bold;
}
""")
save_btn.clicked.connect(self._save_settings)
btn_layout.addWidget(save_btn)
reset_btn = QPushButton("Reset to Default")
reset_btn.setStyleSheet("""
QPushButton {
background-color: rgba(255,255,255,20);
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
}
""")
reset_btn.clicked.connect(self._reset_settings)
btn_layout.addWidget(reset_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
return widget
def _create_general_tab(self):
"""Create general settings tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(15)
# Appearance
appear_group = QGroupBox("Appearance")
appear_group.setStyleSheet(self._group_style())
appear_layout = QVBoxLayout(appear_group)
# Theme
theme_layout = QHBoxLayout()
theme_layout.addWidget(QLabel("Theme:"))
self.theme_combo = QComboBox()
self.theme_combo.addItems(["Dark (EU Style)", "Light", "Auto"])
self.theme_combo.setCurrentText(self.settings.get('theme', 'Dark (EU Style)'))
theme_layout.addWidget(self.theme_combo)
theme_layout.addStretch()
appear_layout.addLayout(theme_layout)
# Opacity
opacity_layout = QHBoxLayout()
opacity_layout.addWidget(QLabel("Overlay Opacity:"))
self.opacity_slider = QSlider(Qt.Orientation.Horizontal)
self.opacity_slider.setMinimum(50)
self.opacity_slider.setMaximum(100)
self.opacity_slider.setValue(int(self.settings.get('overlay_opacity', 0.9) * 100))
opacity_layout.addWidget(self.opacity_slider)
self.opacity_label = QLabel(f"{self.opacity_slider.value()}%")
opacity_layout.addWidget(self.opacity_label)
opacity_layout.addStretch()
appear_layout.addLayout(opacity_layout)
# Icon size
icon_layout = QHBoxLayout()
icon_layout.addWidget(QLabel("Icon Size:"))
self.icon_combo = QComboBox()
self.icon_combo.addItems(["Small (20px)", "Medium (24px)", "Large (32px)"])
icon_layout.addWidget(self.icon_combo)
icon_layout.addStretch()
appear_layout.addLayout(icon_layout)
layout.addWidget(appear_group)
# Behavior
behavior_group = QGroupBox("Behavior")
behavior_group.setStyleSheet(self._group_style())
behavior_layout = QVBoxLayout(behavior_group)
self.auto_start_cb = QCheckBox("Start with Windows")
self.auto_start_cb.setChecked(self.settings.get('auto_start', False))
behavior_layout.addWidget(self.auto_start_cb)
self.minimize_cb = QCheckBox("Minimize to tray on close")
self.minimize_cb.setChecked(self.settings.get('minimize_to_tray', True))
behavior_layout.addWidget(self.minimize_cb)
self.tooltips_cb = QCheckBox("Show tooltips")
self.tooltips_cb.setChecked(self.settings.get('show_tooltips', True))
behavior_layout.addWidget(self.tooltips_cb)
layout.addWidget(behavior_group)
layout.addStretch()
return tab
def _create_plugins_tab(self):
"""Create plugins management tab - enable/disable plugins."""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(15)
# Info label
info = QLabel("Check plugins to enable them. Uncheck to disable. Changes take effect immediately.")
info.setStyleSheet("color: rgba(255,255,255,150);")
layout.addWidget(info)
# Scroll area for plugin list
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.Shape.NoFrame)
scroll.setStyleSheet("background: transparent; border: none;")
scroll_content = QWidget()
plugins_layout = QVBoxLayout(scroll_content)
plugins_layout.setSpacing(8)
plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.plugin_checkboxes = {}
# Get all discovered plugins from plugin manager
if hasattr(self.overlay, 'plugin_manager'):
plugin_manager = self.overlay.plugin_manager
all_plugins = plugin_manager.get_all_discovered_plugins()
# Sort by name
sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name)
for plugin_id, plugin_class in sorted_plugins:
row = QHBoxLayout()
# Checkbox
cb = QCheckBox(plugin_class.name)
cb.setChecked(plugin_manager.is_plugin_enabled(plugin_id))
cb.setStyleSheet("""
QCheckBox {
color: white;
spacing: 8px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
}
""")
# Connect to enable/disable
cb.stateChanged.connect(
lambda state, pid=plugin_id: self._toggle_plugin(pid, state == Qt.CheckState.Checked.value)
)
self.plugin_checkboxes[plugin_id] = cb
row.addWidget(cb)
# Version
version_label = QLabel(f"v{plugin_class.version}")
version_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
row.addWidget(version_label)
# Description
desc_label = QLabel(f"- {plugin_class.description}")
desc_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
desc_label.setWordWrap(True)
row.addWidget(desc_label, 1)
row.addStretch()
plugins_layout.addLayout(row)
# Separator
sep = QFrame()
sep.setFrameShape(QFrame.Shape.HLine)
sep.setStyleSheet("background-color: rgba(100, 110, 130, 40);")
sep.setFixedHeight(1)
plugins_layout.addWidget(sep)
plugins_layout.addStretch()
scroll.setWidget(scroll_content)
layout.addWidget(scroll)
# Buttons
btn_layout = QHBoxLayout()
enable_all_btn = QPushButton("Enable All")
enable_all_btn.setStyleSheet("""
QPushButton {
background-color: #4caf50;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
}
""")
enable_all_btn.clicked.connect(self._enable_all_plugins)
btn_layout.addWidget(enable_all_btn)
disable_all_btn = QPushButton("Disable All")
disable_all_btn.setStyleSheet("""
QPushButton {
background-color: rgba(255,255,255,20);
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
}
""")
disable_all_btn.clicked.connect(self._disable_all_plugins)
btn_layout.addWidget(disable_all_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
return tab
def _create_hotkeys_tab(self):
"""Create hotkeys configuration tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(15)
hotkeys_group = QGroupBox("Global Hotkeys")
hotkeys_group.setStyleSheet(self._group_style())
hotkeys_layout = QVBoxLayout(hotkeys_group)
self.hotkey_inputs = {}
hotkeys = [
("Toggle Overlay", "hotkey_toggle", "ctrl+shift+u"),
("Universal Search", "hotkey_search", "ctrl+shift+f"),
("Calculator", "hotkey_calculator", "ctrl+shift+c"),
("Spotify", "hotkey_music", "ctrl+shift+m"),
("Game Reader", "hotkey_scan", "ctrl+shift+r"),
("Skill Scanner", "hotkey_skills", "ctrl+shift+s"),
]
for label, key, default in hotkeys:
row = QHBoxLayout()
row.addWidget(QLabel(label + ":"))
input_field = QLineEdit()
input_field.setText(self.settings.get(key, default))
input_field.setStyleSheet("""
QLineEdit {
background-color: rgba(30, 35, 45, 200);
color: white;
border: 1px solid rgba(100, 110, 130, 80);
padding: 5px;
min-width: 150px;
}
""")
self.hotkey_inputs[key] = input_field
row.addWidget(input_field)
row.addStretch()
hotkeys_layout.addLayout(row)
layout.addWidget(hotkeys_group)
layout.addStretch()
return tab
def _create_overlay_tab(self):
"""Create overlay widgets configuration tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(15)
overlays_group = QGroupBox("In-Game Overlays")
overlays_group.setStyleSheet(self._group_style())
overlays_layout = QVBoxLayout(overlays_group)
overlays = [
("Spotify Player", "spotify", True),
("Mission Tracker", "mission", False),
("Skill Gains", "skillgain", False),
("DPP Tracker", "dpp", False),
]
for name, key, enabled in overlays:
cb = QCheckBox(name)
cb.setChecked(enabled)
overlays_layout.addWidget(cb)
# Reset positions
reset_pos_btn = QPushButton("↺ Reset All Positions")
reset_pos_btn.setStyleSheet("""
QPushButton {
background-color: rgba(255,255,255,20);
color: white;
padding: 8px;
border: none;
border-radius: 4px;
}
""")
overlays_layout.addWidget(reset_pos_btn)
layout.addWidget(overlays_group)
layout.addStretch()
return tab
def _create_data_tab(self):
"""Create data management tab."""
tab = QWidget()
layout = QVBoxLayout(tab)
layout.setSpacing(15)
data_group = QGroupBox("Data Management")
data_group.setStyleSheet(self._group_style())
data_layout = QVBoxLayout(data_group)
# Export
export_btn = QPushButton("📤 Export All Data")
export_btn.setStyleSheet("""
QPushButton {
background-color: #4a9eff;
color: white;
padding: 10px;
border: none;
border-radius: 4px;
font-weight: bold;
}
""")
export_btn.clicked.connect(self._export_data)
data_layout.addWidget(export_btn)
# Import
import_btn = QPushButton("📥 Import Data")
import_btn.setStyleSheet("""
QPushButton {
background-color: rgba(255,255,255,20);
color: white;
padding: 10px;
border: none;
border-radius: 4px;
}
""")
import_btn.clicked.connect(self._import_data)
data_layout.addWidget(import_btn)
# Clear
clear_btn = QPushButton("Clear All Data")
clear_btn.setStyleSheet("""
QPushButton {
background-color: #f44336;
color: white;
padding: 10px;
border: none;
border-radius: 4px;
}
""")
clear_btn.clicked.connect(self._clear_data)
data_layout.addWidget(clear_btn)
# Retention
retention_layout = QHBoxLayout()
retention_layout.addWidget(QLabel("Data retention:"))
self.retention_combo = QComboBox()
self.retention_combo.addItems(["7 days", "30 days", "90 days", "Forever"])
retention_layout.addWidget(self.retention_combo)
retention_layout.addStretch()
data_layout.addLayout(retention_layout)
layout.addWidget(data_group)
layout.addStretch()
return tab
def _group_style(self):
"""Get group box style."""
return """
QGroupBox {
color: rgba(255,255,255,200);
border: 1px solid rgba(100, 110, 130, 80);
border-radius: 6px;
margin-top: 10px;
font-weight: bold;
font-size: 12px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
}
"""
def _save_settings(self):
"""Save all settings."""
# General
self.settings.set('theme', self.theme_combo.currentText())
self.settings.set('overlay_opacity', self.opacity_slider.value() / 100)
self.settings.set('auto_start', self.auto_start_cb.isChecked())
self.settings.set('minimize_to_tray', self.minimize_cb.isChecked())
self.settings.set('show_tooltips', self.tooltips_cb.isChecked())
# Hotkeys
for key, input_field in self.hotkey_inputs.items():
self.settings.set(key, input_field.text())
print("Settings saved!")
def _reset_settings(self):
"""Reset to defaults."""
self.settings.reset()
print("Settings reset to defaults!")
def _export_data(self):
"""Export all data."""
from PyQt6.QtWidgets import QFileDialog
filepath, _ = QFileDialog.getSaveFileName(
None, "Export EU-Utility Data", "eu_utility_backup.json", "JSON (*.json)"
)
if filepath:
import shutil
data_dir = Path("data")
if data_dir.exists():
# Create export
import json
export_data = {}
for f in data_dir.glob("*.json"):
with open(f, 'r') as file:
export_data[f.stem] = json.load(file)
with open(filepath, 'w') as file:
json.dump(export_data, file, indent=2)
def _import_data(self):
"""Import data."""
pass
def _clear_data(self):
"""Clear all data."""
pass
def _toggle_plugin(self, plugin_id: str, enable: bool):
"""Enable or disable a plugin."""
if not hasattr(self.overlay, 'plugin_manager'):
return
plugin_manager = self.overlay.plugin_manager
if enable:
success = plugin_manager.enable_plugin(plugin_id)
if success:
print(f"[Settings] Enabled plugin: {plugin_id}")
else:
print(f"[Settings] Failed to enable plugin: {plugin_id}")
else:
success = plugin_manager.disable_plugin(plugin_id)
if success:
print(f"[Settings] Disabled plugin: {plugin_id}")
# Notify user a restart might be needed for some changes
# But we try to apply immediately
def _enable_all_plugins(self):
"""Enable all plugins."""
if not hasattr(self.overlay, 'plugin_manager'):
return
plugin_manager = self.overlay.plugin_manager
for plugin_id, cb in self.plugin_checkboxes.items():
cb.setChecked(True)
plugin_manager.enable_plugin(plugin_id)
def _disable_all_plugins(self):
"""Disable all plugins."""
if not hasattr(self.overlay, 'plugin_manager'):
return
plugin_manager = self.overlay.plugin_manager
for plugin_id, cb in self.plugin_checkboxes.items():
cb.setChecked(False)
plugin_manager.disable_plugin(plugin_id)