diff --git a/src/main.py b/src/main.py index fb55572..0c2599c 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,5 @@ import sys +import logging from PySide6.QtWidgets import QApplication @@ -9,6 +10,13 @@ from conf import app_settings def main(): """Haupteinstiegspunkt der Anwendung.""" + # Logging konfigurieren + logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%H:%M:%S' + ) + # QApplication-Instanz erstellen app = QApplication(sys.argv) diff --git a/src/ui/MainWindow.py b/src/ui/MainWindow.py index a499b2f..2533257 100644 --- a/src/ui/MainWindow.py +++ b/src/ui/MainWindow.py @@ -989,6 +989,18 @@ class MainWindow(QMainWindow): menu.addSeparator() + # Aktion "Alle Änderungen übernehmen" (nur aktiv wenn Diff-PDFs vorhanden) + diff_pdfs = self._collect_all_diff_pdfs_under_node(tree_node_obj, item) if tree_node_obj else [] + has_diff_pdfs = len(diff_pdfs) > 0 + + action_accept_all = QAction("Alle Änderungen übernehmen", self) + action_accept_all.setIcon(QIcon(QIcon.fromTheme("emblem-default"))) + action_accept_all.triggered.connect(lambda: self._accept_all_changes_under_node(item)) + action_accept_all.setEnabled(has_diff_pdfs) + menu.addAction(action_accept_all) + + menu.addSeparator() + action_edit = QAction("Bearbeiten", self) action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties))) action_edit.triggered.connect(lambda: self._edit_tree_node(item)) @@ -1026,6 +1038,18 @@ class MainWindow(QMainWindow): menu.addSeparator() + # Aktion "Alle Änderungen übernehmen" (nur aktiv wenn Diff-PDFs vorhanden) + diff_pdfs = self._collect_all_diff_pdfs_under_node(xsl_file_obj, item) if xsl_file_obj else [] + has_diff_pdfs = len(diff_pdfs) > 0 + + action_accept_all = QAction("Alle Änderungen übernehmen", self) + action_accept_all.setIcon(QIcon(QIcon.fromTheme("emblem-default"))) + action_accept_all.triggered.connect(lambda: self._accept_all_changes_under_node(item)) + action_accept_all.setEnabled(has_diff_pdfs) + menu.addAction(action_accept_all) + + menu.addSeparator() + action_edit = QAction("Bearbeiten", self) action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties))) action_edit.triggered.connect(lambda: self._edit_xsl_file(item)) @@ -3643,6 +3667,74 @@ class MainWindow(QMainWindow): f"{successful_count} von {total_count} Transformationen erfolgreich\n{failed_count} fehlgeschlagen", ) + def _collect_all_diff_pdfs_under_node( + self, node_obj, item: QTreeWidgetItem + ) -> list[tuple[Path, str, Path, Path, Path]]: + """ + Sammelt alle Diff-PDFs unter einem TreeNode oder XslFile. + + Args: + node_obj: TreeNode oder XslFile Objekt + item: Das TreeWidgetItem des Knotens + + Returns: + list[tuple]: Liste von (xml_file_path, xsl_id_str, diff_pdf_path, ref_pdf_path, new_pdf_path) + """ + diff_pdfs = [] + + if not self.project: + return diff_pdfs + + diff_dir = self.project.project_dir / "diff" + ref_dir = self.project.project_dir / "ref" + new_dir = self.project.project_dir / "new" + + if not diff_dir.exists(): + return diff_pdfs + + if isinstance(node_obj, TreeNode): + # Sammle alle XSL/XML-Paare rekursiv + xsl_xml_pairs = self._collect_all_xsl_xml_pairs_recursive(node_obj, item) + + for xsl_file_obj, xml_file_obj, xsl_file_item in xsl_xml_pairs: + # Baue XML-Pfad auf + xml_file_path = self.project.project_dir / xml_file_obj.xml + + # Baue PDF-Namen + xml_stem = xml_file_path.stem + xsl_id_str = "_".join(str(x) for x in xsl_file_obj.id) if xsl_file_obj.id else "" + pdf_basename = f"{xml_stem}_xsl_{xsl_id_str}.pdf" + + diff_pdf_path = diff_dir / pdf_basename + ref_pdf_path = ref_dir / pdf_basename + new_pdf_path = new_dir / pdf_basename + + # Prüfe ob Diff-PDF existiert + if diff_pdf_path.exists(): + diff_pdfs.append((xml_file_path, xsl_id_str, diff_pdf_path, ref_pdf_path, new_pdf_path)) + + elif isinstance(node_obj, XslFile): + # Sammle alle XML-Dateien dieser XslFile + xsl_id_str = "_".join(str(x) for x in node_obj.id) if node_obj.id else "" + + for xml_file_obj in node_obj.xmls: + # Baue XML-Pfad auf + xml_file_path = self.project.project_dir / xml_file_obj.xml + + # Baue PDF-Namen + xml_stem = xml_file_path.stem + pdf_basename = f"{xml_stem}_xsl_{xsl_id_str}.pdf" + + diff_pdf_path = diff_dir / pdf_basename + ref_pdf_path = ref_dir / pdf_basename + new_pdf_path = new_dir / pdf_basename + + # Prüfe ob Diff-PDF existiert + if diff_pdf_path.exists(): + diff_pdfs.append((xml_file_path, xsl_id_str, diff_pdf_path, ref_pdf_path, new_pdf_path)) + + return diff_pdfs + def _find_next_diff_pdf(self) -> tuple[Path, str] | None: """ Findet die nächste Diff-PDF im Projekt. @@ -3680,6 +3772,138 @@ class MainWindow(QMainWindow): return None + def _accept_single_diff_pdf( + self, xml_file_path: Path, xsl_id_str: str, diff_pdf_path: Path, ref_pdf_path: Path, new_pdf_path: Path + ) -> bool: + """ + Akzeptiert eine einzelne Diff-PDF ohne Viewer-Update. + + Args: + xml_file_path: Pfad zur XML-Datei + xsl_id_str: XSL-ID als String + diff_pdf_path: Pfad zur Diff-PDF + ref_pdf_path: Pfad zur Ref-PDF + new_pdf_path: Pfad zur New-PDF + + Returns: + bool: True wenn erfolgreich, False bei Fehler + """ + try: + pdf_basename = diff_pdf_path.name + + # Prüfe ob new-PDF existiert + if not new_pdf_path.exists(): + logger.warning(f"New-PDF nicht gefunden: {pdf_basename}") + return False + + # Lösche alte ref-PDF falls vorhanden + if ref_pdf_path.exists(): + ref_pdf_path.unlink() + logger.debug(f"Alte Ref-PDF gelöscht: {ref_pdf_path}") + + # Verschiebe new-PDF nach ref + shutil.move(str(new_pdf_path), str(ref_pdf_path)) + logger.debug(f"New-PDF verschoben nach Ref: {new_pdf_path} -> {ref_pdf_path}") + + # Lösche diff-PDF + if diff_pdf_path.exists(): + diff_pdf_path.unlink() + logger.debug(f"Diff-PDF gelöscht: {diff_pdf_path}") + + # Diff-Icon beim XML-Knoten entfernen + # WICHTIG: xml_item_map verwendet relative Pfade, nicht absolute! + xml_relative_path = xml_file_path.relative_to(self.project.project_dir) + map_key = f"{xml_relative_path}|{xsl_id_str}" + if map_key in self.xml_item_map: + tree_item = self.xml_item_map[map_key] + # Entferne Icon-Widget aus Spalte 2 + tree_item.setData(2, Qt.ItemDataRole.UserRole, None) + self.ui.treeWidget.setItemWidget(tree_item, 2, None) + + logger.info(f"Änderungen akzeptiert für: {pdf_basename}") + return True + + except Exception as e: + logger.error(f"Fehler beim Akzeptieren von {pdf_basename}: {e}") + return False + + def _accept_all_changes_under_node(self, item: QTreeWidgetItem): + """ + Akzeptiert alle Diff-PDFs unter einem TreeNode oder XslFile. + Leert den Viewer, falls eine der akzeptierten PDFs gerade angezeigt wird. + + Args: + item: Das TreeWidgetItem des TreeNode oder XslFile + """ + try: + # Hole Node-Objekt + node_obj = item.data(0, Qt.ItemDataRole.UserRole) + if not node_obj: + logger.warning("Kein Node-Objekt gefunden") + return + + # Sammle alle Diff-PDFs unter diesem Knoten + diff_pdfs = self._collect_all_diff_pdfs_under_node(node_obj, item) + + if not diff_pdfs: + QMessageBox.information(self, "Info", "Keine Diff-PDFs unter diesem Knoten gefunden") + return + + # Frage Benutzer um Bestätigung + node_name = node_obj.bez if hasattr(node_obj, "bez") else "Unbekannt" + + reply = QMessageBox.question( + self, + "Alle Änderungen übernehmen", + f"Möchten Sie wirklich alle {len(diff_pdfs)} Änderungen unter '{node_name}' übernehmen?\n\n" + f"Dies verschiebt alle new-PDFs nach ref und löscht die diff-PDFs.", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + + if reply != QMessageBox.StandardButton.Yes: + return + + # Merke, ob eine der zu akzeptierenden PDFs gerade im Viewer angezeigt wird + viewer_needs_clearing = False + if self.current_diff_xml_path and self.current_diff_xsl_id: + for xml_file_path, xsl_id_str, diff_pdf_path, ref_pdf_path, new_pdf_path in diff_pdfs: + if xml_file_path == self.current_diff_xml_path and xsl_id_str == self.current_diff_xsl_id: + viewer_needs_clearing = True + break + + # Akzeptiere alle Diff-PDFs + successful_count = 0 + for xml_file_path, xsl_id_str, diff_pdf_path, ref_pdf_path, new_pdf_path in diff_pdfs: + if self._accept_single_diff_pdf(xml_file_path, xsl_id_str, diff_pdf_path, ref_pdf_path, new_pdf_path): + successful_count += 1 + + # Aktualisiere Diff-PDF-Anzahl auf übergeordneten Ebenen + self._update_all_diff_pdf_counts() + + # Leere Viewer falls nötig + if viewer_needs_clearing: + self._clear_pdf_viewer() + + # Zeige Erfolgsmeldung + if successful_count == len(diff_pdfs): + logger.info(f"Alle {successful_count} Änderungen erfolgreich übernommen") + QMessageBox.information( + self, "Erfolg", f"Alle {successful_count} Änderungen wurden erfolgreich übernommen" + ) + else: + failed_count = len(diff_pdfs) - successful_count + logger.warning(f"{successful_count}/{len(diff_pdfs)} Änderungen übernommen, {failed_count} fehlgeschlagen") + QMessageBox.warning( + self, + "Teilweise erfolgreich", + f"{successful_count} von {len(diff_pdfs)} Änderungen übernommen\n{failed_count} fehlgeschlagen", + ) + + except Exception as e: + logger.error(f"Fehler beim Übernehmen aller Änderungen: {e}") + QMessageBox.critical(self, "Fehler", f"Fehler beim Übernehmen der Änderungen:\n{str(e)}") + def _clear_pdf_viewer(self): """Leert den PDF-Viewer und alle Thumbnails.""" # Entferne Widgets aus Layouts