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, QSpinBox) 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 def parse_pfic(pfic): parts = pfic.split('-') return tuple(int(part) if part.isdigit() else part for part in parts) 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.encounter_cache = {} # Add this line self.init_ui() #self.load_pokemon_list() # Make sure this is called after init_ui def init_database(self): # Create or open the file-based database disk_conn = sqlite3.connect('pokemon_forms.db') disk_cursor = disk_conn.cursor() # Create tables in the file-based database self.create_pokemon_forms_table(disk_cursor) self.create_pokemon_storage_table(disk_cursor) self.create_evolution_chains_table(disk_cursor) self.create_exclusive_encounter_groups_table(disk_cursor) self.create_encounters_table(disk_cursor) # Commit changes to the file-based database disk_conn.commit() # Copy the file-based database to the in-memory database disk_conn.backup(self.conn) # Close the file-based database connection disk_conn.close() # Create tables in the in-memory database (in case they weren't copied) self.create_pokemon_forms_table(self.cursor) self.create_pokemon_storage_table(self.cursor) self.create_evolution_chains_table(self.cursor) self.create_exclusive_encounter_groups_table(self.cursor) self.create_encounters_table(self.cursor) # Commit changes to the in-memory database self.conn.commit() def create_pokemon_forms_table(self, cursor): cursor.execute(''' CREATE TABLE IF NOT EXISTS pokemon_forms ( PFIC TEXT PRIMARY KEY, name TEXT NOT NULL, form_name TEXT, national_dex INTEGER NOT NULL, generation INTEGER NOT NULL ) ''') def create_pokemon_storage_table(self, cursor): cursor.execute(''' CREATE TABLE IF NOT EXISTS pokemon_storage ( PFIC TEXT PRIMARY KEY, storable_in_home BOOLEAN NOT NULL, FOREIGN KEY (PFIC) REFERENCES pokemon_forms (PFIC) ) ''') def create_evolution_chains_table(self, cursor): cursor.execute(''' CREATE TABLE IF NOT EXISTS evolution_chains ( from_pfic TEXT, to_pfic TEXT, method TEXT, PRIMARY KEY (from_pfic, to_pfic), FOREIGN KEY (from_pfic) REFERENCES pokemon_forms (PFIC), FOREIGN KEY (to_pfic) REFERENCES pokemon_forms (PFIC) ) ''') def create_exclusive_encounter_groups_table(self, cursor): cursor.execute(''' CREATE TABLE IF NOT EXISTS exclusive_encounter_groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, group_name TEXT NOT NULL, description TEXT ) ''') def create_encounters_table(self, cursor): # First, check if the table exists cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='encounters'") table_exists = cursor.fetchone() is not None if not table_exists: # If the table doesn't exist, create it with all columns cursor.execute(''' CREATE TABLE encounters ( id INTEGER PRIMARY KEY AUTOINCREMENT, pfic TEXT, game TEXT, location TEXT, day TEXT, time TEXT, dual_slot TEXT, static_encounter BOOLEAN, static_encounter_count INTEGER, extra_text TEXT, stars TEXT, fishing BOOLEAN, rods TEXT, exclusive_group_id INTEGER, FOREIGN KEY (pfic) REFERENCES pokemon_forms (PFIC), FOREIGN KEY (exclusive_group_id) REFERENCES exclusive_encounter_groups (id) ) ''') else: # If the table exists, check if the exclusive_group_id column exists cursor.execute("PRAGMA table_info(encounters)") columns = [column[1] for column in cursor.fetchall()] if 'exclusive_group_id' not in columns: # If the column doesn't exist, add it cursor.execute(''' ALTER TABLE encounters ADD COLUMN exclusive_group_id INTEGER REFERENCES exclusive_encounter_groups (id) ''') if 'starter' not in columns: # If the column doesn't exist, add it cursor.execute(''' ALTER TABLE encounters ADD COLUMN starter BOOLEAN ''') 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() search_layout = QHBoxLayout() self.search_bar = QLineEdit() self.search_bar.setPlaceholderText("Search Pokémon...") self.search_bar.textChanged.connect(self.filter_pokemon_list) search_layout.addWidget(self.search_bar) left_layout.addLayout(search_layout) self.pokemon_list = QListWidget() self.pokemon_list.currentItemChanged.connect(self.load_pokemon_details) left_layout.addWidget(self.pokemon_list) # Move the checkbox here, after the pokemon_list self.highlight_no_encounters = QCheckBox("Highlight Pokémon without encounters") self.highlight_no_encounters.stateChanged.connect(self.toggle_highlight_mode) left_layout.addWidget(self.highlight_no_encounters) # Add the new checkbox for filtering Home-storable Pokémon self.filter_home_storable = QCheckBox("Show only Home-storable Pokémon") self.filter_home_storable.stateChanged.connect(self.filter_pokemon_list) left_layout.addWidget(self.filter_home_storable) # 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) # 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.edit_encounter) text_layout.addWidget(QLabel("Locations:")) text_layout.addWidget(self.locations_tree) # Add New Encounter button self.add_encounter_button = QPushButton("Add New Encounter") self.add_encounter_button.clicked.connect(self.add_new_encounter) text_layout.addWidget(self.add_encounter_button) # 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() 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.pokemon_list.clear() self.cursor.execute(''' SELECT pf.PFIC, pf.name, pf.form_name, pf.national_dex FROM pokemon_forms pf ''') pokemon_data = self.cursor.fetchall() # Sort the pokemon_data based on PFIC pokemon_data.sort(key=lambda x: parse_pfic(x[0])) 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) self.update_encounter_cache() self.update_pokemon_list_highlights() self.adjust_list_width() self.filter_pokemon_list() def filter_pokemon_list(self): search_text = self.search_bar.text().lower() show_only_home_storable = self.filter_home_storable.isChecked() for i in range(self.pokemon_list.count()): item = self.pokemon_list.item(i) pfic = item.data(Qt.ItemDataRole.UserRole) # Check if the item matches the search text text_match = search_text in item.text().lower() # Check if the item is storable in Home (if the filter is active) home_storable = True if show_only_home_storable: self.cursor.execute('SELECT storable_in_home FROM pokemon_storage WHERE PFIC = ?', (pfic,)) result = self.cursor.fetchone() home_storable = result[0] if result else False # Show the item only if it matches both filters item.setHidden(not (text_match and home_storable)) self.update_pokemon_list_highlights() 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)) self.home_checkbox.stateChanged.connect(self.update_home_storable) # 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) # Load and display encounter locations self.load_encounter_locations(pfic) self.current_pfic = pfic self.add_evolution_button.setEnabled(True) # Enable the button when a Pokémon is selected def update_home_storable(self): if hasattr(self, 'current_pfic'): storable_in_home = self.home_checkbox.isChecked() self.cursor.execute('UPDATE pokemon_storage SET storable_in_home = ? WHERE PFIC = ?', (storable_in_home, self.current_pfic)) self.conn.commit() self.filter_pokemon_list() # Reapply the filter 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) def load_encounter_locations(self, pfic): self.locations_tree.clear() self.cursor.execute(''' SELECT game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing FROM encounters WHERE pfic = ? ORDER BY game, location ''', (pfic,)) encounters = self.cursor.fetchall() game_items = {} for encounter in encounters: game, location, day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, rods, fishing = encounter if game not in game_items: game_item = QTreeWidgetItem([game]) game_items[game] = game_item # Use generation for sorting, default to 0 if not found game_item.setData(0, Qt.ItemDataRole.UserRole, self.game_generations.get(game, 0)) location_item = QTreeWidgetItem([location]) details = [] if day: details.append(f"Day: {day}") if time: details.append(f"Time: {time}") if dual_slot: details.append(f"Dual Slot: {dual_slot}") if static_encounter: details.append(f"Static Encounter (Count: {static_encounter_count})") if extra_text: details.append(f"Extra: {extra_text}") if stars: details.append(f"Stars: {stars}") if fishing: details.append(f"Fishing") if rods: details.append(f"Rods: {rods}") location_item.setText(1, ", ".join(details)) game_items[game].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 edit_encounter(self, item, column): if item.parent() is None: # This is a game item, not a location item return game = item.parent().text(0) location = item.text(0) dialog = QDialog(self) dialog.setWindowTitle("Edit Encounter") layout = QFormLayout(dialog) game_edit = QLineEdit(game) location_edit = QLineEdit(location) day_edit = QLineEdit() time_edit = QLineEdit() dual_slot_edit = QLineEdit() static_encounter_check = QCheckBox("Static Encounter") static_encounter_count_edit = QSpinBox() extra_text_edit = QLineEdit() stars_edit = QLineEdit() fishing_check = QCheckBox("Fishing") rods_edit = QLineEdit() layout.addRow("Game:", game_edit) layout.addRow("Location:", location_edit) layout.addRow("Day:", day_edit) layout.addRow("Time:", time_edit) layout.addRow("Dual Slot:", dual_slot_edit) layout.addRow("Static Encounter:", static_encounter_check) layout.addRow("Static Encounter Count:", static_encounter_count_edit) layout.addRow("Extra Text:", extra_text_edit) layout.addRow("Stars:", stars_edit) layout.addRow("Fishing:", fishing_check) layout.addRow("Rods:", rods_edit) exclusive_group_combo = QComboBox() exclusive_group_combo.addItem("None", None) self.cursor.execute('SELECT id, group_name FROM exclusive_encounter_groups') for group_id, group_name in self.cursor.fetchall(): exclusive_group_combo.addItem(group_name, group_id) layout.addRow("Exclusive Group:", exclusive_group_combo) # Fetch current values self.cursor.execute(''' SELECT day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods, exclusive_group_id FROM encounters WHERE pfic = ? AND game = ? AND location = ? ''', (self.current_pfic, game, location)) current_values = self.cursor.fetchone() if current_values: day, time, dual_slot, static_encounter_count, static_encounter, extra_text, stars, fishing, rods, exclusive_group_id = current_values day_edit.setText(day or "") time_edit.setText(time or "") dual_slot_edit.setText(dual_slot or "") static_encounter_check.setChecked(bool(static_encounter)) static_encounter_count_edit.setValue(static_encounter_count or 0) extra_text_edit.setText(extra_text or "") stars_edit.setText(stars or "") fishing_check.setChecked(bool(fishing)) rods_edit.setText(rods or "") index = exclusive_group_combo.findData(exclusive_group_id) if index >= 0: exclusive_group_combo.setCurrentIndex(index) buttons = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, Qt.Orientation.Horizontal, dialog) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) layout.addRow(buttons) if dialog.exec() == QDialog.DialogCode.Accepted: new_game = game_edit.text() new_location = location_edit.text() new_day = day_edit.text() or None new_time = time_edit.text() or None new_dual_slot = dual_slot_edit.text() or None new_static_encounter = static_encounter_check.isChecked() new_static_encounter_count = static_encounter_count_edit.value() new_extra_text = extra_text_edit.text() or None new_stars = stars_edit.text() or None new_fishing = fishing_check.isChecked() new_rods = rods_edit.text() or None new_exclusive_group_id = exclusive_group_combo.currentData() # Update the database self.cursor.execute(''' UPDATE encounters SET game = ?, location = ?, day = ?, time = ?, dual_slot = ?, static_encounter = ?, static_encounter_count = ?, extra_text = ?, stars = ?, fishing = ?, rods = ?, exclusive_group_id = ? WHERE pfic = ? AND game = ? AND location = ? ''', (new_game, new_location, new_day, new_time, new_dual_slot, new_static_encounter, new_static_encounter_count, new_extra_text, new_stars, new_fishing, new_rods, new_exclusive_group_id, self.current_pfic, game, location)) self.conn.commit() # Update the cache if all encounters for this Pokémon were deleted if not self.check_pokemon_has_encounters(self.current_pfic): self.encounter_cache[self.current_pfic] = False # Refresh the locations tree self.load_encounter_locations(self.current_pfic) def add_new_encounter(self): dialog = QDialog(self) dialog.setWindowTitle("Add New Encounter") layout = QFormLayout(dialog) game_edit = QLineEdit() location_edit = QLineEdit() day_edit = QLineEdit() time_edit = QLineEdit() dual_slot_edit = QLineEdit() static_encounter_check = QCheckBox("Static Encounter") static_encounter_count_edit = QSpinBox() extra_text_edit = QLineEdit() stars_edit = QLineEdit() fishing_check = QCheckBox("Fishing") rods_edit = QLineEdit() layout.addRow("Game:", game_edit) layout.addRow("Location:", location_edit) layout.addRow("Day:", day_edit) layout.addRow("Time:", time_edit) layout.addRow("Dual Slot:", dual_slot_edit) layout.addRow("Static Encounter:", static_encounter_check) layout.addRow("Static Encounter Count:", static_encounter_count_edit) layout.addRow("Extra Text:", extra_text_edit) layout.addRow("Stars:", stars_edit) layout.addRow("Fishing:", fishing_check) layout.addRow("Rods:", rods_edit) exclusive_group_combo = QComboBox() exclusive_group_combo.addItem("None", None) self.cursor.execute('SELECT id, group_name FROM exclusive_encounter_groups') for group_id, group_name in self.cursor.fetchall(): exclusive_group_combo.addItem(group_name, group_id) layout.addRow("Exclusive Group:", exclusive_group_combo) buttons = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, Qt.Orientation.Horizontal, dialog) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) layout.addRow(buttons) if dialog.exec() == QDialog.DialogCode.Accepted: game = game_edit.text() location = location_edit.text() day = day_edit.text() or None time = time_edit.text() or None dual_slot = dual_slot_edit.text() or None static_encounter = static_encounter_check.isChecked() static_encounter_count = static_encounter_count_edit.value() extra_text = extra_text_edit.text() or None stars = stars_edit.text() or None fishing = fishing_check.isChecked() rods = rods_edit.text() or None exclusive_group_id = exclusive_group_combo.currentData() # Insert new encounter into the database self.cursor.execute(''' INSERT INTO encounters (pfic, game, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods, exclusive_group_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (self.current_pfic, game, location, day, time, dual_slot, static_encounter, static_encounter_count, extra_text, stars, fishing, rods, exclusive_group_id)) self.conn.commit() # Update the cache self.encounter_cache[self.current_pfic] = True # Refresh the locations tree self.load_encounter_locations(self.current_pfic) # Add this as a class attribute in the DBEditor class game_generations = { "Red": 1, "Blue": 1, "Yellow": 1, "Gold": 2, "Silver": 2, "Crystal": 2, "Ruby": 3, "Sapphire": 3, "Emerald": 3, "FireRed": 3, "LeafGreen": 3, "Diamond": 4, "Pearl": 4, "Platinum": 4, "HeartGold": 4, "SoulSilver": 4, "Black": 5, "White": 5, "Black 2": 5, "White 2": 5, "X": 6, "Y": 6, "Omega Ruby": 6, "Alpha Sapphire": 6, "Sun": 7, "Moon": 7, "Ultra Sun": 7, "Ultra Moon": 7, "Sword": 8, "Shield": 8, "Brilliant Diamond": 8, "Shining Pearl": 8, "Expansion Pass": 8, "Legends: Arceus": 8, "Scarlet": 9, "Violet": 9, "The Teal Mask": 9, "The Hidden Treasure of Area Zero": 9, "The Hidden Treasure of Area Zero (Scarlet)": 9, "The Hidden Treasure of Area Zero (Violet)": 9, "The Teal Mask (Scarlet)": 9, "The Teal Mask (Violet)": 9, "Pokémon Go": 0, "Pokémon Home": 0 } def toggle_highlight_mode(self): self.update_pokemon_list_highlights() def update_pokemon_list_highlights(self): highlight_mode = self.highlight_no_encounters.isChecked() for i in range(self.pokemon_list.count()): item = self.pokemon_list.item(i) pfic = item.data(Qt.ItemDataRole.UserRole) if highlight_mode: has_encounters = self.encounter_cache.get(pfic, False) if not has_encounters: item.setData(Qt.ItemDataRole.BackgroundRole, QColor(255, 200, 200)) # Light red background else: item.setData(Qt.ItemDataRole.BackgroundRole, None) # White background else: item.setData(Qt.ItemDataRole.BackgroundRole, None) # White background def update_encounter_cache(self): self.cursor.execute(''' SELECT DISTINCT pfic FROM encounters ''') pokemon_with_encounters = set(row[0] for row in self.cursor.fetchall()) for i in range(self.pokemon_list.count()): item = self.pokemon_list.item(i) pfic = item.data(Qt.ItemDataRole.UserRole) self.encounter_cache[pfic] = pfic in pokemon_with_encounters def check_pokemon_has_encounters(self, pfic): return self.encounter_cache.get(pfic, False) def manage_exclusive_groups(self): dialog = QDialog(self) dialog.setWindowTitle("Manage Exclusive Encounter Groups") layout = QVBoxLayout(dialog) group_list = QListWidget() self.cursor.execute('SELECT id, group_name, description FROM exclusive_encounter_groups') for group_id, group_name, description in self.cursor.fetchall(): item = QListWidgetItem(f"{group_name} - {description}") item.setData(Qt.ItemDataRole.UserRole, group_id) group_list.addItem(item) layout.addWidget(group_list) add_button = QPushButton("Add Group") edit_button = QPushButton("Edit Group") delete_button = QPushButton("Delete Group") button_layout = QHBoxLayout() button_layout.addWidget(add_button) button_layout.addWidget(edit_button) button_layout.addWidget(delete_button) layout.addLayout(button_layout) add_button.clicked.connect(lambda: self.add_edit_exclusive_group(dialog, group_list)) edit_button.clicked.connect(lambda: self.add_edit_exclusive_group(dialog, group_list, group_list.currentItem())) delete_button.clicked.connect(lambda: self.delete_exclusive_group(dialog, group_list, group_list.currentItem())) dialog.exec() def add_edit_exclusive_group(self, parent_dialog, group_list, item=None): dialog = QDialog(parent_dialog) dialog.setWindowTitle("Add/Edit Exclusive Group") layout = QFormLayout(dialog) name_edit = QLineEdit() description_edit = QLineEdit() layout.addRow("Group Name:", name_edit) layout.addRow("Description:", description_edit) if item: group_id = item.data(Qt.ItemDataRole.UserRole) self.cursor.execute('SELECT group_name, description FROM exclusive_encounter_groups WHERE id = ?', (group_id,)) group_name, description = self.cursor.fetchone() name_edit.setText(group_name) description_edit.setText(description) buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) layout.addRow(buttons) if dialog.exec() == QDialog.DialogCode.Accepted: name = name_edit.text() description = description_edit.text() if item: self.cursor.execute('UPDATE exclusive_encounter_groups SET group_name = ?, description = ? WHERE id = ?', (name, description, group_id)) item.setText(f"{name} - {description}") else: self.cursor.execute('INSERT INTO exclusive_encounter_groups (group_name, description) VALUES (?, ?)', (name, description)) group_id = self.cursor.lastrowid new_item = QListWidgetItem(f"{name} - {description}") new_item.setData(Qt.ItemDataRole.UserRole, group_id) group_list.addItem(new_item) self.conn.commit() def delete_exclusive_group(self, parent_dialog, group_list, item): if item: group_id = item.data(Qt.ItemDataRole.UserRole) reply = QMessageBox.question(parent_dialog, 'Delete Group', 'Are you sure you want to delete this group?', QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No) if reply == QMessageBox.StandardButton.Yes: self.cursor.execute('DELETE FROM exclusive_encounter_groups WHERE id = ?', (group_id,)) self.cursor.execute('UPDATE encounters SET exclusive_group_id = NULL WHERE exclusive_group_id = ?', (group_id,)) self.conn.commit() group_list.takeItem(group_list.row(item)) if __name__ == '__main__': app = QApplication(sys.argv) editor = DBEditor() editor.show() sys.exit(app.exec())