639 lines
20 KiB
Python
639 lines
20 KiB
Python
"""
|
|
Lemontropia Suite - Setup Wizard
|
|
First-run wizard for configuring the application.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWizard, QWizardPage, QVBoxLayout, QHBoxLayout,
|
|
QLabel, QLineEdit, QPushButton, QFileDialog,
|
|
QComboBox, QCheckBox, QProgressBar, QGroupBox,
|
|
QFormLayout, QTextEdit, QMessageBox, QWidget
|
|
)
|
|
from PyQt6.QtCore import Qt, QSettings, QThread, pyqtSignal
|
|
from PyQt6.QtGui import QFont, QPixmap
|
|
|
|
|
|
class GearDownloadWorker(QThread):
|
|
"""Background worker for downloading gear database."""
|
|
progress = pyqtSignal(int)
|
|
finished_signal = pyqtSignal(bool, str)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self._cancelled = False
|
|
|
|
def run(self):
|
|
"""Download gear database in background."""
|
|
try:
|
|
# Simulate download progress
|
|
import time
|
|
for i in range(0, 101, 10):
|
|
if self._cancelled:
|
|
self.finished_signal.emit(False, "Download cancelled")
|
|
return
|
|
self.progress.emit(i)
|
|
time.sleep(0.2)
|
|
|
|
# TODO: Implement actual gear database download
|
|
# For now, just create a placeholder
|
|
self.finished_signal.emit(True, "Gear database downloaded successfully")
|
|
except Exception as e:
|
|
self.finished_signal.emit(False, str(e))
|
|
|
|
def cancel(self):
|
|
self._cancelled = True
|
|
|
|
|
|
class WelcomePage(QWizardPage):
|
|
"""Welcome page of the setup wizard."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Welcome to Lemontropia Suite")
|
|
self.setSubTitle("Let's get you set up for tracking your Entropia Universe sessions.")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Welcome text
|
|
welcome_label = QLabel(
|
|
"<h2>Welcome!</h2>"
|
|
"<p>Lemontropia Suite helps you track your hunting, mining, and crafting sessions "
|
|
"in Entropia Universe. This wizard will guide you through the initial setup.</p>"
|
|
"<p>You'll need:</p>"
|
|
"<ul>"
|
|
"<li>Your avatar name (for tracking globals)</li>"
|
|
"<li>Path to your Entropia Universe chat log file</li>"
|
|
"<li>Preferred default activity type</li>"
|
|
"</ul>"
|
|
)
|
|
welcome_label.setWordWrap(True)
|
|
layout.addWidget(welcome_label)
|
|
|
|
layout.addStretch()
|
|
|
|
# Note about re-running wizard
|
|
note_label = QLabel(
|
|
"<i>You can run this wizard again anytime from the Settings menu.</i>"
|
|
)
|
|
note_label.setStyleSheet("color: #888;")
|
|
layout.addWidget(note_label)
|
|
|
|
|
|
class AvatarNamePage(QWizardPage):
|
|
"""Page for setting the avatar name."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Avatar Name")
|
|
self.setSubTitle("Enter your avatar name exactly as it appears in Entropia Universe.")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Explanation
|
|
info_label = QLabel(
|
|
"Your avatar name is used to identify your globals and HoFs in the chat log. "
|
|
"Make sure to enter it exactly as it appears in-game, including capitalization."
|
|
)
|
|
info_label.setWordWrap(True)
|
|
layout.addWidget(info_label)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Name input
|
|
form_layout = QFormLayout()
|
|
self.name_input = QLineEdit()
|
|
self.name_input.setPlaceholderText("e.g., John Doe Mega")
|
|
self.name_input.textChanged.connect(self.completeChanged)
|
|
form_layout.addRow("Avatar Name:", self.name_input)
|
|
layout.addLayout(form_layout)
|
|
|
|
# Register field for wizard access
|
|
self.registerField("avatar_name*", self.name_input)
|
|
|
|
layout.addSpacing(10)
|
|
|
|
# Example
|
|
example_label = QLabel(
|
|
"<i>Example: If your avatar is named 'Roberth Noname Rajala', "
|
|
"enter it exactly like that.</i>"
|
|
)
|
|
example_label.setStyleSheet("color: #888;")
|
|
layout.addWidget(example_label)
|
|
|
|
layout.addStretch()
|
|
|
|
def isComplete(self) -> bool:
|
|
return len(self.name_input.text().strip()) > 0
|
|
|
|
def get_avatar_name(self) -> str:
|
|
return self.name_input.text().strip()
|
|
|
|
|
|
class LogPathPage(QWizardPage):
|
|
"""Page for configuring the log file path."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Chat Log Path")
|
|
self.setSubTitle("Configure the path to your Entropia Universe chat log file.")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Explanation
|
|
info_label = QLabel(
|
|
"Lemontropia Suite reads your chat log to track events like loot, globals, "
|
|
"damage dealt/taken, and healing. Please select your chat.log file."
|
|
)
|
|
info_label.setWordWrap(True)
|
|
layout.addWidget(info_label)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Default path info
|
|
default_path = self._get_default_log_path()
|
|
default_label = QLabel(f"<b>Default location:</b><br>{default_path}")
|
|
default_label.setStyleSheet("color: #4caf50;")
|
|
default_label.setWordWrap(True)
|
|
layout.addWidget(default_label)
|
|
|
|
layout.addSpacing(10)
|
|
|
|
# Path selection
|
|
path_layout = QHBoxLayout()
|
|
self.path_input = QLineEdit()
|
|
self.path_input.setText(default_path)
|
|
self.path_input.textChanged.connect(self.completeChanged)
|
|
path_layout.addWidget(self.path_input)
|
|
|
|
browse_btn = QPushButton("Browse...")
|
|
browse_btn.clicked.connect(self.on_browse)
|
|
path_layout.addWidget(browse_btn)
|
|
|
|
layout.addLayout(path_layout)
|
|
|
|
layout.addSpacing(10)
|
|
|
|
# Auto-detect checkbox
|
|
self.auto_detect_check = QCheckBox("Automatically detect log path on startup")
|
|
self.auto_detect_check.setChecked(True)
|
|
layout.addWidget(self.auto_detect_check)
|
|
|
|
layout.addStretch()
|
|
|
|
# Help text
|
|
help_label = QLabel(
|
|
"<i>Tip: If you can't find your chat.log, make sure you've run Entropia Universe "
|
|
"at least once and enabled chat logging in-game.</i>"
|
|
)
|
|
help_label.setStyleSheet("color: #888;")
|
|
layout.addWidget(help_label)
|
|
|
|
def _get_default_log_path(self) -> str:
|
|
"""Get the default chat log path based on OS."""
|
|
# Default paths for different scenarios
|
|
import os
|
|
|
|
# Check environment variable first
|
|
env_path = os.getenv('EU_CHAT_LOG_PATH', '')
|
|
if env_path:
|
|
return env_path
|
|
|
|
# Windows default path
|
|
appdata = os.getenv('LOCALAPPDATA', '')
|
|
if appdata:
|
|
return str(Path(appdata) / "Entropia Universe" / "chat.log")
|
|
|
|
# Fallback
|
|
return str(Path.home() / "Entropia Universe" / "chat.log")
|
|
|
|
def on_browse(self):
|
|
"""Open file browser to select chat log."""
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
self,
|
|
"Select Chat Log File",
|
|
str(Path.home()),
|
|
"Log Files (*.log);;All Files (*.*)"
|
|
)
|
|
if file_path:
|
|
self.path_input.setText(file_path)
|
|
|
|
def isComplete(self) -> bool:
|
|
return len(self.path_input.text().strip()) > 0
|
|
|
|
def get_log_path(self) -> str:
|
|
return self.path_input.text().strip()
|
|
|
|
def get_auto_detect(self) -> bool:
|
|
return self.auto_detect_check.isChecked()
|
|
|
|
|
|
class ActivityTypePage(QWizardPage):
|
|
"""Page for selecting default activity type."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Default Activity Type")
|
|
self.setSubTitle("Choose your preferred default activity type.")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Explanation
|
|
info_label = QLabel(
|
|
"Select the activity type you do most often. This will be the default "
|
|
"when starting new sessions. You can always change this later."
|
|
)
|
|
info_label.setWordWrap(True)
|
|
layout.addWidget(info_label)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Activity type selection
|
|
form_layout = QFormLayout()
|
|
self.activity_combo = QComboBox()
|
|
self.activity_combo.addItem("🎯 Hunting", "hunt")
|
|
self.activity_combo.addItem("⛏️ Mining", "mine")
|
|
self.activity_combo.addItem("⚒️ Crafting", "craft")
|
|
form_layout.addRow("Default Activity:", self.activity_combo)
|
|
layout.addLayout(form_layout)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Activity descriptions
|
|
descriptions = QGroupBox("Activity Types")
|
|
desc_layout = QVBoxLayout(descriptions)
|
|
|
|
hunting_desc = QLabel(
|
|
"<b>🎯 Hunting</b><br>"
|
|
"Track weapon decay, armor decay, healing costs, loot, globals, and HoFs."
|
|
)
|
|
hunting_desc.setWordWrap(True)
|
|
desc_layout.addWidget(hunting_desc)
|
|
|
|
mining_desc = QLabel(
|
|
"<b>⛏️ Mining</b><br>"
|
|
"Track finder decay, extractor decay, claim values, and mining runs."
|
|
)
|
|
mining_desc.setWordWrap(True)
|
|
desc_layout.addWidget(mining_desc)
|
|
|
|
crafting_desc = QLabel(
|
|
"<b>⚒️ Crafting</b><br>"
|
|
"Track blueprint runs, material costs, success rates, and near-successes."
|
|
)
|
|
crafting_desc.setWordWrap(True)
|
|
desc_layout.addWidget(crafting_desc)
|
|
|
|
layout.addWidget(descriptions)
|
|
|
|
layout.addStretch()
|
|
|
|
def get_activity_type(self) -> str:
|
|
return self.activity_combo.currentData()
|
|
|
|
|
|
class GearDatabasePage(QWizardPage):
|
|
"""Page for downloading initial gear database."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Gear Database (Optional)")
|
|
self.setSubTitle("Download initial gear database for cost tracking.")
|
|
self.setup_ui()
|
|
self.worker = None
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Explanation
|
|
info_label = QLabel(
|
|
"You can download an initial gear database with common weapons, armor, and tools. "
|
|
"This makes it easier to set up your loadouts for cost tracking.<br><br>"
|
|
"This step is optional - you can always add gear manually later."
|
|
)
|
|
info_label.setWordWrap(True)
|
|
layout.addWidget(info_label)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Download option
|
|
self.download_check = QCheckBox("Download initial gear database")
|
|
self.download_check.setChecked(True)
|
|
layout.addWidget(self.download_check)
|
|
|
|
layout.addSpacing(10)
|
|
|
|
# Progress bar (hidden initially)
|
|
self.progress_bar = QProgressBar()
|
|
self.progress_bar.setVisible(False)
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
# Status label
|
|
self.status_label = QLabel("")
|
|
self.status_label.setStyleSheet("color: #4caf50;")
|
|
layout.addWidget(self.status_label)
|
|
|
|
layout.addStretch()
|
|
|
|
# Note
|
|
note_label = QLabel(
|
|
"<i>Note: The gear database requires internet connection. "
|
|
"If you skip this now, you can download it later from the Settings menu.</i>"
|
|
)
|
|
note_label.setStyleSheet("color: #888;")
|
|
note_label.setWordWrap(True)
|
|
layout.addWidget(note_label)
|
|
|
|
def initializePage(self):
|
|
"""Called when page is shown."""
|
|
self.progress_bar.setVisible(False)
|
|
self.status_label.setText("")
|
|
|
|
def download_gear_database(self):
|
|
"""Start downloading gear database."""
|
|
if not self.download_check.isChecked():
|
|
return True
|
|
|
|
self.progress_bar.setVisible(True)
|
|
self.progress_bar.setValue(0)
|
|
self.status_label.setText("Downloading gear database...")
|
|
|
|
# Start worker thread
|
|
self.worker = GearDownloadWorker(self)
|
|
self.worker.progress.connect(self.progress_bar.setValue)
|
|
self.worker.finished_signal.connect(self.on_download_finished)
|
|
self.worker.start()
|
|
|
|
# Wait for completion
|
|
self.worker.wait()
|
|
|
|
return self._download_success
|
|
|
|
def on_download_finished(self, success: bool, message: str):
|
|
"""Handle download completion."""
|
|
self._download_success = success
|
|
if success:
|
|
self.status_label.setText(f"✓ {message}")
|
|
self.status_label.setStyleSheet("color: #4caf50;")
|
|
else:
|
|
self.status_label.setText(f"✗ {message}")
|
|
self.status_label.setStyleSheet("color: #f44336;")
|
|
|
|
def get_download_gear(self) -> bool:
|
|
return self.download_check.isChecked()
|
|
|
|
|
|
class SummaryPage(QWizardPage):
|
|
"""Final page showing summary of configuration."""
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setTitle("Setup Complete")
|
|
self.setSubTitle("Review your configuration before finishing.")
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Success message
|
|
success_label = QLabel("<h2>✓ Setup Complete!</h2>")
|
|
success_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(success_label)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Summary text area
|
|
self.summary_text = QTextEdit()
|
|
self.summary_text.setReadOnly(True)
|
|
self.summary_text.setMaximumHeight(200)
|
|
layout.addWidget(self.summary_text)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
# Next steps
|
|
next_label = QLabel(
|
|
"<b>Next Steps:</b><br>"
|
|
"1. Create or select a loadout for your gear<br>"
|
|
"2. Start a new session to begin tracking<br>"
|
|
"3. Check the HUD overlay for real-time stats<br><br>"
|
|
"Click <b>Finish</b> to start using Lemontropia Suite!"
|
|
)
|
|
next_label.setWordWrap(True)
|
|
layout.addWidget(next_label)
|
|
|
|
layout.addStretch()
|
|
|
|
def initializePage(self):
|
|
"""Update summary when page is shown."""
|
|
wizard = self.wizard()
|
|
|
|
# Get values from previous pages
|
|
avatar_page = wizard.page(1) # AvatarNamePage
|
|
log_page = wizard.page(2) # LogPathPage
|
|
activity_page = wizard.page(3) # ActivityTypePage
|
|
gear_page = wizard.page(4) # GearDatabasePage
|
|
|
|
summary = f"""
|
|
<b>Configuration Summary:</b><br>
|
|
<br>
|
|
<b>Avatar Name:</b> {avatar_page.get_avatar_name()}<br>
|
|
<b>Chat Log Path:</b> {log_page.get_log_path()}<br>
|
|
<b>Auto-detect Log:</b> {'Yes' if log_page.get_auto_detect() else 'No'}<br>
|
|
<b>Default Activity:</b> {activity_page.get_activity_type().capitalize()}<br>
|
|
<b>Download Gear DB:</b> {'Yes' if gear_page.get_download_gear() else 'No'}<br>
|
|
"""
|
|
|
|
self.summary_text.setHtml(summary)
|
|
|
|
|
|
class SetupWizard(QWizard):
|
|
"""
|
|
First-run setup wizard for Lemontropia Suite.
|
|
|
|
Guides users through:
|
|
- Setting avatar name
|
|
- Configuring log file path
|
|
- Choosing default activity type
|
|
- Downloading initial gear database (optional)
|
|
"""
|
|
|
|
def __init__(self, parent=None, first_run: bool = True):
|
|
super().__init__(parent)
|
|
|
|
self.setWindowTitle("Lemontropia Suite - Setup Wizard")
|
|
self.setMinimumSize(600, 500)
|
|
|
|
# Set wizard style
|
|
self.setWizardStyle(QWizard.WizardStyle.ModernStyle)
|
|
|
|
# Add pages
|
|
self.addPage(WelcomePage())
|
|
self.addPage(AvatarNamePage())
|
|
self.addPage(LogPathPage())
|
|
self.addPage(ActivityTypePage())
|
|
self.addPage(GearDatabasePage())
|
|
self.addPage(SummaryPage())
|
|
|
|
# Set button text
|
|
self.setButtonText(QWizard.WizardButton.FinishButton, "Start Using Lemontropia Suite")
|
|
|
|
self._first_run = first_run
|
|
|
|
def accept(self):
|
|
"""Handle wizard completion."""
|
|
# Collect all settings
|
|
settings = {
|
|
'avatar_name': self.field("avatar_name") or self.page(1).get_avatar_name(),
|
|
'log_path': self.page(2).get_log_path(),
|
|
'auto_detect_log': self.page(2).get_auto_detect(),
|
|
'default_activity': self.page(3).get_activity_type(),
|
|
'gear_db_downloaded': self.page(4).get_download_gear(),
|
|
}
|
|
|
|
# Save to QSettings
|
|
qsettings = QSettings("Lemontropia", "Suite")
|
|
qsettings.setValue("player/name", settings['avatar_name'])
|
|
qsettings.setValue("log/path", settings['log_path'])
|
|
qsettings.setValue("log/auto_detect", settings['auto_detect_log'])
|
|
qsettings.setValue("activity/default", settings['default_activity'])
|
|
qsettings.setValue("setup/first_run_complete", True)
|
|
qsettings.setValue("setup/gear_db_downloaded", settings['gear_db_downloaded'])
|
|
qsettings.sync()
|
|
|
|
# Store settings for access after wizard closes
|
|
self._settings = settings
|
|
|
|
super().accept()
|
|
|
|
def get_settings(self) -> dict:
|
|
"""Get the configured settings after wizard completion."""
|
|
return getattr(self, '_settings', {})
|
|
|
|
@staticmethod
|
|
def is_first_run() -> bool:
|
|
"""Check if this is the first run of the application."""
|
|
settings = QSettings("Lemontropia", "Suite")
|
|
return not settings.value("setup/first_run_complete", False, type=bool)
|
|
|
|
@staticmethod
|
|
def reset_first_run():
|
|
"""Reset the first-run flag (for testing)."""
|
|
settings = QSettings("Lemontropia", "Suite")
|
|
settings.setValue("setup/first_run_complete", False)
|
|
settings.sync()
|
|
|
|
|
|
def main():
|
|
"""Test entry point for the setup wizard."""
|
|
from PyQt6.QtWidgets import QApplication
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
# Apply dark theme
|
|
dark_stylesheet = """
|
|
QWizard {
|
|
background-color: #1e1e1e;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QWizardPage {
|
|
background-color: #1e1e1e;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QLabel {
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QLineEdit {
|
|
background-color: #252525;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
padding: 6px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QPushButton {
|
|
background-color: #2d2d2d;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
padding: 8px 16px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QPushButton:hover {
|
|
background-color: #3d3d3d;
|
|
}
|
|
|
|
QComboBox {
|
|
background-color: #252525;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
padding: 6px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QGroupBox {
|
|
font-weight: bold;
|
|
border: 1px solid #444;
|
|
border-radius: 6px;
|
|
margin-top: 10px;
|
|
padding-top: 10px;
|
|
padding: 10px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QTextEdit {
|
|
background-color: #252525;
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QCheckBox {
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QCheckBox::indicator {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
QProgressBar {
|
|
border: 1px solid #444;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
QProgressBar::chunk {
|
|
background-color: #4caf50;
|
|
border-radius: 3px;
|
|
}
|
|
"""
|
|
app.setStyleSheet(dark_stylesheet)
|
|
|
|
wizard = SetupWizard()
|
|
if wizard.exec() == QWizard.DialogCode.Accepted:
|
|
print("Settings saved:", wizard.get_settings())
|
|
else:
|
|
print("Wizard cancelled")
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|