diff --git a/src/ui/mixins/tree_manager.py b/src/ui/mixins/tree_manager.py index 3a868dc..276670b 100644 --- a/src/ui/mixins/tree_manager.py +++ b/src/ui/mixins/tree_manager.py @@ -794,10 +794,165 @@ class TreeManagerMixin: logger.error(error_msg) QMessageBox.critical(self, "Fehler", error_msg) + def _collect_xsl_files_recursive(self, node): + """ + Sammelt rekursiv alle XslFile-Objekte unter einem TreeNode. + + Args: + node: Der TreeNode, dessen XslFiles gesammelt werden sollen + + Returns: + list[XslFile]: Liste aller XslFiles im Teilbaum + """ + xsl_files = [] + for child in node.children: + if isinstance(child, XslFile): + xsl_files.append(child) + elif isinstance(child, TreeNode): + xsl_files.extend(self._collect_xsl_files_recursive(child)) + return xsl_files + + def _count_sub_nodes_recursive(self, node): + """ + Zählt rekursiv alle untergeordneten TreeNodes. + + Args: + node: Der TreeNode, dessen Unterknoten gezählt werden sollen + + Returns: + int: Anzahl der untergeordneten TreeNodes + """ + count = 0 + for child in node.children: + if isinstance(child, TreeNode): + count += 1 + self._count_sub_nodes_recursive(child) + return count + def _delete_tree_node(self, item): - """Löscht einen TreeNode.""" + """ + Löscht einen TreeNode und alle untergeordneten Elemente. + + Die XSL-Dateien werden nur aus dem Baum entfernt, nicht physisch gelöscht. + Zugehörige PDF-Dateien werden automatisch bereinigt. + XML-Dateien, die nirgends mehr verwendet werden, können optional physisch gelöscht werden. + + Args: + item: Das TreeWidgetItem des TreeNodes + """ logger.debug(f"TreeNode löschen: {item.text(0)}") - # TODO: Bestätigungsdialog und Löschung implementieren + + try: + # Prüfe ob ein Projekt geladen ist + if not hasattr(self, "project") or not self.project: + QMessageBox.warning(self, "Warnung", "Kein Projekt geladen. Bitte öffnen Sie zuerst ein Projekt.") + return + + if not hasattr(self, "pdf_project") or not self.pdf_project: + QMessageBox.warning(self, "Warnung", "Keine Projekt-Einstellungen geladen.") + return + + # Hole das TreeNode-Objekt aus dem TreeWidgetItem + tree_node_obj = item.data(0, Qt.ItemDataRole.UserRole) + if not tree_node_obj or not isinstance(tree_node_obj, TreeNode): + QMessageBox.warning(self, "Warnung", "Kein gültiger Baumknoten gefunden.") + return + + # Sammle alle XslFiles im Teilbaum vor der Löschung + xsl_files = self._collect_xsl_files_recursive(tree_node_obj) + sub_node_count = self._count_sub_nodes_recursive(tree_node_obj) + + # Bestätigungsdialog + details = [] + if sub_node_count > 0: + details.append(f"{sub_node_count} Unterknoten") + if xsl_files: + details.append(f"{len(xsl_files)} XSL-Datei(en)") + detail_text = f"\n\nEnthaltene Elemente: {', '.join(details)}." if details else "" + + reply = QMessageBox.question( + self, + "Knoten löschen", + f"Möchten Sie den Knoten '{tree_node_obj.bez}' und alle untergeordneten " + f"Elemente aus dem Baum entfernen?\n\n" + f"XSL-Dateien werden nicht physisch gelöscht.{detail_text}", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + + if reply != QMessageBox.StandardButton.Yes: + logger.debug("Löschung abgebrochen") + return + + # Ermittle den Eltern-Kontext und entferne den TreeNode aus dem Datenmodell + # WICHTIG: Zuerst entfernen, damit _is_xml_xsl_combination_used_elsewhere() + # und _is_xml_file_used_elsewhere() den gelöschten Teilbaum nicht mehr sehen + parent_item = item.parent() + if parent_item: + parent_node = parent_item.data(0, Qt.ItemDataRole.UserRole) + if not parent_node or not isinstance(parent_node, TreeNode): + QMessageBox.warning(self, "Warnung", "Kein gültiger Eltern-Knoten gefunden.") + return + children_before = len(parent_node.children) + parent_node.children = [child for child in parent_node.children if child is not tree_node_obj] + if len(parent_node.children) == children_before: + QMessageBox.warning(self, "Warnung", "Knoten konnte nicht aus dem Eltern-Knoten entfernt werden.") + return + else: + # Top-Level-Knoten + nodes_before = len(self.pdf_project.nodes) + self.pdf_project.nodes = [node for node in self.pdf_project.nodes if node is not tree_node_obj] + if len(self.pdf_project.nodes) == nodes_before: + QMessageBox.warning(self, "Warnung", "Knoten konnte nicht aus dem Projekt entfernt werden.") + return + + # PDF-Bereinigung für alle gesammelten XslFiles + total_deleted_pdfs = 0 + for xsl_file_obj in xsl_files: + xsl_id = xsl_file_obj.id + for xml_file_obj in xsl_file_obj.xmls: + is_combination_used = self._is_xml_xsl_combination_used_elsewhere( + xml_file_obj.xml, xsl_id, xsl_file_obj + ) + if not is_combination_used: + deleted_pdfs = self._delete_pdf_files_for_xml_xsl_combination(xml_file_obj.xml, xsl_id) + total_deleted_pdfs += deleted_pdfs + + if total_deleted_pdfs > 0: + logger.info(f"{total_deleted_pdfs} PDF-Datei(en) für Knoten '{tree_node_obj.bez}' gelöscht") + + # Sammle XML-Dateien, die nirgends mehr verwendet werden + unused_xml_files = [] + seen_xml_paths = set() + for xsl_file_obj in xsl_files: + for xml_file_obj in xsl_file_obj.xmls: + xml_path_str = str(xml_file_obj.xml) + if xml_path_str in seen_xml_paths: + continue + seen_xml_paths.add(xml_path_str) + xml_file_path = Path(self.project.project_dir) / xml_file_obj.xml + if xml_file_path.exists(): + is_used = self._is_xml_file_used_elsewhere(xml_file_obj.xml, xsl_file_obj) + if not is_used: + unused_xml_files.append((xml_file_obj, xml_file_path)) + + # Nicht mehr verwendete XML-Dateien physisch löschen + for xml_file_obj, xml_file_path in unused_xml_files: + try: + xml_file_path.unlink() + logger.info(f"Physische XML-Datei gelöscht: {xml_file_path}") + except Exception as e: + logger.warning(f"Konnte XML-Datei nicht löschen: {xml_file_path} - {e}") + + # Speichere und aktualisiere + self._save_project_settings() + self._load_nodes_to_tree() + + logger.info(f"TreeNode '{tree_node_obj.bez}' erfolgreich entfernt") + + except Exception as e: + error_msg = f"Fehler beim Löschen des Knotens: {str(e)}" + logger.error(error_msg) + QMessageBox.critical(self, "Fehler", error_msg) # Kontextmenü-Aktionen für XslFile def _add_xml_file_to_xsl(self, parent_item):