341 lines
13 KiB
Python
341 lines
13 KiB
Python
import sys
|
|
import os
|
|
import xml.etree.ElementTree as ET
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton,
|
|
QMessageBox, QHeaderView, QLabel, QComboBox, QSpinBox,
|
|
QFileDialog, QCheckBox, QScrollBar)
|
|
from PyQt5.QtCore import Qt, QObject, QEvent
|
|
|
|
class CustomTable(QTableWidget):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.installEventFilter(self)
|
|
self.setVerticalScrollMode(QTableWidget.ScrollPerItem)
|
|
self.verticalScrollBar().setSingleStep(1)
|
|
self.verticalScrollBar().setPageStep(1)
|
|
|
|
def eventFilter(self, obj, event):
|
|
if event.type() == QEvent.KeyPress:
|
|
if event.key() == Qt.Key_Up:
|
|
current_row = self.currentRow()
|
|
if current_row > 0:
|
|
self.selectRow(current_row - 1)
|
|
self.setCurrentCell(current_row - 1, self.currentColumn())
|
|
# Stelle sicher, dass die ausgewählte Zeile sichtbar ist
|
|
self.scrollToItem(self.item(current_row - 1, 0))
|
|
return True
|
|
elif event.key() == Qt.Key_Down:
|
|
current_row = self.currentRow()
|
|
if current_row < self.rowCount() - 1:
|
|
self.selectRow(current_row + 1)
|
|
self.setCurrentCell(current_row + 1, self.currentColumn())
|
|
# Stelle sicher, dass die ausgewählte Zeile sichtbar ist
|
|
self.scrollToItem(self.item(current_row + 1, 0))
|
|
return True
|
|
return super().eventFilter(obj, event)
|
|
|
|
class AnimeListEditor(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("MyAnimeList Editor")
|
|
self.setGeometry(100, 100, 1200, 600)
|
|
|
|
# Initialisiere Variablen
|
|
self.current_file = None
|
|
self.tree = None
|
|
self.root = None
|
|
|
|
# Erstelle das zentrale Widget und Layout
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
layout = QVBoxLayout(central_widget)
|
|
|
|
# Erstelle die obere Button-Leiste
|
|
top_button_layout = QHBoxLayout()
|
|
|
|
open_button = QPushButton("XML Öffnen")
|
|
open_button.clicked.connect(self.open_file)
|
|
top_button_layout.addWidget(open_button)
|
|
|
|
save_button = QPushButton("Speichern")
|
|
save_button.clicked.connect(self.save_changes)
|
|
save_button.setEnabled(False)
|
|
self.save_button = save_button
|
|
top_button_layout.addWidget(save_button)
|
|
|
|
save_as_button = QPushButton("Speichern unter")
|
|
save_as_button.clicked.connect(self.save_as)
|
|
save_as_button.setEnabled(False)
|
|
self.save_as_button = save_as_button
|
|
top_button_layout.addWidget(save_as_button)
|
|
|
|
# Füge Massenbearbeitung-Buttons hinzu
|
|
top_button_layout.addStretch()
|
|
|
|
select_all_button = QPushButton("Alle auswählen")
|
|
select_all_button.clicked.connect(self.select_all_entries)
|
|
top_button_layout.addWidget(select_all_button)
|
|
|
|
deselect_all_button = QPushButton("Alle abwählen")
|
|
deselect_all_button.clicked.connect(self.deselect_all_entries)
|
|
top_button_layout.addWidget(deselect_all_button)
|
|
|
|
delete_selected_button = QPushButton("Ausgewählte löschen")
|
|
delete_selected_button.clicked.connect(self.delete_selected_entries)
|
|
top_button_layout.addWidget(delete_selected_button)
|
|
|
|
layout.addLayout(top_button_layout)
|
|
|
|
# Erstelle die Tabelle
|
|
self.table = CustomTable()
|
|
self.table.setColumnCount(8) # Eine zusätzliche Spalte für Checkboxen
|
|
self.table.setHorizontalHeaderLabels([
|
|
"Auswahl", "Titel", "Typ", "Episoden", "Status",
|
|
"Gesehene Episoden", "Bewertung", "Aktionen"
|
|
])
|
|
|
|
# Setze die Spaltenbreiten
|
|
header = self.table.horizontalHeader()
|
|
header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # Checkbox-Spalte
|
|
header.setSectionResizeMode(1, QHeaderView.Stretch) # Titel-Spalte
|
|
for i in range(2, 8):
|
|
header.setSectionResizeMode(i, QHeaderView.ResizeToContents)
|
|
|
|
layout.addWidget(self.table)
|
|
|
|
def select_all_entries(self):
|
|
for row in range(self.table.rowCount()):
|
|
checkbox = self.table.cellWidget(row, 0)
|
|
if checkbox:
|
|
checkbox.setChecked(True)
|
|
|
|
def deselect_all_entries(self):
|
|
for row in range(self.table.rowCount()):
|
|
checkbox = self.table.cellWidget(row, 0)
|
|
if checkbox:
|
|
checkbox.setChecked(False)
|
|
|
|
def delete_selected_entries(self):
|
|
selected_rows = []
|
|
for row in range(self.table.rowCount()):
|
|
checkbox = self.table.cellWidget(row, 0)
|
|
if checkbox and checkbox.isChecked():
|
|
selected_rows.append(row)
|
|
|
|
if not selected_rows:
|
|
QMessageBox.information(self, "Information", "Keine Einträge ausgewählt!")
|
|
return
|
|
|
|
reply = QMessageBox.question(
|
|
self, 'Bestätigung',
|
|
f'Möchten Sie die ausgewählten {len(selected_rows)} Einträge wirklich löschen?',
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
# Lösche von unten nach oben, um die Indizes nicht zu verschieben
|
|
for row in sorted(selected_rows, reverse=True):
|
|
self.table.removeRow(row)
|
|
|
|
def open_file(self):
|
|
file_name, _ = QFileDialog.getOpenFileName(
|
|
self,
|
|
"MyAnimeList XML öffnen",
|
|
"",
|
|
"XML Dateien (*.xml);;Alle Dateien (*.*)"
|
|
)
|
|
|
|
if file_name:
|
|
try:
|
|
self.tree = ET.parse(file_name)
|
|
self.root = self.tree.getroot()
|
|
self.current_file = file_name
|
|
|
|
# Aktualisiere den Fenstertitel
|
|
self.setWindowTitle(f"MyAnimeList Editor - {os.path.basename(file_name)}")
|
|
|
|
# Aktiviere die Buttons
|
|
self.save_button.setEnabled(True)
|
|
self.save_as_button.setEnabled(True)
|
|
|
|
# Lade die Daten
|
|
self.load_anime_data()
|
|
|
|
except ET.ParseError:
|
|
QMessageBox.critical(
|
|
self,
|
|
"Fehler",
|
|
"Die ausgewählte Datei ist keine gültige XML-Datei."
|
|
)
|
|
except Exception as e:
|
|
QMessageBox.critical(
|
|
self,
|
|
"Fehler",
|
|
f"Fehler beim Öffnen der Datei: {str(e)}"
|
|
)
|
|
|
|
def load_anime_data(self):
|
|
if not self.root:
|
|
return
|
|
|
|
# Finde alle Anime-Einträge
|
|
anime_entries = self.root.findall(".//anime")
|
|
|
|
# Setze die Anzahl der Zeilen
|
|
self.table.setRowCount(len(anime_entries))
|
|
|
|
# Deaktiviere die Sortierung während des Ladens
|
|
self.table.setSortingEnabled(False)
|
|
|
|
# Fülle die Tabelle
|
|
for row, anime in enumerate(anime_entries):
|
|
# Checkbox für Massenbearbeitung
|
|
checkbox = QCheckBox()
|
|
self.table.setCellWidget(row, 0, checkbox)
|
|
|
|
# Titel
|
|
title = anime.find("series_title").text.replace("<![CDATA[", "").replace("]]>", "")
|
|
title_item = QTableWidgetItem(title)
|
|
title_item.setFlags(title_item.flags() & ~Qt.ItemIsEditable) # Mache nicht editierbar
|
|
self.table.setItem(row, 1, title_item)
|
|
|
|
# Typ
|
|
type_item = QTableWidgetItem(anime.find("series_type").text)
|
|
type_item.setFlags(type_item.flags() & ~Qt.ItemIsEditable)
|
|
self.table.setItem(row, 2, type_item)
|
|
|
|
# Episoden
|
|
episodes = QTableWidgetItem(anime.find("series_episodes").text)
|
|
episodes.setFlags(episodes.flags() & ~Qt.ItemIsEditable)
|
|
self.table.setItem(row, 3, episodes)
|
|
|
|
# Status
|
|
status_combo = QComboBox()
|
|
status_combo.addItems(["Watching", "Completed", "On-Hold", "Dropped", "Plan to Watch"])
|
|
status_combo.setCurrentText(anime.find("my_status").text)
|
|
self.table.setCellWidget(row, 4, status_combo)
|
|
|
|
# Gesehene Episoden
|
|
watched_spin = QSpinBox()
|
|
watched_spin.setRange(0, int(anime.find("series_episodes").text))
|
|
watched_spin.setValue(int(anime.find("my_watched_episodes").text))
|
|
self.table.setCellWidget(row, 5, watched_spin)
|
|
|
|
# Bewertung
|
|
score_combo = QComboBox()
|
|
score_combo.addItems([str(i) for i in range(11)]) # 0-10
|
|
score_combo.setCurrentText(anime.find("my_score").text)
|
|
self.table.setCellWidget(row, 6, score_combo)
|
|
|
|
# Löschen Button
|
|
delete_button = QPushButton("Löschen")
|
|
delete_button.clicked.connect(lambda checked, row=row: self.delete_entry(row))
|
|
self.table.setCellWidget(row, 7, delete_button)
|
|
|
|
# Aktiviere die Sortierung wieder
|
|
self.table.setSortingEnabled(True)
|
|
|
|
# Setze die Scrollbar auf den Anfang
|
|
self.table.verticalScrollBar().setValue(0)
|
|
|
|
def delete_entry(self, row):
|
|
reply = QMessageBox.question(
|
|
self, 'Bestätigung',
|
|
'Möchten Sie diesen Eintrag wirklich löschen?',
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
self.table.removeRow(row)
|
|
|
|
def save_changes(self):
|
|
if not self.current_file or not self.root:
|
|
return
|
|
|
|
self.save_to_file(self.current_file)
|
|
|
|
def save_as(self):
|
|
if not self.root:
|
|
return
|
|
|
|
file_name, _ = QFileDialog.getSaveFileName(
|
|
self,
|
|
"Speichern unter",
|
|
"",
|
|
"XML Dateien (*.xml);;Alle Dateien (*.*)"
|
|
)
|
|
|
|
if file_name:
|
|
self.current_file = file_name
|
|
self.save_to_file(file_name)
|
|
self.setWindowTitle(f"MyAnimeList Editor - {os.path.basename(file_name)}")
|
|
|
|
def save_to_file(self, file_path):
|
|
# Entferne alle bestehenden Anime-Einträge
|
|
for anime in self.root.findall(".//anime"):
|
|
self.root.remove(anime)
|
|
|
|
# Füge die aktualisierten Einträge hinzu
|
|
for row in range(self.table.rowCount()):
|
|
anime = ET.SubElement(self.root, "anime")
|
|
|
|
# Titel
|
|
title = ET.SubElement(anime, "series_title")
|
|
title.text = f"<![CDATA[{self.table.item(row, 1).text()}]]>"
|
|
|
|
# Typ
|
|
type_elem = ET.SubElement(anime, "series_type")
|
|
type_elem.text = self.table.item(row, 2).text()
|
|
|
|
# Episoden
|
|
episodes = ET.SubElement(anime, "series_episodes")
|
|
episodes.text = self.table.item(row, 3).text()
|
|
|
|
# Status
|
|
status = ET.SubElement(anime, "my_status")
|
|
status.text = self.table.cellWidget(row, 4).currentText()
|
|
|
|
# Gesehene Episoden
|
|
watched = ET.SubElement(anime, "my_watched_episodes")
|
|
watched.text = str(self.table.cellWidget(row, 5).value())
|
|
|
|
# Bewertung
|
|
score = ET.SubElement(anime, "my_score")
|
|
score.text = self.table.cellWidget(row, 6).currentText()
|
|
|
|
# Füge weitere benötigte Felder hinzu
|
|
ET.SubElement(anime, "series_animedb_id").text = "0"
|
|
ET.SubElement(anime, "my_id").text = "0"
|
|
ET.SubElement(anime, "my_start_date").text = ""
|
|
ET.SubElement(anime, "my_finish_date").text = ""
|
|
ET.SubElement(anime, "my_rated").text = ""
|
|
ET.SubElement(anime, "my_storage").text = ""
|
|
ET.SubElement(anime, "my_storage_value").text = "0.00"
|
|
ET.SubElement(anime, "my_comments").text = "<![CDATA[]]>"
|
|
ET.SubElement(anime, "my_times_watched").text = "0"
|
|
ET.SubElement(anime, "my_rewatch_value").text = ""
|
|
ET.SubElement(anime, "my_priority").text = "LOW"
|
|
ET.SubElement(anime, "my_tags").text = "<![CDATA[]]>"
|
|
ET.SubElement(anime, "my_rewatching").text = "0"
|
|
ET.SubElement(anime, "my_rewatching_ep").text = "0"
|
|
ET.SubElement(anime, "my_discuss").text = "1"
|
|
ET.SubElement(anime, "my_sns").text = "default"
|
|
ET.SubElement(anime, "update_on_import").text = "0"
|
|
|
|
try:
|
|
# Speichere die Änderungen
|
|
self.tree.write(file_path, encoding='UTF-8', xml_declaration=True)
|
|
QMessageBox.information(self, "Erfolg", "Die Änderungen wurden gespeichert!")
|
|
except Exception as e:
|
|
QMessageBox.critical(
|
|
self,
|
|
"Fehler",
|
|
f"Fehler beim Speichern der Datei: {str(e)}"
|
|
)
|
|
|
|
if __name__ == '__main__':
|
|
app = QApplication(sys.argv)
|
|
window = AnimeListEditor()
|
|
window.show()
|
|
sys.exit(app.exec_()) |