import sys import sqlite3 import json import os from datetime import datetime from PyQt6.QtWidgets import (QApplication, QMainWindow, QListWidget, QLabel, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QLineEdit, QCheckBox, QFormLayout, QMessageBox, QDialog, QComboBox, QFileDialog, QTabWidget) from PyQt6.QtGui import QPixmap from PyQt6.QtCore import Qt class PokemonDatabaseApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Pokémon Database Viewer") self.setGeometry(100, 100, 1200, 600) # Create an in-memory database self.conn = sqlite3.connect(':memory:') self.cursor = self.conn.cursor() # Load the database from disk into memory self.load_database() # Apply all existing patches self.apply_all_patches() self.current_pokemon_id = None self.current_pokemon_name = None self.current_form_id = None self.all_pokemon = [] self.patches = {} self.init_ui() self.load_locations_list() def init_ui(self): main_layout = QVBoxLayout() # Create tab widget self.tab_widget = QTabWidget() # Create and add Pokemon tab pokemon_tab = self.create_pokemon_tab() self.tab_widget.addTab(pokemon_tab, "Pokémon") # Create and add Locations tab locations_tab = self.create_locations_tab() self.tab_widget.addTab(locations_tab, "Locations") main_layout.addWidget(self.tab_widget) container = QWidget() container.setLayout(main_layout) self.setCentralWidget(container) self.load_pokemon_list() def create_pokemon_tab(self): pokemon_tab = QWidget() tab_layout = QHBoxLayout() # Pokémon list section pokemon_list_layout = QVBoxLayout() # Search box self.search_box = QLineEdit() self.search_box.setPlaceholderText("Search Pokémon...") self.search_box.textChanged.connect(self.filter_pokemon_list) pokemon_list_layout.addWidget(self.search_box) # Pokémon list self.pokemon_list = QListWidget() self.pokemon_list.itemClicked.connect(self.load_pokemon_data) pokemon_list_layout.addWidget(self.pokemon_list) tab_layout.addLayout(pokemon_list_layout, 1) # Pokémon details details_layout = QVBoxLayout() image_and_form_layout = QHBoxLayout() # Image self.image_label = QLabel() self.image_label.setFixedSize(200, 200) self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) image_and_form_layout.addWidget(self.image_label) # Form details form_details_layout = QFormLayout() self.form_name_edit = QLineEdit() self.is_default_checkbox = QCheckBox() self.image_path_edit = QLineEdit() form_details_layout.addRow("Form Name:", self.form_name_edit) form_details_layout.addRow("Default Form:", self.is_default_checkbox) form_details_layout.addRow("Image Path:", self.image_path_edit) # Save button self.save_button = QPushButton("Save Form Changes") self.save_button.clicked.connect(self.save_form_changes) form_details_layout.addRow(self.save_button) image_and_form_layout.addLayout(form_details_layout) details_layout.addLayout(image_and_form_layout) # Forms list and add new form button forms_layout = QHBoxLayout() self.forms_list = QListWidget() self.forms_list.itemClicked.connect(self.load_form_data) forms_layout.addWidget(self.forms_list) add_form_button = QPushButton("Add New Form") add_form_button.clicked.connect(self.add_new_form) details_layout.addWidget(add_form_button) details_layout.addWidget(QLabel("Forms:")) details_layout.addLayout(forms_layout) # Encounters table and add new encounter button self.encounters_table = QTableWidget() self.encounters_table.setColumnCount(3) self.encounters_table.setHorizontalHeaderLabels(['Game', 'Location', 'Method']) self.encounters_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) details_layout.addWidget(QLabel("Encounters:")) details_layout.addWidget(self.encounters_table) add_encounter_button = QPushButton("Add New Encounter") add_encounter_button.clicked.connect(self.add_new_encounter) details_layout.addWidget(add_encounter_button) tab_layout.addLayout(details_layout, 2) pokemon_tab.setLayout(tab_layout) return pokemon_tab def create_locations_tab(self): locations_tab = QWidget() tab_layout = QHBoxLayout() # Locations list locations_list_layout = QVBoxLayout() # Search box for locations self.locations_search_box = QLineEdit() self.locations_search_box.setPlaceholderText("Search Locations...") self.locations_search_box.textChanged.connect(self.filter_locations_list) locations_list_layout.addWidget(self.locations_search_box) # Locations list self.locations_list = QListWidget() self.locations_list.itemClicked.connect(self.load_location_data) locations_list_layout.addWidget(self.locations_list) # Add new location button add_location_button = QPushButton("Add New Location") add_location_button.clicked.connect(self.add_new_location) locations_list_layout.addWidget(add_location_button) tab_layout.addLayout(locations_list_layout, 1) # Location details location_details_layout = QFormLayout() self.location_name_edit = QLineEdit() self.location_description_edit = QLineEdit() location_details_layout.addRow("Name:", self.location_name_edit) location_details_layout.addRow("description:", self.location_description_edit) # Save location changes button save_location_button = QPushButton("Save Location Changes") save_location_button.clicked.connect(self.save_location_changes) location_details_layout.addRow(save_location_button) tab_layout.addLayout(location_details_layout, 2) locations_tab.setLayout(tab_layout) return locations_tab def load_database(self): disk_conn = sqlite3.connect('pokemon_database.db') disk_conn.backup(self.conn) disk_conn.close() def apply_all_patches(self): patches_dir = "patches" if not os.path.exists(patches_dir): return patch_files = sorted([f for f in os.listdir(patches_dir) if f.endswith('.json')]) for patch_file in patch_files: with open(os.path.join(patches_dir, patch_file), 'r') as f: patches = json.load(f) self.apply_patches(patches) def apply_patches(self, patches): try: for timestamp, patch in patches.items(): if patch["type"] == "form_update": self.cursor.execute(""" UPDATE pokemon_forms SET form_name = ?, is_default = ?, image_path = ? WHERE id = ? """, (patch["form_name"], patch["is_default"], patch["image_path"], patch["form_id"])) elif patch["type"] == "new_form": self.cursor.execute(""" INSERT INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) VALUES (?, ?, ?, ?) """, (patch["pokemon_id"], patch["form_name"], patch["is_default"], patch["image_path"])) elif patch["type"] == "new_encounter": self.cursor.execute(""" INSERT INTO form_encounters (form_id, game_id, location_id, encounter_method_id) VALUES (?, (SELECT id FROM games WHERE name = ?), (SELECT id FROM locations WHERE name = ?), (SELECT id FROM encounter_methods WHERE name = ?)) """, (patch["form_id"], patch["game"], patch["location"], patch["method"])) elif patch["type"] == "location_update": self.cursor.execute(""" UPDATE locations SET name = ?, description = ? WHERE name = ? """, (patch["new_name"], patch["description"], patch["old_name"])) elif patch["type"] == "new_location": self.cursor.execute(""" INSERT INTO locations (name, description) VALUES (?, ?) """, (patch["name"], patch["description"])) self.conn.commit() except sqlite3.Error as e: print(f"An error occurred while applying patches: {e}") def load_pokemon_list(self): self.cursor.execute("SELECT national_dex_number, name FROM pokemon ORDER BY national_dex_number") self.all_pokemon = [f"{row[0]:03d} - {row[1]}" for row in self.cursor.fetchall()] self.pokemon_list.addItems(self.all_pokemon) def filter_pokemon_list(self): search_text = self.search_box.text().lower() self.pokemon_list.clear() for pokemon in self.all_pokemon: if search_text in pokemon.lower(): self.pokemon_list.addItem(pokemon) def load_pokemon_data(self, item): self.current_pokemon_id = int(item.text().split('-')[0]) self.current_pokemon_name = item.text().split('-')[1] # Load forms self.forms_list.clear() self.cursor.execute(""" SELECT form_name FROM pokemon_forms WHERE pokemon_id = ? ORDER BY is_default DESC, form_name """, (self.current_pokemon_id,)) for row in self.cursor.fetchall(): self.forms_list.addItem(row[0]) # Load default form data self.forms_list.setCurrentRow(0) self.load_form_data(self.forms_list.item(0)) def load_form_data(self, item): if not item: return form_name = item.text() # Load form data self.cursor.execute(""" SELECT id, form_name, is_default, image_path FROM pokemon_forms WHERE pokemon_id = ? AND form_name = ? """, (self.current_pokemon_id, form_name)) form_data = self.cursor.fetchone() if form_data: self.current_form_id, form_name, is_default, image_path = form_data # Update form details self.form_name_edit.setText(form_name) self.is_default_checkbox.setChecked(bool(is_default)) self.image_path_edit.setText(image_path) # Load image pixmap = QPixmap(image_path) self.image_label.setPixmap(pixmap.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio)) # Load encounters self.encounters_table.setRowCount(0) self.cursor.execute(""" SELECT g.name, l.name, em.name FROM form_encounters fe JOIN games g ON fe.game_id = g.id JOIN locations l ON fe.location_id = l.id JOIN encounter_methods em ON fe.encounter_method_id = em.id WHERE fe.form_id = ? ORDER BY g.name, l.name """, (self.current_form_id,)) for row in self.cursor.fetchall(): current_row = self.encounters_table.rowCount() self.encounters_table.insertRow(current_row) for col, value in enumerate(row): self.encounters_table.setItem(current_row, col, QTableWidgetItem(str(value))) def save_form_changes(self): if not self.current_form_id: return new_form_name = self.form_name_edit.text() new_is_default = self.is_default_checkbox.isChecked() new_image_path = self.image_path_edit.text() # Add changes to patches patch = { "type": "form_update", "form_id": self.current_form_id, "form_name": new_form_name, "is_default": new_is_default, "image_path": new_image_path } self.add_to_patches(patch) try: self.cursor.execute(""" UPDATE pokemon_forms SET form_name = ?, is_default = ?, image_path = ? WHERE id = ? """, (new_form_name, new_is_default, new_image_path, self.current_form_id)) self.conn.commit() QMessageBox.information(self, "Success", "Form data updated successfully!") # Refresh the forms list and current form data self.load_pokemon_data(self.pokemon_list.currentItem()) except sqlite3.Error as e: QMessageBox.warning(self, "Error", f"An error occurred: {e}") def add_new_form(self): if not self.current_pokemon_id: QMessageBox.warning(self, "Error", "Please select a Pokémon first.") return dialog = QDialog(self) dialog.setWindowTitle("Add New Form") layout = QFormLayout(dialog) form_name_edit = QLineEdit() layout.addRow("Form Name:", form_name_edit) buttons = QHBoxLayout() save_button = QPushButton("Save") save_button.clicked.connect(dialog.accept) cancel_button = QPushButton("Cancel") cancel_button.clicked.connect(dialog.reject) buttons.addWidget(save_button) buttons.addWidget(cancel_button) layout.addRow(buttons) if dialog.exec() == QDialog.DialogCode.Accepted: form_name = form_name_edit.text() try: self.cursor.execute(""" INSERT INTO pokemon_forms (pokemon_id, form_name, is_default, image_path) VALUES (?, ?, ?, ?) """, (self.current_pokemon_id, form_name, False, f"images/pokemon/{self.current_pokemon_id:04d}_{self.current_pokemon_name}_({form_name}).png".replace(" ", "_"))) self.conn.commit() new_form_id = self.cursor.lastrowid # Add new form to patches patch = { "type": "new_form", "form_id": new_form_id, "pokemon_id": self.current_pokemon_id, "form_name": form_name, "is_default": False, "image_path": f"images/pokemon/{self.current_pokemon_id:04d}_{self.current_pokemon_name}_({form_name}).png".replace(" ", "_") } self.add_to_patches(patch) QMessageBox.information(self, "Success", "New form added successfully!") self.load_pokemon_data(self.pokemon_list.currentItem()) except sqlite3.Error as e: QMessageBox.warning(self, "Error", f"An error occurred: {e}") def add_new_encounter(self): if not self.current_form_id: QMessageBox.warning(self, "Error", "Please select a form first.") return dialog = QDialog(self) dialog.setWindowTitle("Add New Encounter") layout = QFormLayout(dialog) game_combo = QComboBox() self.cursor.execute("SELECT name FROM games ORDER BY name") games = [row[0] for row in self.cursor.fetchall()] game_combo.addItems(games) layout.addRow("Game:", game_combo) location_combo = QComboBox() self.cursor.execute("SELECT name FROM locations ORDER BY name") locations = [row[0] for row in self.cursor.fetchall()] location_combo.addItems(locations) layout.addRow("Location:", location_combo) method_combo = QComboBox() self.cursor.execute("SELECT name FROM encounter_methods ORDER BY name") methods = [row[0] for row in self.cursor.fetchall()] method_combo.addItems(methods) layout.addRow("Method:", method_combo) buttons = QHBoxLayout() save_button = QPushButton("Save") save_button.clicked.connect(dialog.accept) cancel_button = QPushButton("Cancel") cancel_button.clicked.connect(dialog.reject) buttons.addWidget(save_button) buttons.addWidget(cancel_button) layout.addRow(buttons) if dialog.exec() == QDialog.DialogCode.Accepted: game = game_combo.currentText() location = location_combo.currentText() method = method_combo.currentText() try: self.cursor.execute(""" INSERT INTO form_encounters (form_id, game_id, location_id, encounter_method_id) VALUES (?, (SELECT id FROM games WHERE name = ?), (SELECT id FROM locations WHERE name = ?), (SELECT id FROM encounter_methods WHERE name = ?)) """, (self.current_form_id, game, location, method)) self.conn.commit() new_encounter_id = self.cursor.lastrowid # Add new encounter to patches patch = { "type": "new_encounter", "encounter_id": new_encounter_id, "form_id": self.current_form_id, "game": game, "location": location, "method": method } self.add_to_patches(patch) QMessageBox.information(self, "Success", "New encounter added successfully!") self.load_form_data(self.forms_list.currentItem()) except sqlite3.Error as e: QMessageBox.warning(self, "Error", f"An error occurred: {e}") def add_to_patches(self, patch): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") self.patches[f"{timestamp}_{len(self.patches)}"] = patch self.auto_save_patches() def auto_save_patches(self): patches_dir = "patches" if not os.path.exists(patches_dir): os.makedirs(patches_dir) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") file_name = f"{patches_dir}/patch_{timestamp}.json" with open(file_name, 'w') as f: json.dump(self.patches, f, indent=2) print(f"Patches auto-saved to {file_name}") def closeEvent(self, event): reply = QMessageBox.question(self, 'Save Changes', "Do you want to save changes to the disk database?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) if reply == QMessageBox.StandardButton.Yes: # Save changes to disk disk_conn = sqlite3.connect('pokemon_database.db') self.conn.backup(disk_conn) disk_conn.close() QMessageBox.information(self, "Success", "Changes saved to disk database.") self.conn.close() event.accept() def filter_locations_list(self): search_text = self.locations_search_box.text().lower() for i in range(self.locations_list.count()): item = self.locations_list.item(i) if search_text in item.text().lower(): item.setHidden(False) else: item.setHidden(True) def load_location_data(self, item): location_name = item.text() self.cursor.execute("SELECT name, description FROM locations WHERE name = ?", (location_name,)) location_data = self.cursor.fetchone() if location_data: self.location_name_edit.setText(location_data[0]) self.location_description_edit.setText(location_data[1]) def save_location_changes(self): old_name = self.locations_list.currentItem().text() new_name = self.location_name_edit.text() new_description = self.location_description_edit.text() try: self.cursor.execute(""" UPDATE locations SET name = ?, description = ? WHERE name = ? """, (new_name, new_description, old_name)) self.conn.commit() # Add changes to patches patch = { "type": "location_update", "old_name": old_name, "new_name": new_name, "description": new_description } self.add_to_patches(patch) QMessageBox.information(self, "Success", "Location data updated successfully!") self.load_locations_list() except sqlite3.Error as e: QMessageBox.warning(self, "Error", f"An error occurred: {e}") def add_new_location(self): dialog = QDialog(self) dialog.setWindowTitle("Add New Location") layout = QFormLayout(dialog) name_edit = QLineEdit() description_edit = QLineEdit() layout.addRow("Name:", name_edit) layout.addRow("description:", description_edit) buttons = QHBoxLayout() save_button = QPushButton("Save") save_button.clicked.connect(dialog.accept) cancel_button = QPushButton("Cancel") cancel_button.clicked.connect(dialog.reject) buttons.addWidget(save_button) buttons.addWidget(cancel_button) layout.addRow(buttons) if dialog.exec() == QDialog.DialogCode.Accepted: name = name_edit.text() description = description_edit.text() try: self.cursor.execute(""" INSERT INTO locations (name, description) VALUES (?, ?) """, (name, description)) self.conn.commit() # Add new location to patches patch = { "type": "new_location", "name": name, "description": description } self.add_to_patches(patch) QMessageBox.information(self, "Success", "New location added successfully!") self.load_locations_list() except sqlite3.Error as e: QMessageBox.warning(self, "Error", f"An error occurred: {e}") def load_locations_list(self): self.locations_list.clear() self.cursor.execute("SELECT name FROM locations ORDER BY name") locations = [row[0] for row in self.cursor.fetchall()] self.locations_list.addItems(locations) if __name__ == '__main__': app = QApplication(sys.argv) window = PokemonDatabaseApp() window.show() sys.exit(app.exec())