UX-Verbesserung: Gesamtdauer in Transformations-Zusammenfassung

Erweitert die Zusammenfassung nach Abschluss aller Transformationen um die Gesamtdauer:
- TransformationThread misst jetzt die Gesamtdauer aller Jobs
- Signal all_jobs_finished erweitert um total_duration Parameter
- Statusbar und MessageBox zeigen Gesamtdauer an (Format: "12.34s")
- Dauer wird sowohl bei Erfolg als auch bei Fehlern angezeigt

🤖 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-28 12:58:39 +01:00
parent e7408eac7c
commit 9f48a0d62a
+43 -25
View File
@@ -388,7 +388,7 @@ class TransformationThread(QThread):
job_started = Signal(str, str) # xml_file_name, xsl_id_str job_started = Signal(str, str) # xml_file_name, xsl_id_str
job_finished = Signal(dict) # result_dict job_finished = Signal(dict) # result_dict
job_error = Signal(str, str, str) # xml_file_name, xsl_id_str, error_message job_error = Signal(str, str, str) # xml_file_name, xsl_id_str, error_message
all_jobs_finished = Signal(int, int) # successful_count, total_count all_jobs_finished = Signal(int, int, float) # successful_count, total_count, total_duration
def __init__(self, jobs: list[TransformationJob], force: bool = False): def __init__(self, jobs: list[TransformationJob], force: bool = False):
""" """
@@ -407,6 +407,9 @@ class TransformationThread(QThread):
""" """
Führt alle Transformations-Jobs sequenziell aus. Führt alle Transformations-Jobs sequenziell aus.
""" """
from datetime import datetime
start_time = datetime.now()
logger.info(f"Starte Transformation von {len(self.jobs)} Jobs") logger.info(f"Starte Transformation von {len(self.jobs)} Jobs")
for job in self.jobs: for job in self.jobs:
@@ -430,9 +433,14 @@ class TransformationThread(QThread):
xsl_id_str = "_".join(str(x) for x in job.xsl_id) if job.xsl_id else "" xsl_id_str = "_".join(str(x) for x in job.xsl_id) if job.xsl_id else ""
self.job_error.emit(str(job.xml_file), xsl_id_str, error_msg) self.job_error.emit(str(job.xml_file), xsl_id_str, error_msg)
# Sende Abschluss-Signal für alle Jobs # Berechne Gesamtdauer
self.all_jobs_finished.emit(self.successful_count, len(self.jobs)) total_duration = (datetime.now() - start_time).total_seconds()
logger.info(f"Transformation abgeschlossen: {self.successful_count}/{len(self.jobs)} erfolgreich")
# Sende Abschluss-Signal für alle Jobs mit Gesamtdauer
self.all_jobs_finished.emit(self.successful_count, len(self.jobs), total_duration)
logger.info(
f"Transformation abgeschlossen: {self.successful_count}/{len(self.jobs)} erfolgreich ({total_duration:.2f}s)"
)
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
@@ -954,7 +962,9 @@ class MainWindow(QMainWindow):
if not selected_items or not self.project: if not selected_items or not self.project:
# Keine Selektion oder kein Projekt - Viewer leeren # Keine Selektion oder kein Projekt - Viewer leeren
logger.debug(f"Keine Selektion oder kein Projekt: selected_items={len(selected_items) if selected_items else 0}, project={self.project is not None}") logger.debug(
f"Keine Selektion oder kein Projekt: selected_items={len(selected_items) if selected_items else 0}, project={self.project is not None}"
)
if self.pdf_documents: if self.pdf_documents:
self._clear_pdf_viewer() self._clear_pdf_viewer()
return return
@@ -1080,6 +1090,7 @@ class MainWindow(QMainWindow):
Returns: Returns:
QTreeWidgetItem oder None wenn nicht gefunden QTreeWidgetItem oder None wenn nicht gefunden
""" """
def search_recursive(item): def search_recursive(item):
"""Rekursive Suche durch TreeWidget.""" """Rekursive Suche durch TreeWidget."""
# Prüfe aktuelles Item # Prüfe aktuelles Item
@@ -2721,9 +2732,7 @@ class MainWindow(QMainWindow):
return return
# Zeige Dialog für die erste Datei # Zeige Dialog für die erste Datei
dialog = XmlToXslAssignDialog( dialog = XmlToXslAssignDialog(parent=self, xml_file_path=xml_files[0], project_nodes=self.pdf_project.nodes)
parent=self, xml_file_path=xml_files[0], project_nodes=self.pdf_project.nodes
)
if dialog.exec() != XmlToXslAssignDialog.DialogCode.Accepted: if dialog.exec() != XmlToXslAssignDialog.DialogCode.Accepted:
logger.debug("Dialog abgebrochen - keine Dateien verarbeitet") logger.debug("Dialog abgebrochen - keine Dateien verarbeitet")
@@ -3703,7 +3712,7 @@ class MainWindow(QMainWindow):
Returns: Returns:
bool: True wenn mindestens eine XML-Datei gefunden wurde bool: True wenn mindestens eine XML-Datei gefunden wurde
""" """
if not hasattr(node, 'children') or not node.children: if not hasattr(node, "children") or not node.children:
return False return False
for child in node.children: for child in node.children:
@@ -3731,7 +3740,7 @@ class MainWindow(QMainWindow):
""" """
pairs = [] pairs = []
if not hasattr(tree_node, 'children') or not tree_node.children: if not hasattr(tree_node, "children") or not tree_node.children:
return pairs return pairs
# Durchlaufe alle Kinder des TreeNode # Durchlaufe alle Kinder des TreeNode
@@ -3845,23 +3854,23 @@ class MainWindow(QMainWindow):
return None return None
# Zusätzliche Sicherheitsprüfung für path_to_binary_file Attribute # Zusätzliche Sicherheitsprüfung für path_to_binary_file Attribute
if java_vm is None or not hasattr(java_vm, 'path_to_binary_file') or java_vm.path_to_binary_file is None: if java_vm is None or not hasattr(java_vm, "path_to_binary_file") or java_vm.path_to_binary_file is None:
QMessageBox.warning(self, "Konfigurationsfehler", "Java VM Pfad ist nicht konfiguriert") QMessageBox.warning(self, "Konfigurationsfehler", "Java VM Pfad ist nicht konfiguriert")
return None return None
if saxon_jar is None or not hasattr(saxon_jar, 'path_to_jar_file') or saxon_jar.path_to_jar_file is None: if saxon_jar is None or not hasattr(saxon_jar, "path_to_jar_file") or saxon_jar.path_to_jar_file is None:
QMessageBox.warning(self, "Konfigurationsfehler", "Saxon JAR Pfad ist nicht konfiguriert") QMessageBox.warning(self, "Konfigurationsfehler", "Saxon JAR Pfad ist nicht konfiguriert")
return None return None
if apache_fop is None or not hasattr(apache_fop, 'path_to_dir') or apache_fop.path_to_dir is None: if apache_fop is None or not hasattr(apache_fop, "path_to_dir") or apache_fop.path_to_dir is None:
QMessageBox.warning(self, "Konfigurationsfehler", "Apache FOP Pfad ist nicht konfiguriert") QMessageBox.warning(self, "Konfigurationsfehler", "Apache FOP Pfad ist nicht konfiguriert")
return None return None
if diff_pdf is None or not hasattr(diff_pdf, 'path_to_binary_file') or diff_pdf.path_to_binary_file is None: if diff_pdf is None or not hasattr(diff_pdf, "path_to_binary_file") or diff_pdf.path_to_binary_file is None:
QMessageBox.warning(self, "Konfigurationsfehler", "diff-pdf Pfad ist nicht konfiguriert") QMessageBox.warning(self, "Konfigurationsfehler", "diff-pdf Pfad ist nicht konfiguriert")
return None return None
if xsl_dir is None or not hasattr(xsl_dir, 'path_to_root_dir') or xsl_dir.path_to_root_dir is None: if xsl_dir is None or not hasattr(xsl_dir, "path_to_root_dir") or xsl_dir.path_to_root_dir is None:
QMessageBox.warning(self, "Konfigurationsfehler", "XSL-Verzeichnis Pfad ist nicht konfiguriert") QMessageBox.warning(self, "Konfigurationsfehler", "XSL-Verzeichnis Pfad ist nicht konfiguriert")
return None return None
@@ -3885,9 +3894,7 @@ class MainWindow(QMainWindow):
# 2. Überschreibe mit XslFile-eigenen Parametern (höchste Priorität) # 2. Überschreibe mit XslFile-eigenen Parametern (höchste Priorität)
xslt_params.update(xsl_file_obj.xslt_params) xslt_params.update(xsl_file_obj.xslt_params)
logger.info( logger.info(f"Finale XSLT-Parameter für {xml_file_obj.xml} mit {xsl_file_obj.bez}: {xslt_params}")
f"Finale XSLT-Parameter für {xml_file_obj.xml} mit {xsl_file_obj.bez}: {xslt_params}"
)
# Erstelle TransformationJob # Erstelle TransformationJob
job = TransformationJob( job = TransformationJob(
@@ -4078,15 +4085,18 @@ class MainWindow(QMainWindow):
self.ui.treeWidget.removeItemWidget(tree_item, 2) self.ui.treeWidget.removeItemWidget(tree_item, 2)
logger.debug(f"Progress Bar für {map_key} entfernt (Fehler)") logger.debug(f"Progress Bar für {map_key} entfernt (Fehler)")
def _on_all_transformations_finished(self, successful_count: int, total_count: int): def _on_all_transformations_finished(self, successful_count: int, total_count: int, total_duration: float):
""" """
Signal-Handler: Alle Jobs wurden abgeschlossen. Signal-Handler: Alle Jobs wurden abgeschlossen.
Args: Args:
successful_count: Anzahl erfolgreicher Jobs successful_count: Anzahl erfolgreicher Jobs
total_count: Gesamtanzahl der Jobs total_count: Gesamtanzahl der Jobs
total_duration: Gesamtdauer aller Transformationen in Sekunden
""" """
logger.info(f"Alle Transformationen abgeschlossen: {successful_count}/{total_count} erfolgreich") logger.info(
f"Alle Transformationen abgeschlossen: {successful_count}/{total_count} erfolgreich ({total_duration:.2f}s)"
)
# Verstecke Transformation-Progressbar # Verstecke Transformation-Progressbar
self._hide_transformation_progress_bar() self._hide_transformation_progress_bar()
@@ -4095,20 +4105,25 @@ class MainWindow(QMainWindow):
self._update_all_diff_pdf_counts() self._update_all_diff_pdf_counts()
self._update_diff_icons_for_existing_pdfs() self._update_diff_icons_for_existing_pdfs()
# Formatiere Dauer für Anzeige
duration_str = f"{total_duration:.2f}s"
if successful_count == total_count: if successful_count == total_count:
self.statusBar().showMessage(f"✓ Alle {total_count} Transformationen erfolgreich", 5000) self.statusBar().showMessage(f"✓ Alle {total_count} Transformationen erfolgreich ({duration_str})", 5000)
QMessageBox.information( QMessageBox.information(
self, "Abgeschlossen", f"Alle {total_count} Transformationen wurden erfolgreich abgeschlossen" self,
"Abgeschlossen",
f"Alle {total_count} Transformationen wurden erfolgreich abgeschlossen.\n\nGesamtdauer: {duration_str}",
) )
else: else:
failed_count = total_count - successful_count failed_count = total_count - successful_count
self.statusBar().showMessage( self.statusBar().showMessage(
f"{successful_count}/{total_count} erfolgreich, {failed_count} fehlgeschlagen", 5000 f"{successful_count}/{total_count} erfolgreich, {failed_count} fehlgeschlagen ({duration_str})", 5000
) )
QMessageBox.warning( QMessageBox.warning(
self, self,
"Abgeschlossen mit Fehlern", "Abgeschlossen mit Fehlern",
f"{successful_count} von {total_count} Transformationen erfolgreich\n{failed_count} fehlgeschlagen", f"{successful_count} von {total_count} Transformationen erfolgreich\n{failed_count} fehlgeschlagen\n\nGesamtdauer: {duration_str}",
) )
def _collect_all_diff_pdfs_under_node( def _collect_all_diff_pdfs_under_node(
@@ -4341,7 +4356,9 @@ class MainWindow(QMainWindow):
) )
else: else:
failed_count = len(diff_pdfs) - successful_count failed_count = len(diff_pdfs) - successful_count
logger.warning(f"{successful_count}/{len(diff_pdfs)} Änderungen übernommen, {failed_count} fehlgeschlagen") logger.warning(
f"{successful_count}/{len(diff_pdfs)} Änderungen übernommen, {failed_count} fehlgeschlagen"
)
QMessageBox.warning( QMessageBox.warning(
self, self,
"Teilweise erfolgreich", "Teilweise erfolgreich",
@@ -4435,6 +4452,7 @@ class MainWindow(QMainWindow):
# Verarbeite alle pending Qt Events um sicherzustellen, dass Widgets/Ressourcen freigegeben werden # Verarbeite alle pending Qt Events um sicherzustellen, dass Widgets/Ressourcen freigegeben werden
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
QApplication.processEvents() QApplication.processEvents()
logger.info("PDF-Dokumente geschlossen und UI geleert vor Dateioperationen") logger.info("PDF-Dokumente geschlossen und UI geleert vor Dateioperationen")