UX-Verbesserung: Batch-Verarbeitung für Drag&Drop von XML-Dateien

- Checkbox 'Alle XML-Dateien zuordnen' im XmlToXslAssignDialog hinzugefügt
- Bei aktivierter Checkbox wird Dialog nur einmal angezeigt und Auswahl auf alle weiteren Dateien angewendet
- Einzelne Erfolgsdialoge durch einen zusammenfassenden Dialog ersetzt
- Zusammenfassungsdialog zeigt detaillierte Statistiken:
  • Anzahl neu hinzugefügter Dateien
  • Anzahl bereits vorhandener Dateien (Hash-Duplikate)
  • Anzahl bereits zugeordneter Dateien
  • Liste umbenannter Dateien
  • Fehlerberichte falls aufgetreten
- Deutliche Verbesserung der UX: Bei 30 Dateien nur 1 Dialog statt 30

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 20:01:46 +01:00
parent 4634b6e027
commit e4b2272e61
4 changed files with 225 additions and 44 deletions
+197 -38
View File
@@ -2427,9 +2427,8 @@ class MainWindow(QMainWindow):
logger.info(f"Drop-Event: {len(xml_files)} XML-Dateien erkannt")
# Verarbeite jede XML-Datei einzeln
for xml_file_path in xml_files:
self._handle_xml_file_drop(xml_file_path)
# Verarbeite alle XML-Dateien mit optionalem "Alle zuordnen" Feature
self._handle_multiple_xml_files_drop(xml_files)
event.acceptProposedAction()
@@ -2439,9 +2438,171 @@ class MainWindow(QMainWindow):
QMessageBox.critical(self, "Fehler", error_msg)
event.ignore()
def _handle_multiple_xml_files_drop(self, xml_files: list):
"""
Verarbeitet mehrere XML-Dateien, die per Drag&Drop hinzugefügt wurden.
Unterstützt das "Alle XML-Dateien zuordnen" Feature.
Args:
xml_files: Liste von Pfaden zu XML-Dateien
"""
if not xml_files:
return
try:
# Prüfe ob Projekt-Nodes verfügbar sind
if not self.pdf_project or not self.pdf_project.nodes:
QMessageBox.warning(self, "Warnung", "Keine Projekt-Nodes verfügbar für die Zuordnung.")
return
# Variablen für "Alle zuordnen" Feature
apply_to_all = False
cached_selected_nodes = None
# Statistiken für Zusammenfassung
stats = {
"total": len(xml_files),
"processed": 0,
"new_added": 0,
"existing_added": 0,
"already_assigned": 0,
"cancelled": 0,
"errors": 0,
"error_messages": [],
"renamed_files": [],
}
# Verarbeite jede XML-Datei
for i, xml_file_path in enumerate(xml_files):
logger.debug(f"Verarbeite XML-Datei {i+1}/{len(xml_files)}: {xml_file_path}")
# Prüfe ob die Datei existiert
if not xml_file_path.exists():
stats["errors"] += 1
stats["error_messages"].append(f"{xml_file_path.name}: Datei existiert nicht")
continue
# Wenn "Alle zuordnen" aktiv ist und wir bereits eine Auswahl haben
if apply_to_all and cached_selected_nodes:
# Verwende die gecachte Auswahl
result = self._assign_xml_to_xsl_nodes(xml_file_path, cached_selected_nodes)
self._update_stats(stats, result)
continue
# Zeige den Dialog für die erste Datei oder wenn "Alle zuordnen" nicht aktiv ist
dialog = XmlToXslAssignDialog(
parent=self, xml_file_path=xml_file_path, project_nodes=self.pdf_project.nodes
)
if dialog.exec() == XmlToXslAssignDialog.DialogCode.Accepted:
# Hole die ausgewählten XSL-Knoten
selected_xsl_nodes = dialog.get_selected_xsl_nodes()
if selected_xsl_nodes:
# Verarbeite die Zuordnung
result = self._assign_xml_to_xsl_nodes(xml_file_path, selected_xsl_nodes)
self._update_stats(stats, result)
# Prüfe ob "Alle zuordnen" aktiviert wurde
if dialog.is_apply_to_all() and i < len(xml_files) - 1:
# Es gibt noch weitere Dateien und User möchte alle zuordnen
apply_to_all = True
cached_selected_nodes = selected_xsl_nodes
logger.info(
f"'Alle zuordnen' aktiviert - {len(xml_files) - i - 1} weitere Datei(en) werden automatisch zugeordnet"
)
else:
logger.warning("Keine XSL-Knoten ausgewählt")
else:
# Dialog wurde abgebrochen - beende die Verarbeitung
stats["cancelled"] = len(xml_files) - i
logger.debug(f"Dialog abgebrochen - {stats['cancelled']} Datei(en) übersprungen")
break
# Zeige Zusammenfassungsdialog
self._show_drop_summary_dialog(stats)
except Exception as e:
error_msg = f"Fehler beim Verarbeiten der XML-Dateien: {str(e)}"
logger.error(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)
def _update_stats(self, stats: dict, result: dict):
"""
Aktualisiert die Statistiken basierend auf dem Verarbeitungsergebnis.
Args:
stats: Statistik-Dictionary
result: Ergebnis von _assign_xml_to_xsl_nodes
"""
stats["processed"] += 1
status = result.get("status")
if status == "new_added":
stats["new_added"] += 1
if result.get("renamed_from"):
stats["renamed_files"].append(f"{result['renamed_from']}{result['new_file']}")
elif status == "existing_added":
stats["existing_added"] += 1
elif status == "already_assigned":
stats["already_assigned"] += 1
elif status == "cancelled":
stats["cancelled"] += 1
elif status == "error":
stats["errors"] += 1
stats["error_messages"].append(result.get("error_msg", "Unbekannter Fehler"))
def _show_drop_summary_dialog(self, stats: dict):
"""
Zeigt einen Zusammenfassungsdialog über die verarbeiteten XML-Dateien.
Args:
stats: Statistik-Dictionary mit Verarbeitungsergebnissen
"""
# Erstelle Zusammenfassungstext
summary_lines = []
summary_lines.append(f"Verarbeitung abgeschlossen:\n")
summary_lines.append(f"📊 Gesamt: {stats['total']} Datei(en)")
summary_lines.append(f"✓ Verarbeitet: {stats['processed']} Datei(en)")
if stats["new_added"] > 0:
summary_lines.append(f" Neu hinzugefügt: {stats['new_added']} Datei(en)")
if stats["existing_added"] > 0:
summary_lines.append(f"🔗 Vorhandene zugeordnet: {stats['existing_added']} Datei(en)")
if stats["already_assigned"] > 0:
summary_lines.append(f"️ Bereits zugeordnet: {stats['already_assigned']} Datei(en)")
if stats["cancelled"] > 0:
summary_lines.append(f"🚫 Abgebrochen: {stats['cancelled']} Datei(en)")
if stats["renamed_files"]:
summary_lines.append(f"\n📝 Umbenannte Dateien:")
for renamed in stats["renamed_files"]:
summary_lines.append(f"{renamed}")
if stats["errors"] > 0:
summary_lines.append(f"\n❌ Fehler: {stats['errors']}")
for error_msg in stats["error_messages"][:5]: # Zeige max. 5 Fehler
summary_lines.append(f"{error_msg}")
if len(stats["error_messages"]) > 5:
summary_lines.append(f" ... und {len(stats['error_messages']) - 5} weitere Fehler")
summary_text = "\n".join(summary_lines)
# Wähle Icon basierend auf Erfolg
if stats["errors"] > 0:
QMessageBox.warning(self, "Verarbeitung mit Fehlern abgeschlossen", summary_text)
elif stats["cancelled"] > 0:
QMessageBox.information(self, "Verarbeitung abgebrochen", summary_text)
else:
QMessageBox.information(self, "Verarbeitung erfolgreich", summary_text)
def _handle_xml_file_drop(self, xml_file_path: Path):
"""
Verarbeitet eine einzelne XML-Datei, die per Drag&Drop hinzugefügt wurde.
DEPRECATED: Diese Methode wird durch _handle_multiple_xml_files_drop ersetzt.
Args:
xml_file_path: Pfad zur XML-Datei
@@ -2489,6 +2650,9 @@ class MainWindow(QMainWindow):
Args:
xml_file_path: Pfad zur XML-Datei
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
Returns:
dict: Statistiken über die Verarbeitung
"""
try:
logger.info(f"Ordne XML-Datei '{xml_file_path.name}' zu {len(selected_xsl_nodes)} XSL-Knoten zu")
@@ -2504,16 +2668,16 @@ class MainWindow(QMainWindow):
if existing_xml:
# 3. Hash-Match gefunden: Ordne vorhandene XML-Datei zu
logger.info(f"Hash-Duplikat gefunden: {existing_xml.xml} hat gleichen Hash wie {xml_file_path.name}")
self._assign_existing_xml_to_nodes(existing_xml, selected_xsl_nodes)
return self._assign_existing_xml_to_nodes(existing_xml, selected_xsl_nodes)
else:
# 4. Kein Hash-Match: Verarbeite als neue XML-Datei
logger.info(f"Keine Hash-Duplikate gefunden für {xml_file_path.name}, verarbeite als neue Datei")
self._process_new_xml_file(xml_file_path, selected_xsl_nodes, file_hash)
return self._process_new_xml_file(xml_file_path, selected_xsl_nodes, file_hash)
except Exception as e:
error_msg = f"Fehler beim Zuordnen der XML-Datei: {str(e)}"
logger.error(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)
return {"status": "error", "error_msg": error_msg}
def _start_xml_hash_calculation(self):
"""
@@ -2848,6 +3012,9 @@ class MainWindow(QMainWindow):
Args:
existing_xml: Die bereits vorhandene XML-Datei
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
Returns:
dict: Statistiken mit 'status', 'added_count', 'existing_file'
"""
try:
added_count = 0
@@ -2872,24 +3039,18 @@ class MainWindow(QMainWindow):
# Aktualisiere das TreeWidget
self._load_nodes_to_tree()
# Zeige Erfolgsmeldung
QMessageBox.information(
self,
"XML-Datei zugeordnet",
f"Eine XML-Datei mit gleichem Inhalt war bereits im Projekt vorhanden.\n\n"
f"Die vorhandene Datei '{existing_xml.xml.name}' wurde automatisch zu {added_count} XSL-Knoten zugeordnet.",
)
return {
"status": "existing_added",
"added_count": added_count,
"existing_file": existing_xml.xml.name,
}
else:
QMessageBox.information(
self,
"Bereits zugeordnet",
"Die XML-Datei mit gleichem Inhalt ist bereits in allen ausgewählten XSL-Knoten vorhanden.",
)
return {"status": "already_assigned", "added_count": 0, "existing_file": existing_xml.xml.name}
except Exception as e:
error_msg = f"Fehler beim Zuordnen der vorhandenen XML-Datei: {str(e)}"
logger.error(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)
return {"status": "error", "error_msg": error_msg}
def _process_new_xml_file(self, xml_file_path: Path, selected_xsl_nodes: list, file_hash: str | None):
"""
@@ -2899,13 +3060,15 @@ class MainWindow(QMainWindow):
xml_file_path: Pfad zur neuen XML-Datei
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
file_hash: Berechneter Hash der Datei
Returns:
dict: Statistiken mit 'status', 'added_count', 'new_file', 'renamed_from'
"""
try:
# Prüfe ob Projekt verfügbar ist
if not self.project or not self.project.project_dir:
logger.error("Kein Projekt-Verzeichnis für neue XML-Datei verfügbar")
QMessageBox.critical(self, "Fehler", "Kein Projekt-Verzeichnis verfügbar.")
return
return {"status": "error", "error_msg": "Kein Projekt-Verzeichnis verfügbar."}
# Erstelle xml-Ordner im Projekt-Verzeichnis falls er nicht existiert
xml_dir = Path(self.project.project_dir) / "xml"
@@ -2928,7 +3091,7 @@ class MainWindow(QMainWindow):
if not selected_path:
# Benutzer hat abgebrochen
return
return {"status": "cancelled", "added_count": 0}
target_xml_path = selected_path
@@ -2965,27 +3128,23 @@ class MainWindow(QMainWindow):
# Aktualisiere das TreeWidget
self._load_nodes_to_tree()
# Zeige Erfolgsmeldung
success_msg = (
f"XML-Datei '{target_xml_path.name}' wurde erfolgreich zu {added_count} XSL-Knoten hinzugefügt."
)
if target_xml_path.name != xml_file_path.name:
success_msg += (
f"\n\nDie Datei wurde umbenannt von '{xml_file_path.name}' zu '{target_xml_path.name}'."
)
QMessageBox.information(self, "Erfolg", success_msg)
return {
"status": "new_added",
"added_count": added_count,
"new_file": target_xml_path.name,
"renamed_from": xml_file_path.name if target_xml_path.name != xml_file_path.name else None,
}
else:
QMessageBox.information(
self,
"Information",
f"XML-Datei '{target_xml_path.name}' war bereits in allen ausgewählten XSL-Knoten vorhanden.",
)
return {
"status": "already_assigned",
"added_count": 0,
"new_file": target_xml_path.name,
}
except Exception as e:
error_msg = f"Fehler beim Verarbeiten der neuen XML-Datei: {str(e)}"
logger.error(error_msg)
QMessageBox.critical(self, "Fehler", error_msg)
return {"status": "error", "error_msg": error_msg}
def _show_filename_selection_dialog(self, original_name: str, alternative_paths: List[Path]) -> Path | None:
"""
+10 -1
View File
@@ -280,12 +280,21 @@ class XmlToXslAssignDialog(QDialog):
def get_xml_file_path(self):
"""
Gibt den Pfad zur XML-Datei zurück.
Returns:
Path: Pfad zur XML-Datei
"""
return self.xml_file_path
def is_apply_to_all(self):
"""
Prüft, ob die Checkbox 'Alle XML-Dateien' aktiviert ist.
Returns:
bool: True wenn die Checkbox aktiviert ist, sonst False
"""
return self.ui.alle_xml.isChecked()
def accept(self):
"""Überschreibt accept() um Validierung durchzuführen."""
selected_nodes = self.get_selected_xsl_nodes()
+7
View File
@@ -97,6 +97,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="alle_xml">
<property name="text">
<string>Alle XML-Dateien den ausgewählten XSL-Dateien zuordnen</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
+11 -5
View File
@@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'XmlToXslAssignDialog.ui'
##
## Created by: Qt User Interface Compiler version 6.9.1
## Created by: Qt User Interface Compiler version 6.9.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
@@ -15,10 +15,10 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
QHBoxLayout, QHeaderView, QLabel, QPushButton,
QSizePolicy, QSpacerItem, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
QDialogButtonBox, QHBoxLayout, QHeaderView, QLabel,
QPushButton, QSizePolicy, QSpacerItem, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget)
class Ui_XmlToXslAssignDialog(object):
def setupUi(self, XmlToXslAssignDialog):
@@ -64,6 +64,11 @@ class Ui_XmlToXslAssignDialog(object):
self.buttonLayout.addItem(self.horizontalSpacer)
self.alle_xml = QCheckBox(XmlToXslAssignDialog)
self.alle_xml.setObjectName(u"alle_xml")
self.buttonLayout.addWidget(self.alle_xml)
self.verticalLayout.addLayout(self.buttonLayout)
@@ -94,5 +99,6 @@ class Ui_XmlToXslAssignDialog(object):
___qtreewidgetitem.setText(0, QCoreApplication.translate("XmlToXslAssignDialog", u"XSL-Knoten", None));
self.selectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle ausw\u00e4hlen", None))
self.deselectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle abw\u00e4hlen", None))
self.alle_xml.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle XML-Dateien den ausgew\u00e4hlten XSL-Dateien zuordnen", None))
# retranslateUi