Feature: Löschen-Funktion für TreeNode-Knoten im Baum implementiert
Rekursive Löschung von TreeNodes mit PDF-Bereinigung, automatischer physischer Löschung nicht mehr verwendeter XML-Dateien und korrekter "anderswo verwendet"-Prüfung durch vorheriges Entfernen aus dem Datenmodell. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -794,10 +794,165 @@ class TreeManagerMixin:
|
|||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
QMessageBox.critical(self, "Fehler", 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):
|
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)}")
|
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
|
# Kontextmenü-Aktionen für XslFile
|
||||||
def _add_xml_file_to_xsl(self, parent_item):
|
def _add_xml_file_to_xsl(self, parent_item):
|
||||||
|
|||||||
Reference in New Issue
Block a user