You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
23 KiB
585 lines
23 KiB
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())
|
|
|