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") logger.info(f"Drop-Event: {len(xml_files)} XML-Dateien erkannt")
# Verarbeite jede XML-Datei einzeln # Verarbeite alle XML-Dateien mit optionalem "Alle zuordnen" Feature
for xml_file_path in xml_files: self._handle_multiple_xml_files_drop(xml_files)
self._handle_xml_file_drop(xml_file_path)
event.acceptProposedAction() event.acceptProposedAction()
@@ -2439,9 +2438,171 @@ class MainWindow(QMainWindow):
QMessageBox.critical(self, "Fehler", error_msg) QMessageBox.critical(self, "Fehler", error_msg)
event.ignore() 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): def _handle_xml_file_drop(self, xml_file_path: Path):
""" """
Verarbeitet eine einzelne XML-Datei, die per Drag&Drop hinzugefügt wurde. Verarbeitet eine einzelne XML-Datei, die per Drag&Drop hinzugefügt wurde.
DEPRECATED: Diese Methode wird durch _handle_multiple_xml_files_drop ersetzt.
Args: Args:
xml_file_path: Pfad zur XML-Datei xml_file_path: Pfad zur XML-Datei
@@ -2489,6 +2650,9 @@ class MainWindow(QMainWindow):
Args: Args:
xml_file_path: Pfad zur XML-Datei xml_file_path: Pfad zur XML-Datei
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
Returns:
dict: Statistiken über die Verarbeitung
""" """
try: try:
logger.info(f"Ordne XML-Datei '{xml_file_path.name}' zu {len(selected_xsl_nodes)} XSL-Knoten zu") 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: if existing_xml:
# 3. Hash-Match gefunden: Ordne vorhandene XML-Datei zu # 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}") 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: else:
# 4. Kein Hash-Match: Verarbeite als neue XML-Datei # 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") 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: except Exception as e:
error_msg = f"Fehler beim Zuordnen der XML-Datei: {str(e)}" error_msg = f"Fehler beim Zuordnen der XML-Datei: {str(e)}"
logger.error(error_msg) logger.error(error_msg)
QMessageBox.critical(self, "Fehler", error_msg) return {"status": "error", "error_msg": error_msg}
def _start_xml_hash_calculation(self): def _start_xml_hash_calculation(self):
""" """
@@ -2848,6 +3012,9 @@ class MainWindow(QMainWindow):
Args: Args:
existing_xml: Die bereits vorhandene XML-Datei existing_xml: Die bereits vorhandene XML-Datei
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
Returns:
dict: Statistiken mit 'status', 'added_count', 'existing_file'
""" """
try: try:
added_count = 0 added_count = 0
@@ -2872,24 +3039,18 @@ class MainWindow(QMainWindow):
# Aktualisiere das TreeWidget # Aktualisiere das TreeWidget
self._load_nodes_to_tree() self._load_nodes_to_tree()
# Zeige Erfolgsmeldung return {
QMessageBox.information( "status": "existing_added",
self, "added_count": added_count,
"XML-Datei zugeordnet", "existing_file": existing_xml.xml.name,
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.",
)
else: else:
QMessageBox.information( return {"status": "already_assigned", "added_count": 0, "existing_file": existing_xml.xml.name}
self,
"Bereits zugeordnet",
"Die XML-Datei mit gleichem Inhalt ist bereits in allen ausgewählten XSL-Knoten vorhanden.",
)
except Exception as e: except Exception as e:
error_msg = f"Fehler beim Zuordnen der vorhandenen XML-Datei: {str(e)}" error_msg = f"Fehler beim Zuordnen der vorhandenen XML-Datei: {str(e)}"
logger.error(error_msg) 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): 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 xml_file_path: Pfad zur neuen XML-Datei
selected_xsl_nodes: Liste der ausgewählten XSL-Knoten selected_xsl_nodes: Liste der ausgewählten XSL-Knoten
file_hash: Berechneter Hash der Datei file_hash: Berechneter Hash der Datei
Returns:
dict: Statistiken mit 'status', 'added_count', 'new_file', 'renamed_from'
""" """
try: try:
# Prüfe ob Projekt verfügbar ist # Prüfe ob Projekt verfügbar ist
if not self.project or not self.project.project_dir: if not self.project or not self.project.project_dir:
logger.error("Kein Projekt-Verzeichnis für neue XML-Datei verfügbar") logger.error("Kein Projekt-Verzeichnis für neue XML-Datei verfügbar")
QMessageBox.critical(self, "Fehler", "Kein Projekt-Verzeichnis verfügbar.") return {"status": "error", "error_msg": "Kein Projekt-Verzeichnis verfügbar."}
return
# Erstelle xml-Ordner im Projekt-Verzeichnis falls er nicht existiert # Erstelle xml-Ordner im Projekt-Verzeichnis falls er nicht existiert
xml_dir = Path(self.project.project_dir) / "xml" xml_dir = Path(self.project.project_dir) / "xml"
@@ -2928,7 +3091,7 @@ class MainWindow(QMainWindow):
if not selected_path: if not selected_path:
# Benutzer hat abgebrochen # Benutzer hat abgebrochen
return return {"status": "cancelled", "added_count": 0}
target_xml_path = selected_path target_xml_path = selected_path
@@ -2965,27 +3128,23 @@ class MainWindow(QMainWindow):
# Aktualisiere das TreeWidget # Aktualisiere das TreeWidget
self._load_nodes_to_tree() self._load_nodes_to_tree()
# Zeige Erfolgsmeldung return {
success_msg = ( "status": "new_added",
f"XML-Datei '{target_xml_path.name}' wurde erfolgreich zu {added_count} XSL-Knoten hinzugefügt." "added_count": added_count,
) "new_file": target_xml_path.name,
if target_xml_path.name != xml_file_path.name: "renamed_from": xml_file_path.name if target_xml_path.name != xml_file_path.name else None,
success_msg += ( }
f"\n\nDie Datei wurde umbenannt von '{xml_file_path.name}' zu '{target_xml_path.name}'."
)
QMessageBox.information(self, "Erfolg", success_msg)
else: else:
QMessageBox.information( return {
self, "status": "already_assigned",
"Information", "added_count": 0,
f"XML-Datei '{target_xml_path.name}' war bereits in allen ausgewählten XSL-Knoten vorhanden.", "new_file": target_xml_path.name,
) }
except Exception as e: except Exception as e:
error_msg = f"Fehler beim Verarbeiten der neuen XML-Datei: {str(e)}" error_msg = f"Fehler beim Verarbeiten der neuen XML-Datei: {str(e)}"
logger.error(error_msg) 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: def _show_filename_selection_dialog(self, original_name: str, alternative_paths: List[Path]) -> Path | None:
""" """
+9
View File
@@ -286,6 +286,15 @@ class XmlToXslAssignDialog(QDialog):
""" """
return self.xml_file_path 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): def accept(self):
"""Überschreibt accept() um Validierung durchzuführen.""" """Überschreibt accept() um Validierung durchzuführen."""
selected_nodes = self.get_selected_xsl_nodes() selected_nodes = self.get_selected_xsl_nodes()
+7
View File
@@ -97,6 +97,13 @@
</property> </property>
</spacer> </spacer>
</item> </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> </layout>
</item> </item>
<item> <item>
+11 -5
View File
@@ -3,7 +3,7 @@
################################################################################ ################################################################################
## Form generated from reading UI file 'XmlToXslAssignDialog.ui' ## 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! ## 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, QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter, QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform) QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox, from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
QHBoxLayout, QHeaderView, QLabel, QPushButton, QDialogButtonBox, QHBoxLayout, QHeaderView, QLabel,
QSizePolicy, QSpacerItem, QTreeWidget, QTreeWidgetItem, QPushButton, QSizePolicy, QSpacerItem, QTreeWidget,
QVBoxLayout, QWidget) QTreeWidgetItem, QVBoxLayout, QWidget)
class Ui_XmlToXslAssignDialog(object): class Ui_XmlToXslAssignDialog(object):
def setupUi(self, XmlToXslAssignDialog): def setupUi(self, XmlToXslAssignDialog):
@@ -64,6 +64,11 @@ class Ui_XmlToXslAssignDialog(object):
self.buttonLayout.addItem(self.horizontalSpacer) 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) self.verticalLayout.addLayout(self.buttonLayout)
@@ -94,5 +99,6 @@ class Ui_XmlToXslAssignDialog(object):
___qtreewidgetitem.setText(0, QCoreApplication.translate("XmlToXslAssignDialog", u"XSL-Knoten", None)); ___qtreewidgetitem.setText(0, QCoreApplication.translate("XmlToXslAssignDialog", u"XSL-Knoten", None));
self.selectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle ausw\u00e4hlen", None)) self.selectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle ausw\u00e4hlen", None))
self.deselectAllButton.setText(QCoreApplication.translate("XmlToXslAssignDialog", u"Alle abw\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 # retranslateUi