""" 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( "

Welcome!

" "

Lemontropia Suite helps you track your hunting, mining, and crafting sessions " "in Entropia Universe. This wizard will guide you through the initial setup.

" "

You'll need:

" "" ) welcome_label.setWordWrap(True) layout.addWidget(welcome_label) layout.addStretch() # Note about re-running wizard note_label = QLabel( "You can run this wizard again anytime from the Settings menu." ) 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( "Example: If your avatar is named 'Roberth Noname Rajala', " "enter it exactly like that." ) 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"Default location:
{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( "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." ) 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( "🎯 Hunting
" "Track weapon decay, armor decay, healing costs, loot, globals, and HoFs." ) hunting_desc.setWordWrap(True) desc_layout.addWidget(hunting_desc) mining_desc = QLabel( "⛏️ Mining
" "Track finder decay, extractor decay, claim values, and mining runs." ) mining_desc.setWordWrap(True) desc_layout.addWidget(mining_desc) crafting_desc = QLabel( "⚒️ Crafting
" "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.

" "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( "Note: The gear database requires internet connection. " "If you skip this now, you can download it later from the Settings menu." ) 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("

✓ Setup Complete!

") 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( "Next Steps:
" "1. Create or select a loadout for your gear
" "2. Start a new session to begin tracking
" "3. Check the HUD overlay for real-time stats

" "Click Finish 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""" Configuration Summary:

Avatar Name: {avatar_page.get_avatar_name()}
Chat Log Path: {log_page.get_log_path()}
Auto-detect Log: {'Yes' if log_page.get_auto_detect() else 'No'}
Default Activity: {activity_page.get_activity_type().capitalize()}
Download Gear DB: {'Yes' if gear_page.get_download_gear() else 'No'}
""" 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()