562 lines
19 KiB
Python
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)
|