from typing import List from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QLineEdit, QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget, QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox, QSpinBox, QMenu, QTabWidget, QTextEdit) from PyQt6.QtCore import Qt, QSize, QTimer, QMetaObject from PyQt6.QtGui import QPixmap, QFontMetrics, QColor, QAction from routes.pokemon_game_desc import PokemonGameDesc from .main_window_controller import MainWindowController from utility.functions import get_display_name from db import db import json class PokemonUI(QWidget): def __init__(self, parent=None): super().__init__(parent) self.controller = MainWindowController(self) self.setup_ui() data = db.get_list_of_pokemon_forms() self.controller.initialize_pokemon_list(data) def setup_ui(self): main_layout = QVBoxLayout(self) self.tab_widget = QTabWidget() main_layout.addWidget(self.tab_widget) self.setup_main_tab() self.setup_db_operations_tab() self.setup_manage_encounters_tab() self.setup_plan_tab() self.setup_route_planning_tab() self.save_button = QPushButton("Save Changes") self.save_button.clicked.connect(self.controller.save_changes) main_layout.addWidget(self.save_button) self.export_button = QPushButton("Export Database") self.export_button.clicked.connect(self.controller.export_database) main_layout.addWidget(self.export_button) def setup_main_tab(self): main_tab = QWidget() main_tab_layout = QHBoxLayout(main_tab) self.tab_widget.addTab(main_tab, "Main") self.create_main_left_panel(main_tab_layout) self.create_main_right_panel(main_tab_layout) def create_main_left_panel(self, main_tab_layout): left_layout = QVBoxLayout() search_layout = QHBoxLayout() self.search_bar = QLineEdit() self.search_bar.setPlaceholderText("Search Pokémon...") self.search_bar.textChanged.connect(self.controller.filter_pokemon_list) search_layout.addWidget(self.search_bar) left_layout.addLayout(search_layout) self.pokemon_list = QListWidget() self.pokemon_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.pokemon_list.customContextMenuRequested.connect(self.controller.show_pokemon_context_menu) self.pokemon_list.currentItemChanged.connect(self.controller.on_pokemon_selected) left_layout.addWidget(self.pokemon_list) self.highlight_no_encounters = QCheckBox("Highlight Pokémon without encounters") self.highlight_no_encounters.stateChanged.connect(self.controller.filter_pokemon_list) left_layout.addWidget(self.highlight_no_encounters) self.filter_home_storable = QCheckBox("Show only Home-storable Pokémon") self.filter_home_storable.stateChanged.connect(self.controller.filter_pokemon_list) left_layout.addWidget(self.filter_home_storable) main_tab_layout.addLayout(left_layout, 1) def create_main_right_panel(self, main_tab_layout): right_layout = QVBoxLayout() # Left side of right panel: Text information info_layout = QHBoxLayout() text_layout = QVBoxLayout() self.edit_form = QFormLayout() self.name_label = QLabel() self.form_name_label = QLabel() self.national_dex_label = QLabel() self.generation_label = QLabel() self.home_checkbox = QCheckBox("Available in Home") self.is_baby_form_checkbox = QCheckBox("Is Baby Form") self.edit_form.addRow("Name:", self.name_label) self.edit_form.addRow("Form:", self.form_name_label) self.edit_form.addRow("National Dex:", self.national_dex_label) self.edit_form.addRow("Generation:", self.generation_label) self.edit_form.addRow(self.home_checkbox) self.edit_form.addRow(self.is_baby_form_checkbox) text_layout.addLayout(self.edit_form) # Right side of right panel: Image image_layout = QVBoxLayout() self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.image_label.setFixedSize(150, 150) image_layout.addWidget(self.image_label) image_layout.addStretch(1) info_layout.addLayout(text_layout) info_layout.addLayout(image_layout) second_half_layout = QVBoxLayout() # Evolution chain tree self.evolution_tree = QTreeWidget() self.evolution_tree.setHeaderLabels(["Pokémon", "Evolution Method"]) self.evolution_tree.setColumnWidth(0, 200) second_half_layout.addWidget(self.evolution_tree) # Add Locations tree self.locations_tree = QTreeWidget() self.locations_tree.setHeaderLabels(["Game/Location", "Details"]) self.locations_tree.setColumnWidth(0, 200) self.locations_tree.itemDoubleClicked.connect(self.controller.edit_encounter) second_half_layout.addWidget(QLabel("Locations:")) second_half_layout.addWidget(self.locations_tree) # Add New Encounter button self.add_encounter_button = QPushButton("Add New Encounter") self.add_encounter_button.clicked.connect(self.controller.add_new_encounter) second_half_layout.addWidget(self.add_encounter_button) # Move buttons to the bottom second_half_layout.addStretch(1) # Add New Evolution button self.add_evolution_button = QPushButton("Add New Evolution") self.add_evolution_button.clicked.connect(self.controller.add_new_evolution) second_half_layout.addWidget(self.add_evolution_button) right_layout.addLayout(info_layout) right_layout.addLayout(second_half_layout) main_tab_layout.addLayout(right_layout, 1) def setup_db_operations_tab(self): db_tab = QWidget() db_tab_layout = QVBoxLayout(db_tab) self.tab_widget.addTab(db_tab, "Database Operations") gather_forms_btn = QPushButton("Gather Pokémon Forms") gather_forms_btn.clicked.connect(self.controller.gather_pokemon_forms) db_tab_layout.addWidget(gather_forms_btn) gather_home_btn = QPushButton("Gather Home Storage Info") gather_home_btn.clicked.connect(self.controller.gather_home_storage_info) db_tab_layout.addWidget(gather_home_btn) gather_evolutions_btn = QPushButton("Gather Evolution Information") gather_evolutions_btn.clicked.connect(self.controller.gather_evolution_info) db_tab_layout.addWidget(gather_evolutions_btn) gather_evolutions_btn = QPushButton("Gather Baby Status") gather_evolutions_btn.clicked.connect(self.controller.gather_baby_status) db_tab_layout.addWidget(gather_evolutions_btn) gather_encounters_btn = QPushButton("Gather Encounter Information") gather_encounters_btn.clicked.connect(self.controller.gather_encounter_info) db_tab_layout.addWidget(gather_encounters_btn) gather_marks_btn = QPushButton("Gather Marks Information") gather_marks_btn.clicked.connect(self.controller.gather_marks_info) db_tab_layout.addWidget(gather_marks_btn) load_shiftable_forms_btn = QPushButton("Load Shiftable Forms") load_shiftable_forms_btn.clicked.connect(self.controller.load_shiftable_forms) db_tab_layout.addWidget(load_shiftable_forms_btn) self.progress_text = QTextEdit() self.progress_text.setReadOnly(True) self.progress_text.setMinimumHeight(200) # Set a minimum height db_tab_layout.addWidget(self.progress_text) db_tab_layout.addStretch(1) reinit_db_btn = QPushButton("Clear and Reinitialize Database") reinit_db_btn.clicked.connect(self.controller.reinitialize_database) db_tab_layout.addWidget(reinit_db_btn) def setup_manage_encounters_tab(self): manage_encounters = QWidget() self.manage_encounters_tab = QHBoxLayout(manage_encounters) self.tab_widget.addTab(manage_encounters, "Manage Encounters") left_layout = QVBoxLayout() self.exclusive_set_list = QListWidget() self.exclusive_set_list.currentItemChanged.connect(self.controller.on_exclusive_set_selected) left_layout.addWidget(QLabel("Exclusive Encounter Sets:")) left_layout.addWidget(self.exclusive_set_list) add_set_button = QPushButton("Add New Set") add_set_button.clicked.connect(self.controller.add_new_exclusive_set) left_layout.addWidget(add_set_button) # Right side: Set details and encounters right_layout = QVBoxLayout() self.set_name_label = QLabel() self.set_description_label = QLabel() self.set_game_label = QLabel() right_layout.addWidget(self.set_name_label) right_layout.addWidget(self.set_description_label) right_layout.addWidget(self.set_game_label) self.encounter_list = QListWidget() self.encounter_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.encounter_list.customContextMenuRequested.connect(self.controller.show_encounter_context_menu) right_layout.addWidget(QLabel("Encounters in Set:")) right_layout.addWidget(self.encounter_list) add_encounter_button = QPushButton("Add Encounter to Set") add_encounter_button.clicked.connect(self.controller.add_encounter_to_set) right_layout.addWidget(add_encounter_button) self.manage_encounters_tab.addLayout(left_layout, 1) self.manage_encounters_tab.addLayout(right_layout, 2) def setup_plan_tab(self): plan_tab = QWidget() plan_tab_layout = QVBoxLayout(plan_tab) self.tab_widget.addTab(plan_tab, "Plan Generation") plan_tab_layout.addStretch(1) generate_plan_btn = QPushButton("Generate Plan") generate_plan_btn.clicked.connect(self.controller.generate_plan) plan_tab_layout.addWidget(generate_plan_btn) def setup_route_planning_tab(self): route_plan_tab = QWidget() route_plan_tab_layout = QVBoxLayout(route_plan_tab) self.tab_widget.addTab(route_plan_tab, "Route Generation") self.route_combo_box = QComboBox() route_plan_tab_layout.addWidget(self.route_combo_box) solve_route_btn = QPushButton("Solve Selected Route") solve_route_btn.clicked.connect(self.solve_route) route_plan_tab_layout.addWidget(solve_route_btn) route_plan_tab_layout.addStretch(1) generate_pddls_btn = QPushButton("Generate PDDLs") generate_pddls_btn.clicked.connect(self.controller.generate_pddls) route_plan_tab_layout.addWidget(generate_pddls_btn) def update_pokemon_forms(self, data): self.pokemon_list.clear() for pokemon in data: display_name = get_display_name(pokemon, not pokemon["gender_relevant"]) item = QListWidgetItem(display_name) item.setData(Qt.ItemDataRole.UserRole, pokemon["pfic"]) self.pokemon_list.addItem(item) def update_evolution_tree(self, evolution_chains, selected_pfic): self.evolution_tree.clear() tree_items = {} for chains in evolution_chains["predecessors"]: for pfic, method in chains: pokemon_details = db.get_pokemon_details(pfic) display_name = get_display_name(pokemon_details, not pokemon_details["gender_relevant"]) item = QTreeWidgetItem([display_name, method if method else ""]) item.setData(0, Qt.ItemDataRole.UserRole, pfic) tree_items[pfic] = item if pfic == selected_pfic: item.setBackground(0, QColor(255, 255, 0, 100)) # Highlight selected Pokémon # Second pass: build the tree structure root = None for pfic, method in chains: item = tree_items[pfic] # Find the parent of this item parent_pfic, method = db.get_previous_evolution(pfic) if parent_pfic: parent_item = tree_items.get(parent_pfic) if parent_item: parent_item.addChild(item) elif not root: root = item self.evolution_tree.addTopLevelItem(root) for chains in evolution_chains["successors"]: for pfic, method in chains: pokemon_details = db.get_pokemon_details(pfic) display_name = get_display_name(pokemon_details, not pokemon_details["gender_relevant"]) item = QTreeWidgetItem([display_name, method if method else ""]) item.setData(0, Qt.ItemDataRole.UserRole, pfic) tree_items[pfic] = item if pfic == selected_pfic: item.setBackground(0, QColor(255, 255, 0, 100)) # Highlight selected Pokémon # Second pass: build the tree structure root = None for pfic, method in chains: item = tree_items[pfic] # Find the parent of this item parent_pfic, method = db.get_previous_evolution(pfic) if parent_pfic: parent_item = tree_items.get(parent_pfic) if parent_item: parent_item.addChild(item) elif not root: root = item self.evolution_tree.addTopLevelItem(root) # Expand the entire tree self.evolution_tree.expandAll() # Scroll to and select the current Pokémon if selected_pfic in tree_items: current_item = tree_items[selected_pfic] self.evolution_tree.scrollToItem(current_item) self.evolution_tree.setCurrentItem(current_item) def update_encounter_list(self, encounters, pfic): self.locations_tree.clear() game_items = {} for encounter in encounters: pfic = encounter["PFIC"] game_id = encounter["game_id"] type = encounter["type"] if type == "event": continue if type == "evolve": continue data = json.loads(encounter["data"]) game = db.get_game_by_id(game_id) game_name = game["name"] location = data["location"] if game_name not in game_items: #print(f'finding generation for {game}') game_item = QTreeWidgetItem([game_name]) game_items[game_name] = game_item # Use generation for sorting, default to 0 if not found game_item.setData(0, Qt.ItemDataRole.UserRole, game["generation"]) #print(f'generation for {game} is {generation}') location_item = QTreeWidgetItem([location]) details = [] if "day" in data: details.append(f"Day: {data["day"]}") if "time" in data: details.append(f"Time: {data["time"]}") if "dual_slot" in data: details.append(f"Dual Slot: {data["dual_slot"]}") if "static_encounter" in data: details.append(f"Static Encounter (Count: {data["static_encounter_count"]})") if "extra_text" in data: details.append(f"Extra: {data["extra_text"]}") if "stars" in data: details.append(f"Stars: {data["stars"]}") if "fishing" in data: details.append(f"Fishing") if "rods" in data: details.append(f"Rods: {data["rods"]}") location_item.setText(1, ", ".join(details)) game_items[game_name].addChild(location_item) # Sort game items by generation and add them to the tree sorted_game_items = sorted(game_items.values(), key=lambda x: x.data(0, Qt.ItemDataRole.UserRole)) self.locations_tree.addTopLevelItems(sorted_game_items) self.locations_tree.expandAll() # Update the cache for this Pokémon #self.encounter_cache[pfic] = len(encounters) > 0 # After updating the locations tree #self.update_pokemon_list_highlights() def update_route_list(self, data: List[PokemonGameDesc]): self.route_combo_box.clear() for item in data: self.route_combo_box.addItem(item.game_name, item) def solve_route(self): index = self.route_combo_box.currentIndex() data = self.route_combo_box.itemData(index, role=Qt.ItemDataRole.UserRole) self.controller.solve_route(data)