""" Rings, Clothing & Pet Selector for Lemontropia Suite Browse and search accessories from Entropia Nexus API """ from decimal import Decimal from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox, QProgressBar, QGroupBox, QFormLayout, QTabWidget, QWidget, QComboBox ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QColor from typing import Optional, List from core.nexus_full_api import get_nexus_api, NexusRing, NexusClothing, NexusPet class AccessoriesLoaderThread(QThread): """Background thread for loading accessories from API.""" rings_loaded = pyqtSignal(list) clothing_loaded = pyqtSignal(list) pets_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: api = get_nexus_api() rings = api.get_all_rings() clothing = api.get_all_clothing() pets = api.get_all_pets() self.rings_loaded.emit(rings) self.clothing_loaded.emit(clothing) self.pets_loaded.emit(pets) except Exception as e: self.error_occurred.emit(str(e)) class AccessoriesSelectorDialog(QDialog): """Dialog for selecting rings, clothing, and pets from Entropia Nexus API.""" ring_selected = pyqtSignal(NexusRing) clothing_selected = pyqtSignal(NexusClothing) pet_selected = pyqtSignal(NexusPet) def __init__(self, parent=None, initial_tab: str = "rings", slot_filter: str = None): super().__init__(parent) self.setWindowTitle("Select Accessories - Entropia Nexus") self.setMinimumSize(900, 600) self.all_rings: List[NexusRing] = [] self.all_clothing: List[NexusClothing] = [] self.all_pets: List[NexusPet] = [] self.selected_ring: Optional[NexusRing] = None self.selected_clothing: Optional[NexusClothing] = None self.selected_pet: Optional[NexusPet] = None self.slot_filter = slot_filter # "Left Finger" or "Right Finger" or None self._setup_ui() # Set initial tab tab_map = {"rings": 0, "clothing": 1, "pets": 2} self.tabs.setCurrentIndex(tab_map.get(initial_tab, 0)) self._load_data() def _setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # Status self.status_label = QLabel("Loading accessories from Entropia Nexus...") layout.addWidget(self.status_label) self.progress = QProgressBar() self.progress.setRange(0, 0) layout.addWidget(self.progress) # Tabs self.tabs = QTabWidget() # Rings tab self.tab_rings = self._create_rings_tab() self.tabs.addTab(self.tab_rings["widget"], "💍 Rings") # Clothing tab self.tab_clothing = self._create_clothing_tab() self.tabs.addTab(self.tab_clothing["widget"], "👕 Clothing") # Pets tab self.tab_pets = self._create_pets_tab() self.tabs.addTab(self.tab_pets["widget"], "🐾 Pets") self.tabs.currentChanged.connect(self._on_tab_changed) layout.addWidget(self.tabs) # Preview panel self.preview_group = QGroupBox("Item Preview") preview_layout = QFormLayout(self.preview_group) self.preview_name = QLabel("-") self.preview_type = QLabel("-") self.preview_effect = QLabel("-") self.preview_extra = QLabel("-") preview_layout.addRow("Name:", self.preview_name) preview_layout.addRow("Type:", self.preview_type) preview_layout.addRow("Effect:", self.preview_effect) preview_layout.addRow("", self.preview_extra) layout.addWidget(self.preview_group) # Buttons buttons = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) buttons.accepted.connect(self._on_accept) buttons.rejected.connect(self.reject) self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok) self.ok_button.setEnabled(False) layout.addWidget(buttons) def _create_rings_tab(self) -> dict: """Create rings selection tab.""" widget = QWidget() layout = QVBoxLayout(widget) # Filters filter_layout = QHBoxLayout() filter_layout.addWidget(QLabel("Side:")) side_combo = QComboBox() side_combo.addItems(["Any", "Left", "Right"]) filter_layout.addWidget(side_combo) filter_layout.addWidget(QLabel("Limited:")) limited_combo = QComboBox() limited_combo.addItems(["Any", "Yes", "No"]) filter_layout.addWidget(limited_combo) layout.addLayout(filter_layout) # Search search_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) search_input = QLineEdit() search_input.setPlaceholderText("Search rings...") search_layout.addWidget(search_input) clear_btn = QPushButton("Clear") search_layout.addWidget(clear_btn) layout.addLayout(search_layout) # Tree tree = QTreeWidget() tree.setHeaderLabels(["Name", "Effects", "Slot", "Gender", "Limited"]) header = tree.header() header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) tree.itemSelectionChanged.connect(self._on_selection_changed) tree.itemDoubleClicked.connect(self._on_double_click) layout.addWidget(tree) return { "widget": widget, "side": side_combo, "limited": limited_combo, "search": search_input, "clear": clear_btn, "tree": tree } def _create_clothing_tab(self) -> dict: """Create clothing selection tab.""" widget = QWidget() layout = QVBoxLayout(widget) # Filters filter_layout = QHBoxLayout() filter_layout.addWidget(QLabel("Slot:")) slot_combo = QComboBox() slot_combo.addItems(["Any", "Face", "Body", "Legs", "Feet", "Back"]) filter_layout.addWidget(slot_combo) filter_layout.addWidget(QLabel("Cosmetic:")) cosmetic_combo = QComboBox() cosmetic_combo.addItems(["Any", "Yes", "No"]) filter_layout.addWidget(cosmetic_combo) layout.addLayout(filter_layout) # Search search_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) search_input = QLineEdit() search_input.setPlaceholderText("Search clothing...") search_layout.addWidget(search_input) clear_btn = QPushButton("Clear") search_layout.addWidget(clear_btn) layout.addLayout(search_layout) # Tree tree = QTreeWidget() tree.setHeaderLabels(["Name", "Slot", "Buffs", "Cosmetic"]) header = tree.header() header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) tree.itemSelectionChanged.connect(self._on_selection_changed) tree.itemDoubleClicked.connect(self._on_double_click) layout.addWidget(tree) return { "widget": widget, "slot": slot_combo, "cosmetic": cosmetic_combo, "search": search_input, "clear": clear_btn, "tree": tree } def _create_pets_tab(self) -> dict: """Create pets selection tab.""" widget = QWidget() layout = QVBoxLayout(widget) # Search search_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) search_input = QLineEdit() search_input.setPlaceholderText("Search pets...") search_layout.addWidget(search_input) clear_btn = QPushButton("Clear") search_layout.addWidget(clear_btn) layout.addLayout(search_layout) # Tree tree = QTreeWidget() tree.setHeaderLabels(["Name", "Effect Type", "Effect Value", "Level Req"]) header = tree.header() header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) tree.itemSelectionChanged.connect(self._on_selection_changed) tree.itemDoubleClicked.connect(self._on_double_click) layout.addWidget(tree) return { "widget": widget, "search": search_input, "clear": clear_btn, "tree": tree } def _load_data(self): """Load accessories in background thread.""" self.loader = AccessoriesLoaderThread() self.loader.rings_loaded.connect(self._on_rings_loaded) self.loader.clothing_loaded.connect(self._on_clothing_loaded) self.loader.pets_loaded.connect(self._on_pets_loaded) self.loader.error_occurred.connect(self._on_load_error) self.loader.start() def _on_rings_loaded(self, rings: List[NexusRing]): """Handle loaded rings.""" self.all_rings = rings self._populate_rings() self._update_status() def _on_clothing_loaded(self, clothing: List[NexusClothing]): """Handle loaded clothing.""" self.all_clothing = clothing self._populate_clothing() self._update_status() def _on_pets_loaded(self, pets: List[NexusPet]): """Handle loaded pets.""" self.all_pets = pets self._populate_pets() self._update_status() def _on_load_error(self, error: str): """Handle load error.""" self.status_label.setText(f"Error loading accessories: {error}") self.progress.setRange(0, 100) self.progress.setValue(0) def _update_status(self): """Update status label.""" total = len(self.all_rings) + len(self.all_clothing) + len(self.all_pets) if total > 0: self.status_label.setText( f"Loaded {len(self.all_rings)} rings, " f"{len(self.all_clothing)} clothing items, " f"{len(self.all_pets)} pets" ) self.progress.setRange(0, 100) self.progress.setValue(100) def _populate_rings(self): """Populate rings tree.""" tree = self.tab_rings["tree"] tree.clear() if not self.all_rings: item = QTreeWidgetItem() item.setText(0, "No ring data available from API") item.setForeground(0, QColor("#888888")) tree.addTopLevelItem(item) return # Filter rings by slot if specified rings_to_show = self.all_rings if self.slot_filter: rings_to_show = [r for r in rings_to_show if r.slot == self.slot_filter] for ring in rings_to_show: item = QTreeWidgetItem() item.setText(0, ring.name) # Format effects as string effects_str = ", ".join([f"{k}: {v}" for k, v in ring.effects.items()]) if ring.effects else "-" item.setText(1, effects_str) item.setText(2, ring.slot) item.setText(3, ring.gender) item.setText(4, "Yes" if ring.is_limited else "No") # Color limited items if ring.is_limited: item.setForeground(4, QColor("#ff9800")) item.setData(0, Qt.ItemDataRole.UserRole, ring) tree.addTopLevelItem(item) # Connect filters self.tab_rings["search"].textChanged.connect(self._filter_rings) self.tab_rings["clear"].clicked.connect(self.tab_rings["search"].clear) def _populate_clothing(self): """Populate clothing tree.""" tree = self.tab_clothing["tree"] tree.clear() if not self.all_clothing: item = QTreeWidgetItem() item.setText(0, "No clothing data available from API") item.setForeground(0, QColor("#888888")) tree.addTopLevelItem(item) return for item_data in self.all_clothing: item = QTreeWidgetItem() item.setText(0, item_data.name) item.setText(1, item_data.slot) buffs = ", ".join([f"{k}:{v}" for k, v in item_data.buffs.items()]) item.setText(2, buffs if buffs else "None") item.setText(3, "Yes" if item_data.is_cosmetic else "No") # Color cosmetic items if not item_data.is_cosmetic: item.setForeground(3, QColor("#4caf50")) item.setData(0, Qt.ItemDataRole.UserRole, item_data) tree.addTopLevelItem(item) # Connect filters self.tab_clothing["search"].textChanged.connect(self._filter_clothing) self.tab_clothing["clear"].clicked.connect(self.tab_clothing["search"].clear) def _populate_pets(self): """Populate pets tree.""" tree = self.tab_pets["tree"] tree.clear() for pet in self.all_pets: item = QTreeWidgetItem() item.setText(0, pet.name) item.setText(1, pet.effect_type) item.setText(2, str(pet.effect_value)) item.setText(3, str(pet.level_required) if pet.level_required > 0 else "-") # Color by level requirement if pet.level_required > 50: item.setForeground(3, QColor("#f44336")) # Red elif pet.level_required > 20: item.setForeground(3, QColor("#ff9800")) # Orange item.setData(0, Qt.ItemDataRole.UserRole, pet) tree.addTopLevelItem(item) # Connect filters self.tab_pets["search"].textChanged.connect(self._filter_pets) self.tab_pets["clear"].clicked.connect(self.tab_pets["search"].clear) def _filter_rings(self): """Filter rings based on search and filters.""" search = self.tab_rings["search"].text().lower() tree = self.tab_rings["tree"] for i in range(tree.topLevelItemCount()): item = tree.topLevelItem(i) ring = item.data(0, Qt.ItemDataRole.UserRole) visible = search in ring.name.lower() or any(search in k.lower() or search in v.lower() for k, v in ring.effects.items()) item.setHidden(not visible) def _filter_clothing(self): """Filter clothing based on search and filters.""" search = self.tab_clothing["search"].text().lower() tree = self.tab_clothing["tree"] for i in range(tree.topLevelItemCount()): item = tree.topLevelItem(i) clothing = item.data(0, Qt.ItemDataRole.UserRole) visible = search in clothing.name.lower() or search in clothing.slot.lower() item.setHidden(not visible) def _filter_pets(self): """Filter pets based on search.""" search = self.tab_pets["search"].text().lower() tree = self.tab_pets["tree"] for i in range(tree.topLevelItemCount()): item = tree.topLevelItem(i) pet = item.data(0, Qt.ItemDataRole.UserRole) visible = search in pet.name.lower() or search in pet.effect_type.lower() item.setHidden(not visible) def _on_tab_changed(self, index: int): """Handle tab change.""" self.selected_ring = None self.selected_clothing = None self.selected_pet = None self.ok_button.setEnabled(False) self.preview_name.setText("-") self.preview_type.setText("-") self.preview_effect.setText("-") self.preview_extra.setText("-") def _on_selection_changed(self): """Handle selection change.""" index = self.tabs.currentIndex() if index == 0: # Rings items = self.tab_rings["tree"].selectedItems() if items: self.selected_ring = items[0].data(0, Qt.ItemDataRole.UserRole) self._update_preview_ring(self.selected_ring) self.ok_button.setEnabled(True) elif index == 1: # Clothing items = self.tab_clothing["tree"].selectedItems() if items: self.selected_clothing = items[0].data(0, Qt.ItemDataRole.UserRole) self._update_preview_clothing(self.selected_clothing) self.ok_button.setEnabled(True) elif index == 2: # Pets items = self.tab_pets["tree"].selectedItems() if items: self.selected_pet = items[0].data(0, Qt.ItemDataRole.UserRole) self._update_preview_pet(self.selected_pet) self.ok_button.setEnabled(True) def _update_preview_ring(self, ring: NexusRing): """Update preview for ring.""" self.preview_name.setText(ring.name) self.preview_type.setText(f"Ring ({ring.slot})") effects_str = ", ".join([f"{k}: {v}" for k, v in ring.effects.items()]) if ring.effects else "No effects" self.preview_effect.setText(effects_str) self.preview_extra.setText(f"Gender: {ring.gender} | Limited: {'Yes' if ring.is_limited else 'No'}") def _update_preview_clothing(self, clothing: NexusClothing): """Update preview for clothing.""" self.preview_name.setText(clothing.name) self.preview_type.setText(f"Clothing ({clothing.slot})") buffs = ", ".join([f"{k}:{v}" for k, v in clothing.buffs.items()]) self.preview_effect.setText(buffs if buffs else "No buffs") self.preview_extra.setText(f"Cosmetic: {'Yes' if clothing.is_cosmetic else 'No'}") def _update_preview_pet(self, pet: NexusPet): """Update preview for pet.""" self.preview_name.setText(pet.name) self.preview_type.setText("Combat Pet") self.preview_effect.setText(f"{pet.effect_type}: {pet.effect_value}") self.preview_extra.setText(f"Level Required: {pet.level_required}" if pet.level_required > 0 else "No level requirement") def _on_double_click(self, item, column): """Handle double click.""" self._on_accept() def _on_accept(self): """Handle OK button.""" index = self.tabs.currentIndex() if index == 0 and self.selected_ring: self.ring_selected.emit(self.selected_ring) self.accept() elif index == 1 and self.selected_clothing: self.clothing_selected.emit(self.selected_clothing) self.accept() elif index == 2 and self.selected_pet: self.pet_selected.emit(self.selected_pet) self.accept()