import sys from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QLineEdit, QLabel, QCheckBox, QPushButton, QFormLayout, QListWidgetItem, QSplitter, QTreeWidget, QTreeWidgetItem, QDialog, QDialogButtonBox, QComboBox, QMessageBox) from PyQt6.QtCore import Qt, QSize from PyQt6.QtGui import QPixmap, QFontMetrics, QColor import sqlite3 import json import os class EvolutionEditDialog(QDialog): def __init__(self, parent=None, from_pfic=None, to_pfic=None, method=None): super().__init__(parent) self.setWindowTitle("Edit Evolution") self.setModal(True) layout = QVBoxLayout(self) self.from_combo = QComboBox() self.to_combo = QComboBox() self.method_edit = QLineEdit(method) layout.addWidget(QLabel("From Pokémon:")) layout.addWidget(self.from_combo) layout.addWidget(QLabel("To Pokémon:")) layout.addWidget(self.to_combo) layout.addWidget(QLabel("Evolution Method:")) layout.addWidget(self.method_edit) self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) layout.addWidget(self.button_box) self.delete_button = QPushButton("Delete Evolution") self.delete_button.clicked.connect(self.delete_evolution) layout.addWidget(self.delete_button) self.populate_combos(from_pfic, to_pfic) def populate_combos(self, from_pfic, to_pfic): cursor = self.parent().cursor cursor.execute('SELECT PFIC, name, form_name FROM pokemon_forms ORDER BY name, form_name') for pfic, name, form_name in cursor.fetchall(): display_name = f"{name} ({form_name})" if form_name else name self.from_combo.addItem(display_name, pfic) self.to_combo.addItem(display_name, pfic) if from_pfic: self.from_combo.setCurrentIndex(self.from_combo.findData(from_pfic)) if to_pfic: self.to_combo.setCurrentIndex(self.to_combo.findData(to_pfic)) def delete_evolution(self): self.done(2) # Use a custom return code for delete action class DBEditor(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Pokémon Database Editor") self.setGeometry(100, 100, 1000, 600) self.conn = sqlite3.connect(':memory:') # Use in-memory database for runtime self.cursor = self.conn.cursor() self.init_database() self.patches = self.load_and_apply_patches() self.init_ui() def init_database(self): # Copy the original database to the in-memory database disk_conn = sqlite3.connect('pokemon_forms.db') disk_conn.backup(self.conn) disk_conn.close() def load_and_apply_patches(self): try: with open('patches.json', 'r') as f: patches = json.load(f) except FileNotFoundError: patches = {} # Apply patches to the in-memory database for patch_key, patch in patches.items(): if patch_key.startswith('evolution_'): from_pfic, to_pfic = patch_key.split('_')[1:] if patch['action'] == 'delete': self.cursor.execute(''' DELETE FROM evolution_chains WHERE from_pfic = ? AND to_pfic = ? ''', (from_pfic, to_pfic)) elif patch['action'] == 'update': self.cursor.execute(''' UPDATE evolution_chains SET from_pfic = ?, to_pfic = ?, method = ? WHERE from_pfic = ? AND to_pfic = ? ''', (patch['new_from_pfic'], patch['new_to_pfic'], patch['new_method'], from_pfic, to_pfic)) elif patch['action'] == 'add': self.cursor.execute(''' INSERT OR REPLACE INTO evolution_chains (from_pfic, to_pfic, method) VALUES (?, ?, ?) ''', (patch['from_pfic'], patch['to_pfic'], patch['method'])) else: # pokemon_storage patches self.cursor.execute(''' UPDATE pokemon_storage SET storable_in_home = ? WHERE PFIC = ? ''', (patch['storable_in_home'], patch_key)) self.conn.commit() return patches def save_patches(self): with open('patches.json', 'w') as f: json.dump(self.patches, f) def init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QHBoxLayout(central_widget) # Left side: Search and List left_layout = QVBoxLayout() self.search_bar = QLineEdit() self.search_bar.setPlaceholderText("Search Pokémon...") self.search_bar.textChanged.connect(self.filter_pokemon_list) left_layout.addWidget(self.search_bar) self.pokemon_list = QListWidget() self.pokemon_list.currentItemChanged.connect(self.load_pokemon_details) left_layout.addWidget(self.pokemon_list) # Right side: Edit panel right_layout = QHBoxLayout() # Left side of right panel: Text information 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.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) text_layout.addLayout(self.edit_form) # Evolution chain tree self.evolution_tree = QTreeWidget() self.evolution_tree.setHeaderLabels(["Pokémon", "Evolution Method"]) self.evolution_tree.setColumnWidth(0, 200) text_layout.addWidget(self.evolution_tree) # Move buttons to the bottom text_layout.addStretch(1) self.save_button = QPushButton("Save Changes") self.save_button.clicked.connect(self.save_changes) text_layout.addWidget(self.save_button) self.export_button = QPushButton("Export Database") self.export_button.clicked.connect(self.export_database) text_layout.addWidget(self.export_button) # Add New Evolution button self.add_evolution_button = QPushButton("Add New Evolution") self.add_evolution_button.clicked.connect(self.add_new_evolution) text_layout.addWidget(self.add_evolution_button) # Right side of right panel: Image image_layout = QVBoxLayout() self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.image_label.setFixedSize(200, 200) image_layout.addWidget(self.image_label) image_layout.addStretch(1) right_layout.addLayout(text_layout) right_layout.addLayout(image_layout) main_layout.addLayout(left_layout, 1) main_layout.addLayout(right_layout, 1) self.load_pokemon_list() self.adjust_list_width() def adjust_list_width(self): max_width = 0 font_metrics = QFontMetrics(self.pokemon_list.font()) for i in range(self.pokemon_list.count()): item_width = font_metrics.horizontalAdvance(self.pokemon_list.item(i).text()) max_width = max(max_width, item_width) # Add some padding to the width list_width = max_width + 50 self.pokemon_list.setFixedWidth(list_width) self.search_bar.setFixedWidth(list_width) def load_pokemon_list(self): self.cursor.execute(''' SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex FROM pokemon_forms pf ORDER BY pf.national_dex, pf.form_name ''') pokemon_data = self.cursor.fetchall() for pfic, name, form_name, national_dex in pokemon_data: display_name = f"{national_dex:04d} - {name}" if form_name: display_name += f" ({form_name})" item = QListWidgetItem(display_name) item.setData(Qt.ItemDataRole.UserRole, pfic) self.pokemon_list.addItem(item) def filter_pokemon_list(self): search_text = self.search_bar.text().lower() for i in range(self.pokemon_list.count()): item = self.pokemon_list.item(i) item.setHidden(search_text not in item.text().lower()) def load_pokemon_details(self, current, previous): if not current: return pfic = current.data(Qt.ItemDataRole.UserRole) self.cursor.execute(''' SELECT pf.name, pf.form_name, pf.national_dex, pf.generation, ps.storable_in_home FROM pokemon_forms pf LEFT JOIN pokemon_storage ps ON pf.PFIC = ps.PFIC WHERE pf.PFIC = ? ''', (pfic,)) pokemon_data = self.cursor.fetchone() if pokemon_data: name, form_name, national_dex, generation, storable_in_home = pokemon_data self.name_label.setText(name) self.form_name_label.setText(form_name if form_name else "") self.national_dex_label.setText(str(national_dex)) self.generation_label.setText(str(generation)) self.home_checkbox.setChecked(bool(storable_in_home)) # Load and display the image image_path = f"images-new/{pfic}.png" if os.path.exists(image_path): pixmap = QPixmap(image_path) self.image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) else: self.image_label.setText("Image not found") # Load and display evolution chain self.load_evolution_chain(pfic) self.current_pfic = pfic self.add_evolution_button.setEnabled(True) # Enable the button when a Pokémon is selected def edit_evolution(self, item, column): parent = item.parent() if not parent: return # Don't edit the root item from_pfic = parent.data(0, Qt.ItemDataRole.UserRole) to_pfic = item.data(0, Qt.ItemDataRole.UserRole) method = item.text(1) dialog = EvolutionEditDialog(self, from_pfic, to_pfic, method) result = dialog.exec() if result == QDialog.DialogCode.Accepted: new_from_pfic = dialog.from_combo.currentData() new_to_pfic = dialog.to_combo.currentData() new_method = dialog.method_edit.text() # Update the in-memory database self.cursor.execute(''' UPDATE evolution_chains SET from_pfic = ?, to_pfic = ?, method = ? WHERE from_pfic = ? AND to_pfic = ? ''', (new_from_pfic, new_to_pfic, new_method, from_pfic, to_pfic)) # Create or update the patch patch_key = f"evolution_{from_pfic}_{to_pfic}" self.patches[patch_key] = { 'action': 'update', 'new_from_pfic': new_from_pfic, 'new_to_pfic': new_to_pfic, 'new_method': new_method } self.save_patches() # Refresh the evolution chain display self.load_evolution_chain(self.current_pfic) elif result == 2: # Delete action confirm = QMessageBox.question(self, "Confirm Deletion", "Are you sure you want to delete this evolution?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) if confirm == QMessageBox.StandardButton.Yes: # Delete from the in-memory database self.cursor.execute(''' DELETE FROM evolution_chains WHERE from_pfic = ? AND to_pfic = ? ''', (from_pfic, to_pfic)) # Create a delete patch patch_key = f"evolution_{from_pfic}_{to_pfic}" self.patches[patch_key] = {'action': 'delete'} self.save_patches() # Refresh the evolution chain display self.load_evolution_chain(self.current_pfic) def get_evolution_chain(self, pfic): def follow_chain(start_pfic, visited=None): if visited is None: visited = set() chain = [] stack = [(start_pfic, None)] while stack: current_pfic, method = stack.pop() if current_pfic in visited: continue visited.add(current_pfic) self.cursor.execute('SELECT name, form_name FROM pokemon_forms WHERE PFIC = ?', (current_pfic,)) name, form_name = self.cursor.fetchone() chain.append((current_pfic, name, form_name, method)) # Get previous evolutions self.cursor.execute('SELECT from_pfic, method FROM evolution_chains WHERE to_pfic = ?', (current_pfic,)) prev_evolutions = self.cursor.fetchall() for prev_pfic, prev_method in prev_evolutions: if prev_pfic not in visited: stack.append((prev_pfic, prev_method)) # Get next evolutions self.cursor.execute('SELECT to_pfic, method FROM evolution_chains WHERE from_pfic = ?', (current_pfic,)) next_evolutions = self.cursor.fetchall() for next_pfic, next_method in next_evolutions: if next_pfic not in visited: stack.append((next_pfic, next_method)) return chain return follow_chain(pfic) def load_evolution_chain(self, pfic): self.evolution_tree.clear() evolution_chain = self.get_evolution_chain(pfic) # Create a dictionary to store tree items tree_items = {} # First pass: create all tree items for current_pfic, name, form_name, method in evolution_chain: display_name = f"{name} ({form_name})" if form_name else name item = QTreeWidgetItem([display_name, method if method else ""]) item.setData(0, Qt.ItemDataRole.UserRole, current_pfic) tree_items[current_pfic] = item if current_pfic == pfic: item.setBackground(0, QColor(255, 255, 0, 100)) # Highlight selected Pokémon # Second pass: build the tree structure root = None for current_pfic, name, form_name, method in evolution_chain: item = tree_items[current_pfic] # Find the parent of this item self.cursor.execute('SELECT from_pfic FROM evolution_chains WHERE to_pfic = ?', (current_pfic,)) parent_pfic = self.cursor.fetchone() if parent_pfic: parent_item = tree_items.get(parent_pfic[0]) 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 current_item = tree_items[pfic] self.evolution_tree.scrollToItem(current_item) self.evolution_tree.setCurrentItem(current_item) # Connect double-click signal self.evolution_tree.itemDoubleClicked.connect(self.edit_evolution) def save_changes(self): if hasattr(self, 'current_pfic'): storable_in_home = self.home_checkbox.isChecked() self.patches[self.current_pfic] = {'storable_in_home': storable_in_home} with open('patches.json', 'w') as f: json.dump(self.patches, f) def export_database(self): export_conn = sqlite3.connect('pokemon_forms_production.db') self.conn.backup(export_conn) export_conn.close() def closeEvent(self, event): self.conn.close() event.accept() def add_new_evolution(self): if not hasattr(self, 'current_pfic'): return dialog = EvolutionEditDialog(self, self.current_pfic, None, "") result = dialog.exec() if result == QDialog.DialogCode.Accepted: new_from_pfic = dialog.from_combo.currentData() new_to_pfic = dialog.to_combo.currentData() new_method = dialog.method_edit.text() # Update the in-memory database self.cursor.execute(''' INSERT INTO evolution_chains (from_pfic, to_pfic, method) VALUES (?, ?, ?) ''', (new_from_pfic, new_to_pfic, new_method)) # Create a new patch patch_key = f"evolution_{new_from_pfic}_{new_to_pfic}" self.patches[patch_key] = { 'action': 'add', 'from_pfic': new_from_pfic, 'to_pfic': new_to_pfic, 'method': new_method } self.save_patches() # Refresh the evolution chain display self.load_evolution_chain(self.current_pfic) if __name__ == '__main__': app = QApplication(sys.argv) editor = DBEditor() editor.show() sys.exit(app.exec())