2026-01-15 18:23:55 +01:00
|
|
|
"""
|
|
|
|
|
TreeManagerMixin - Mixin für Tree-Widget-Operationen.
|
|
|
|
|
|
|
|
|
|
Dieses Mixin enthält alle Methoden für die Verwaltung des TreeWidgets im MainWindow:
|
|
|
|
|
- Setup und Styling
|
|
|
|
|
- Kontextmenüs
|
|
|
|
|
- Node-Operationen (Hinzufügen, Bearbeiten, Löschen)
|
|
|
|
|
- Tree-Navigation und -Suche
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import shutil
|
|
|
|
|
import time
|
2026-03-09 20:08:23 +01:00
|
|
|
from enum import Enum, auto
|
2026-01-15 18:23:55 +01:00
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from PySide6.QtCore import Qt
|
|
|
|
|
from PySide6.QtGui import QAction, QIcon
|
|
|
|
|
from PySide6.QtWidgets import (
|
|
|
|
|
QMenu,
|
|
|
|
|
QTreeWidgetItem,
|
|
|
|
|
QMessageBox,
|
|
|
|
|
QFileDialog,
|
|
|
|
|
QWidget,
|
|
|
|
|
QHBoxLayout,
|
|
|
|
|
QLabel,
|
|
|
|
|
QProgressBar,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from conf import TreeNode, XslFile, XmlFile
|
|
|
|
|
from ui.TreeNodeEditDialog import TreeNodeEditDialog
|
|
|
|
|
from ui.XslFileEditDialog import XslFileEditDialog
|
|
|
|
|
|
|
|
|
|
|
2026-03-09 20:08:23 +01:00
|
|
|
class ItemType(Enum):
|
|
|
|
|
TREE_NODE = auto()
|
|
|
|
|
XSL_FILE = auto()
|
|
|
|
|
XML_FILE = auto()
|
|
|
|
|
UNKNOWN = auto()
|
|
|
|
|
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TreeManagerMixin:
|
|
|
|
|
"""
|
|
|
|
|
Mixin-Klasse für Tree-Widget-Operationen.
|
|
|
|
|
|
|
|
|
|
Dieses Mixin erwartet, dass die verwendende Klasse folgende Attribute hat:
|
|
|
|
|
- self.ui: UI-Objekt mit treeWidget
|
|
|
|
|
- self.project: Aktuelles Projekt
|
|
|
|
|
- self.pdf_project: Projekt-Daten
|
|
|
|
|
- self.xml_item_map: Dict für XML-Item-Mapping
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def _setup_tree_context_menu(self):
|
|
|
|
|
"""Richtet das Kontextmenü für das TreeWidget ein."""
|
|
|
|
|
# Aktiviere Kontextmenü für das TreeWidget
|
|
|
|
|
self.ui.treeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
|
|
|
self.ui.treeWidget.customContextMenuRequested.connect(self._show_tree_context_menu)
|
|
|
|
|
|
|
|
|
|
# Verbinde Selection-Changed-Signal für automatisches Laden von Diff-PDFs
|
|
|
|
|
self.ui.treeWidget.itemSelectionChanged.connect(self._on_tree_selection_changed)
|
|
|
|
|
|
|
|
|
|
logger.debug("Kontextmenü und Selection-Handler für TreeWidget eingerichtet")
|
|
|
|
|
|
|
|
|
|
def _show_tree_context_menu(self, position):
|
|
|
|
|
"""
|
|
|
|
|
Zeigt das Kontextmenü für das TreeWidget an.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
position: Position des Rechtsklicks
|
|
|
|
|
"""
|
|
|
|
|
# Hole das Item an der Position
|
|
|
|
|
item = self.ui.treeWidget.itemAt(position)
|
|
|
|
|
|
|
|
|
|
if not item:
|
|
|
|
|
# Kein Item gefunden - zeige Kontextmenü für Root-Elemente
|
2026-03-09 20:08:23 +01:00
|
|
|
node_type = ItemType.UNKNOWN
|
2026-01-15 18:23:55 +01:00
|
|
|
context_menu = self._create_context_menu_for_type(node_type, None)
|
|
|
|
|
else:
|
|
|
|
|
# Bestimme den Node-Typ basierend auf dem Item
|
|
|
|
|
node_type = self._get_node_type_from_item(item)
|
|
|
|
|
# Erstelle das entsprechende Kontextmenü
|
|
|
|
|
context_menu = self._create_context_menu_for_type(node_type, item)
|
|
|
|
|
|
|
|
|
|
if context_menu:
|
|
|
|
|
# Zeige das Kontextmenü an der globalen Position
|
|
|
|
|
global_pos = self.ui.treeWidget.mapToGlobal(position)
|
|
|
|
|
context_menu.exec(global_pos)
|
|
|
|
|
|
|
|
|
|
def _on_tree_selection_changed(self):
|
|
|
|
|
"""
|
|
|
|
|
Handler für Änderungen der Tree-Selektion.
|
|
|
|
|
Lädt automatisch Diff-PDFs wenn ein XML-Knoten mit Diff-PDF ausgewählt wird.
|
|
|
|
|
Leert den Viewer wenn ein Knoten ohne Diff-PDF ausgewählt wird.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
logger.debug("Tree-Selektion geändert")
|
|
|
|
|
|
|
|
|
|
# Hole aktuell selektierte Items
|
|
|
|
|
selected_items = self.ui.treeWidget.selectedItems()
|
|
|
|
|
|
|
|
|
|
if not selected_items or not self.project:
|
|
|
|
|
# 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}"
|
|
|
|
|
)
|
|
|
|
|
if self.pdf_documents:
|
|
|
|
|
self._clear_pdf_viewer()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Erstes selektiertes Item verwenden
|
|
|
|
|
item = selected_items[0]
|
|
|
|
|
|
|
|
|
|
# Prüfe ob es ein XML-Item ist
|
|
|
|
|
node_type = self._get_node_type_from_item(item)
|
|
|
|
|
logger.debug(f"Selektierter Node-Typ: {node_type}")
|
|
|
|
|
|
2026-03-09 20:08:23 +01:00
|
|
|
if node_type == ItemType.XML_FILE:
|
2026-01-15 18:23:55 +01:00
|
|
|
# Hole XmlFile-Objekt und XSL-ID aus UserRole
|
|
|
|
|
xml_file_obj = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
xsl_id_str = item.data(1, Qt.ItemDataRole.UserRole)
|
|
|
|
|
|
|
|
|
|
logger.debug(f"XML-File-Daten: xml_file_obj={xml_file_obj}, xsl_id_str={xsl_id_str}")
|
|
|
|
|
|
|
|
|
|
if xml_file_obj and xsl_id_str:
|
|
|
|
|
# Extrahiere Pfad aus XmlFile-Objekt
|
|
|
|
|
xml_file_path = xml_file_obj.xml
|
|
|
|
|
|
|
|
|
|
# Prüfe ob Diff-PDF existiert
|
|
|
|
|
xml_stem = xml_file_path.stem
|
|
|
|
|
pdf_basename = f"{xml_stem}_xsl_{xsl_id_str}.pdf"
|
|
|
|
|
diff_pdf_path = self.project.project_dir / "diff" / pdf_basename
|
|
|
|
|
|
|
|
|
|
logger.debug(f"Prüfe Diff-PDF: {diff_pdf_path}, existiert={diff_pdf_path.exists()}")
|
|
|
|
|
|
|
|
|
|
if diff_pdf_path.exists():
|
|
|
|
|
# Diff-PDF vorhanden - automatisch laden
|
|
|
|
|
logger.info(f"XML-Knoten mit Diff-PDF ausgewählt: {pdf_basename}, lade automatisch")
|
|
|
|
|
self._load_pdf_for_comparison(xml_file_path, xsl_id_str)
|
|
|
|
|
else:
|
|
|
|
|
# Kein Diff-PDF - Viewer leeren falls noch ein PDF geladen ist
|
|
|
|
|
if self.pdf_documents:
|
|
|
|
|
logger.debug("XML-Knoten ohne Diff-PDF ausgewählt, leere Viewer")
|
|
|
|
|
self._clear_pdf_viewer()
|
|
|
|
|
else:
|
|
|
|
|
logger.debug("XML-File-Daten fehlen (xml_file_obj oder xsl_id_str ist None)")
|
|
|
|
|
else:
|
|
|
|
|
# Kein XML-Item - Viewer leeren falls noch ein PDF geladen ist
|
|
|
|
|
if self.pdf_documents:
|
|
|
|
|
logger.debug(f"Nicht-XML-Knoten ausgewählt ({node_type}), leere Viewer")
|
|
|
|
|
self._clear_pdf_viewer()
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Verarbeiten der Tree-Selektion: {e}", exc_info=True)
|
|
|
|
|
|
|
|
|
|
def _get_node_type_from_item(self, item):
|
|
|
|
|
"""
|
|
|
|
|
Bestimmt den Node-Typ basierend auf dem TreeWidgetItem.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das TreeWidgetItem
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
str: Der Node-Typ ('TreeNode', 'XslFile', 'XmlFile' oder 'Unknown')
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Prüfe ob das Item ein Parent hat (dann ist es ein Child-Item)
|
|
|
|
|
parent_item = item.parent()
|
|
|
|
|
|
|
|
|
|
if parent_item:
|
|
|
|
|
# Child-Item - prüfe ob es ein XML-File ist
|
|
|
|
|
text = item.text(0)
|
|
|
|
|
if text.startswith("XML:"):
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.XML_FILE
|
2026-01-15 18:23:55 +01:00
|
|
|
else:
|
|
|
|
|
# Könnte ein TreeNode-Child oder XslFile-Child sein
|
|
|
|
|
# Prüfe den Parent-Typ
|
|
|
|
|
parent_type = self._get_node_type_from_item(parent_item)
|
2026-03-09 20:08:23 +01:00
|
|
|
if parent_type == ItemType.XSL_FILE:
|
|
|
|
|
return ItemType.XML_FILE
|
2026-01-15 18:23:55 +01:00
|
|
|
else:
|
|
|
|
|
# Rekursiv bestimmen basierend auf gespeicherten Daten
|
|
|
|
|
return self._determine_node_type_from_data(item)
|
|
|
|
|
else:
|
|
|
|
|
# Root-Item - bestimme Typ basierend auf gespeicherten Daten
|
|
|
|
|
return self._determine_node_type_from_data(item)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Bestimmen des Node-Typs: {e}")
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.UNKNOWN
|
2026-01-15 18:23:55 +01:00
|
|
|
|
|
|
|
|
def _determine_node_type_from_data(self, item):
|
|
|
|
|
"""
|
|
|
|
|
Bestimmt den Node-Typ basierend auf den gespeicherten Daten im Item.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das TreeWidgetItem
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
str: Der Node-Typ ('TreeNode', 'XslFile' oder 'Unknown')
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Hole das gespeicherte Node-Objekt direkt
|
|
|
|
|
node = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not node:
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.UNKNOWN
|
2026-01-15 18:23:55 +01:00
|
|
|
|
|
|
|
|
# Bestimme den Typ direkt vom Node-Objekt
|
|
|
|
|
if isinstance(node, TreeNode):
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.TREE_NODE
|
2026-01-15 18:23:55 +01:00
|
|
|
elif isinstance(node, XslFile):
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.XSL_FILE
|
2026-01-15 18:23:55 +01:00
|
|
|
elif isinstance(node, XmlFile):
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.XML_FILE
|
2026-01-15 18:23:55 +01:00
|
|
|
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.UNKNOWN
|
2026-01-15 18:23:55 +01:00
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Bestimmen des Node-Typs aus Daten: {e}")
|
2026-03-09 20:08:23 +01:00
|
|
|
return ItemType.UNKNOWN
|
2026-01-15 18:23:55 +01:00
|
|
|
|
|
|
|
|
def _find_item_by_node(self, node_obj):
|
|
|
|
|
"""
|
|
|
|
|
Findet ein TreeWidgetItem basierend auf einem Node-Objekt.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
node_obj: Das Node-Objekt (TreeNode, XslFile oder XmlFile)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
QTreeWidgetItem oder None wenn nicht gefunden
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def search_recursive(item):
|
|
|
|
|
"""Rekursive Suche durch TreeWidget."""
|
|
|
|
|
# Prüfe aktuelles Item
|
|
|
|
|
item_node = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if item_node is node_obj:
|
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
# Durchsuche Kinder
|
|
|
|
|
for i in range(item.childCount()):
|
|
|
|
|
child = item.child(i)
|
|
|
|
|
result = search_recursive(child)
|
|
|
|
|
if result:
|
|
|
|
|
return result
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# Durchsuche alle Root-Items
|
|
|
|
|
for i in range(self.ui.treeWidget.topLevelItemCount()):
|
|
|
|
|
root_item = self.ui.treeWidget.topLevelItem(i)
|
|
|
|
|
result = search_recursive(root_item)
|
|
|
|
|
if result:
|
|
|
|
|
return result
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def _find_node_by_id(self, nodes, target_id):
|
|
|
|
|
"""
|
|
|
|
|
Sucht rekursiv nach einem Node mit der angegebenen ID.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
nodes: Liste der Nodes zum Durchsuchen
|
|
|
|
|
target_id: Die zu suchende ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
TreeNode|XslFile|None: Der gefundene Node oder None
|
|
|
|
|
"""
|
|
|
|
|
for node in nodes:
|
|
|
|
|
if node.id == target_id:
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
# Rekursiv in Knotenn suchen (nur bei TreeNode)
|
|
|
|
|
if isinstance(node, TreeNode) and node.children:
|
|
|
|
|
found = self._find_node_by_id(node.children, target_id)
|
|
|
|
|
if found:
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def _create_context_menu_for_type(self, node_type, item):
|
|
|
|
|
"""
|
|
|
|
|
Erstellt das Kontextmenü für den angegebenen Node-Typ.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
node_type: Der Typ des Nodes ('TreeNode', 'XslFile', 'XmlFile')
|
|
|
|
|
item: Das TreeWidgetItem
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
QMenu: Das erstellte Kontextmenü oder None
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
menu = QMenu(self)
|
|
|
|
|
|
2026-03-09 20:08:23 +01:00
|
|
|
if node_type == ItemType.TREE_NODE:
|
2026-01-15 18:23:55 +01:00
|
|
|
# Kontextmenü für TreeNode
|
|
|
|
|
action_add_child = QAction("Unterknoten hinzufügen", self)
|
|
|
|
|
action_add_child.setIcon(QIcon(QIcon.fromTheme("folder-new")))
|
|
|
|
|
action_add_child.triggered.connect(lambda: self._add_tree_node_child(item))
|
|
|
|
|
menu.addAction(action_add_child)
|
|
|
|
|
|
|
|
|
|
action_add_xsl = QAction("XSL-Datei hinzufügen", self)
|
|
|
|
|
action_add_xsl.setIcon(QIcon(QIcon.fromTheme("document-new")))
|
|
|
|
|
action_add_xsl.triggered.connect(lambda: self._add_xsl_file_to_node(item))
|
|
|
|
|
menu.addAction(action_add_xsl)
|
|
|
|
|
|
|
|
|
|
menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
# Transformations-Aktionen (nur aktiv wenn XML-Dateien vorhanden)
|
|
|
|
|
tree_node_obj = item.data(0, Qt.ItemDataRole.UserRole) if item else None
|
|
|
|
|
has_xml_files = bool(tree_node_obj and self._has_xml_files_recursive(tree_node_obj))
|
|
|
|
|
|
|
|
|
|
action_transform = QAction("Alle XML-Dateien transformieren", self)
|
|
|
|
|
action_transform.setIcon(QIcon(QIcon.fromTheme("system-run")))
|
|
|
|
|
action_transform.triggered.connect(lambda: self._transform_tree_node(item))
|
|
|
|
|
action_transform.setEnabled(has_xml_files)
|
|
|
|
|
menu.addAction(action_transform)
|
|
|
|
|
|
|
|
|
|
action_transform_force = QAction("Alle XML-Dateien neu transformieren (force)", self)
|
|
|
|
|
action_transform_force.setIcon(QIcon(QIcon.fromTheme("view-refresh")))
|
|
|
|
|
action_transform_force.triggered.connect(lambda: self._transform_tree_node(item, force=True))
|
|
|
|
|
action_transform_force.setEnabled(has_xml_files)
|
|
|
|
|
menu.addAction(action_transform_force)
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
menu.addAction(action_edit)
|
|
|
|
|
|
|
|
|
|
action_delete = QAction("Löschen", self)
|
|
|
|
|
action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete)))
|
|
|
|
|
action_delete.triggered.connect(lambda: self._delete_tree_node(item))
|
|
|
|
|
menu.addAction(action_delete)
|
|
|
|
|
|
2026-03-09 20:08:23 +01:00
|
|
|
elif node_type == ItemType.XSL_FILE:
|
2026-01-15 18:23:55 +01:00
|
|
|
# Kontextmenü für XslFile
|
|
|
|
|
action_add_xml = QAction("XML-Datei hinzufügen", self)
|
|
|
|
|
action_add_xml.setIcon(QIcon(QIcon.fromTheme("document-new")))
|
|
|
|
|
action_add_xml.triggered.connect(lambda: self._add_xml_file_to_xsl(item))
|
|
|
|
|
menu.addAction(action_add_xml)
|
|
|
|
|
|
|
|
|
|
menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
# Transformations-Aktionen (nur aktiv wenn XML-Dateien vorhanden)
|
|
|
|
|
xsl_file_obj = item.data(0, Qt.ItemDataRole.UserRole) if item else None
|
|
|
|
|
has_xml_files = bool(xsl_file_obj and xsl_file_obj.xmls)
|
|
|
|
|
|
|
|
|
|
action_transform = QAction("Alle XML-Dateien transformieren", self)
|
|
|
|
|
action_transform.setIcon(QIcon(QIcon.fromTheme("system-run")))
|
|
|
|
|
action_transform.triggered.connect(lambda: self._transform_xsl_file(item))
|
|
|
|
|
action_transform.setEnabled(has_xml_files)
|
|
|
|
|
menu.addAction(action_transform)
|
|
|
|
|
|
|
|
|
|
action_transform_force = QAction("Alle XML-Dateien neu transformieren (force)", self)
|
|
|
|
|
action_transform_force.setIcon(QIcon(QIcon.fromTheme("view-refresh")))
|
|
|
|
|
action_transform_force.triggered.connect(lambda: self._transform_xsl_file(item, force=True))
|
|
|
|
|
action_transform_force.setEnabled(has_xml_files)
|
|
|
|
|
menu.addAction(action_transform_force)
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
menu.addAction(action_edit)
|
|
|
|
|
|
|
|
|
|
action_delete = QAction("Löschen", self)
|
|
|
|
|
action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete)))
|
|
|
|
|
action_delete.triggered.connect(lambda: self._delete_xsl_file(item))
|
|
|
|
|
menu.addAction(action_delete)
|
|
|
|
|
|
2026-03-09 20:08:23 +01:00
|
|
|
elif node_type == ItemType.XML_FILE:
|
2026-01-15 18:23:55 +01:00
|
|
|
# Kontextmenü für XmlFile
|
|
|
|
|
# Transformations-Aktionen
|
|
|
|
|
action_transform = QAction("Transformieren", self)
|
|
|
|
|
action_transform.setIcon(QIcon(QIcon.fromTheme("system-run")))
|
|
|
|
|
action_transform.triggered.connect(lambda: self._transform_xml_file(item))
|
|
|
|
|
menu.addAction(action_transform)
|
|
|
|
|
|
|
|
|
|
action_transform_force = QAction("Neu transformieren (force)", self)
|
|
|
|
|
action_transform_force.setIcon(QIcon(QIcon.fromTheme("view-refresh")))
|
|
|
|
|
action_transform_force.triggered.connect(lambda: self._transform_xml_file(item, force=True))
|
|
|
|
|
menu.addAction(action_transform_force)
|
|
|
|
|
|
|
|
|
|
menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
# Ref-PDF öffnen Aktion (nur enabled wenn Ref-PDF existiert und keine Diff-PDF)
|
|
|
|
|
xml_file_obj = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
parent_item = item.parent()
|
|
|
|
|
ref_pdf_can_open = False
|
|
|
|
|
|
|
|
|
|
if xml_file_obj and parent_item and self.project:
|
|
|
|
|
xsl_file_obj = parent_item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if xsl_file_obj:
|
|
|
|
|
# Erstelle Pfade zu Ref-PDF und Diff-PDF
|
|
|
|
|
xsl_id_str = "_".join(map(str, xsl_file_obj.id))
|
|
|
|
|
xml_stem = xml_file_obj.xml.stem
|
|
|
|
|
pdf_basename = f"{xml_stem}_xsl_{xsl_id_str}.pdf"
|
|
|
|
|
|
|
|
|
|
ref_pdf_path = self.project.project_dir / "ref" / pdf_basename
|
|
|
|
|
diff_pdf_path = self.project.project_dir / "diff" / pdf_basename
|
|
|
|
|
|
|
|
|
|
# Ref-PDF kann geöffnet werden, wenn sie existiert und keine Diff-PDF vorhanden ist
|
|
|
|
|
ref_pdf_can_open = ref_pdf_path.exists() and not diff_pdf_path.exists()
|
|
|
|
|
|
|
|
|
|
action_open_ref_pdf = QAction("Ref-PDF öffnen", self)
|
|
|
|
|
action_open_ref_pdf.setIcon(QIcon(QIcon.fromTheme("document-open")))
|
|
|
|
|
action_open_ref_pdf.triggered.connect(lambda: self._open_ref_pdf_for_xml_file(item))
|
|
|
|
|
action_open_ref_pdf.setEnabled(ref_pdf_can_open)
|
|
|
|
|
menu.addAction(action_open_ref_pdf)
|
|
|
|
|
|
|
|
|
|
menu.addSeparator()
|
|
|
|
|
|
|
|
|
|
action_edit = QAction("Bearbeiten", self)
|
|
|
|
|
action_edit.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)))
|
|
|
|
|
action_edit.triggered.connect(lambda: self._edit_xml_file(item))
|
|
|
|
|
menu.addAction(action_edit)
|
|
|
|
|
|
|
|
|
|
action_delete = QAction("Löschen", self)
|
|
|
|
|
action_delete.setIcon(QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditDelete)))
|
|
|
|
|
action_delete.triggered.connect(lambda: self._delete_xml_file(item))
|
|
|
|
|
menu.addAction(action_delete)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# Unbekannter Typ oder leerer Bereich - Menü für Root-Elemente
|
|
|
|
|
action_add_tree_node = QAction("Unterknoten hinzufügen", self)
|
|
|
|
|
action_add_tree_node.setIcon(QIcon(QIcon.fromTheme("folder-new")))
|
|
|
|
|
action_add_tree_node.triggered.connect(lambda: self._add_root_tree_node())
|
|
|
|
|
menu.addAction(action_add_tree_node)
|
|
|
|
|
|
|
|
|
|
return menu
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Erstellen des Kontextmenüs: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def _load_nodes_to_tree(self):
|
|
|
|
|
"""
|
|
|
|
|
Lädt die Nodes aus den Projekt-Einstellungen in das TreeWidget.
|
|
|
|
|
Sortiert die Items alphabetisch nach ihrer ID.
|
|
|
|
|
"""
|
|
|
|
|
logger.info("Lade Nodes in TreeWidget...")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# TreeWidget leeren
|
|
|
|
|
self.ui.treeWidget.clear()
|
|
|
|
|
|
|
|
|
|
# Lösche XML-Item-Map
|
|
|
|
|
self.xml_item_map.clear()
|
|
|
|
|
|
|
|
|
|
# Prüfe ob pdf_project existiert und Nodes hat
|
|
|
|
|
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
|
|
|
|
logger.warning("Keine Projekt-Einstellungen verfügbar")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self.pdf_project.nodes:
|
|
|
|
|
logger.warning("Keine Nodes in den Projekt-Einstellungen gefunden")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Sortiere Root-Nodes alphabetisch nach ID
|
|
|
|
|
sorted_nodes = sorted(self.pdf_project.nodes, key=lambda node: node.id)
|
|
|
|
|
|
|
|
|
|
# Lade alle Root-Nodes (sortiert)
|
|
|
|
|
for node in sorted_nodes:
|
|
|
|
|
tree_item = self._create_tree_item_from_node(node)
|
|
|
|
|
self.ui.treeWidget.addTopLevelItem(tree_item)
|
|
|
|
|
|
|
|
|
|
logger.info(f"{len(self.pdf_project.nodes)} Root-Nodes in TreeWidget geladen (alphabetisch sortiert)")
|
|
|
|
|
|
|
|
|
|
# Aktualisiere Diff-PDF-Anzahl und Icons nach dem Laden
|
|
|
|
|
self._update_all_diff_pdf_counts()
|
|
|
|
|
self._update_diff_icons_for_existing_pdfs()
|
|
|
|
|
|
2026-02-01 15:44:55 +01:00
|
|
|
# Stelle Expand-Status wieder her
|
|
|
|
|
self._restore_expanded_state()
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Laden der Nodes in TreeWidget: {e}")
|
|
|
|
|
|
|
|
|
|
def _create_tree_item_from_node(self, node):
|
|
|
|
|
"""
|
|
|
|
|
Erstellt ein QTreeWidgetItem aus einem TreeNode oder XslFile.
|
|
|
|
|
Speichert die vollständigen Node-Daten für spätere Verwendung.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
node: TreeNode oder XslFile Objekt
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
QTreeWidgetItem: Das erstellte Tree-Item mit vollständigen Node-Daten
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Erstelle Tree-Item
|
|
|
|
|
item = QTreeWidgetItem()
|
|
|
|
|
|
|
|
|
|
# Setze die Bezeichnung in Spalte 0
|
|
|
|
|
bez_text = str(node.bez) if node.bez else ""
|
|
|
|
|
item.setText(0, bez_text)
|
|
|
|
|
|
|
|
|
|
# Speichere das komplette Node-Objekt als UserRole-Daten
|
|
|
|
|
# Dies ermöglicht späteren Zugriff auf alle Node-Eigenschaften
|
|
|
|
|
item.setData(0, Qt.ItemDataRole.UserRole, node)
|
|
|
|
|
|
2026-01-18 13:48:20 +01:00
|
|
|
# Setze Icon basierend auf Node-Typ
|
|
|
|
|
if isinstance(node, TreeNode):
|
|
|
|
|
# TreeNode: Ordner-Icon
|
|
|
|
|
folder_icon = QIcon.fromTheme(QIcon.ThemeIcon.FolderOpen)
|
|
|
|
|
if folder_icon.isNull():
|
|
|
|
|
folder_icon = QIcon.fromTheme("folder")
|
|
|
|
|
item.setIcon(0, folder_icon)
|
|
|
|
|
elif isinstance(node, XslFile):
|
|
|
|
|
# XslFile: Code/Script-Icon für XSL-Dateien
|
|
|
|
|
xsl_icon = QIcon.fromTheme("text-x-script")
|
|
|
|
|
if xsl_icon.isNull():
|
|
|
|
|
xsl_icon = QIcon.fromTheme("text-x-generic")
|
|
|
|
|
item.setIcon(0, xsl_icon)
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
# Setze zusätzliche Informationen in Spalte 1
|
|
|
|
|
if isinstance(node, TreeNode):
|
|
|
|
|
# TreeNode: Zeige Anzahl der Knoten
|
|
|
|
|
child_count = len(node.children) if node.children else 0
|
|
|
|
|
item.setText(1, f"{child_count} Knoten")
|
|
|
|
|
|
|
|
|
|
# Speichere zusätzlich die Node-ID in UserRole+1 für Kompatibilität
|
|
|
|
|
item.setData(0, Qt.ItemDataRole.UserRole + 1, node.id)
|
|
|
|
|
|
|
|
|
|
# Lade Knoten rekursiv (sortiert nach ID)
|
|
|
|
|
if node.children:
|
|
|
|
|
sorted_children = sorted(node.children, key=lambda child: child.id)
|
|
|
|
|
for child in sorted_children:
|
|
|
|
|
child_item = self._create_tree_item_from_node(child)
|
|
|
|
|
item.addChild(child_item)
|
|
|
|
|
|
|
|
|
|
# Setze Diff-PDF-Anzahl in Spalte 2 (wird später aktualisiert)
|
|
|
|
|
diff_count = self._count_diff_pdfs_under_node(node, item)
|
|
|
|
|
if diff_count > 0:
|
|
|
|
|
item.setText(2, str(diff_count))
|
|
|
|
|
|
|
|
|
|
elif isinstance(node, XslFile):
|
|
|
|
|
# XslFile: Zeige XSL-Datei-Pfad
|
|
|
|
|
item.setText(1, str(node.xsl_file))
|
|
|
|
|
|
|
|
|
|
# Speichere zusätzlich die Node-ID in UserRole+1 für Kompatibilität
|
|
|
|
|
item.setData(0, Qt.ItemDataRole.UserRole + 1, node.id)
|
|
|
|
|
|
|
|
|
|
# Setze Diff-PDF-Anzahl in Spalte 2 (wird später aktualisiert)
|
|
|
|
|
diff_count = self._count_diff_pdfs_under_node(node, item)
|
|
|
|
|
if diff_count > 0:
|
|
|
|
|
item.setText(2, str(diff_count))
|
|
|
|
|
|
2026-01-17 20:29:29 +01:00
|
|
|
# Prüfe ob XSL-Datei existiert
|
|
|
|
|
xsl_file_missing = False
|
|
|
|
|
if hasattr(self, "project") and self.project:
|
|
|
|
|
from conf import app_settings
|
|
|
|
|
|
|
|
|
|
# Hole XSL-Verzeichnis aus Projekt-Konfiguration
|
2026-01-18 13:48:20 +01:00
|
|
|
xsl_dir = next((xd for xd in app_settings.xsl_dirs if xd.id == self.project.xsl_dir_id), None)
|
2026-01-17 20:29:29 +01:00
|
|
|
|
|
|
|
|
if xsl_dir:
|
|
|
|
|
# Konstruiere absoluten Pfad zur XSL-Datei
|
|
|
|
|
xsl_file_abs = xsl_dir.path_to_root_dir / node.xsl_file
|
|
|
|
|
|
|
|
|
|
if not xsl_file_abs.exists():
|
|
|
|
|
xsl_file_missing = True
|
|
|
|
|
item.setDisabled(True)
|
|
|
|
|
item.setToolTip(0, f"XSL-Datei nicht gefunden: {xsl_file_abs}")
|
|
|
|
|
logger.warning(f"XSL-Datei nicht vorhanden: {xsl_file_abs}")
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
# Lade XML-Dateien als Knoten
|
|
|
|
|
if node.xmls:
|
|
|
|
|
for xml in node.xmls:
|
|
|
|
|
xml_item = QTreeWidgetItem()
|
|
|
|
|
xml_item.setText(0, f"XML: {xml.xml.name}")
|
|
|
|
|
xml_item.setText(1, str(xml.xml))
|
|
|
|
|
|
|
|
|
|
# Speichere auch das XmlFile-Objekt für XML-Items
|
|
|
|
|
xml_item.setData(0, Qt.ItemDataRole.UserRole, xml)
|
|
|
|
|
xml_item.setData(0, Qt.ItemDataRole.UserRole + 1, f"xml_{xml.xml.name}")
|
|
|
|
|
|
|
|
|
|
# Speichere XSL-ID in Spalte 1, UserRole für einfachen Zugriff
|
|
|
|
|
xsl_id_str = "_".join(str(x) for x in node.id)
|
|
|
|
|
xml_item.setData(1, Qt.ItemDataRole.UserRole, xsl_id_str)
|
|
|
|
|
|
2026-01-18 13:48:20 +01:00
|
|
|
# Setze XML-Icon
|
|
|
|
|
xml_icon = QIcon.fromTheme("text-xml")
|
|
|
|
|
if xml_icon.isNull():
|
|
|
|
|
xml_icon = QIcon.fromTheme("application-xml")
|
|
|
|
|
if xml_icon.isNull():
|
2026-01-22 19:33:15 +01:00
|
|
|
xml_icon = QIcon.fromTheme("text-x-generic")
|
2026-01-18 13:48:20 +01:00
|
|
|
xml_item.setIcon(0, xml_icon)
|
|
|
|
|
|
2026-01-17 20:22:42 +01:00
|
|
|
# Prüfe ob XML-Datei existiert und deaktiviere Knoten falls nicht
|
2026-01-17 20:29:29 +01:00
|
|
|
# Wenn XSL-Datei fehlt, deaktiviere auch alle untergeordneten XML-Knoten
|
|
|
|
|
if xsl_file_missing:
|
|
|
|
|
xml_item.setDisabled(True)
|
|
|
|
|
xml_item.setToolTip(0, "XML-Knoten deaktiviert: Übergeordnete XSL-Datei fehlt")
|
|
|
|
|
elif hasattr(self, "project") and self.project:
|
2026-01-17 20:22:42 +01:00
|
|
|
xml_abs_path = self.project.project_dir / xml.xml
|
|
|
|
|
if not xml_abs_path.exists():
|
|
|
|
|
xml_item.setDisabled(True)
|
|
|
|
|
xml_item.setToolTip(0, f"XML-Datei nicht gefunden: {xml_abs_path}")
|
|
|
|
|
logger.warning(f"XML-Datei nicht vorhanden: {xml_abs_path}")
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
item.addChild(xml_item)
|
|
|
|
|
|
|
|
|
|
# Speichere XML-Item für spätere Widget-Updates (Progress Bar, Icon)
|
|
|
|
|
# Key: "xml_path|xsl_id" um mehrfache Verwendung derselben XML zu unterstützen
|
|
|
|
|
xml_path_str = str(xml.xml)
|
|
|
|
|
xsl_id_str = "_".join(str(x) for x in node.id)
|
|
|
|
|
map_key = f"{xml_path_str}|{xsl_id_str}"
|
|
|
|
|
self.xml_item_map[map_key] = xml_item
|
|
|
|
|
logger.debug(f"XML-Item zur Map hinzugefügt: '{map_key}'")
|
|
|
|
|
|
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Erstellen des Tree-Items: {e}")
|
|
|
|
|
# Fallback: Erstelle einfaches Item
|
|
|
|
|
fallback_item = QTreeWidgetItem()
|
|
|
|
|
fallback_item.setText(0, "Fehler beim Laden")
|
|
|
|
|
fallback_item.setText(1, str(e))
|
|
|
|
|
return fallback_item
|
|
|
|
|
|
|
|
|
|
def _create_centered_progress_bar(self) -> tuple[QWidget, QProgressBar]:
|
|
|
|
|
"""
|
|
|
|
|
Erstellt eine linksbündige Progress Bar in einem Container-Widget.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
tuple: (container_widget, progress_bar)
|
|
|
|
|
"""
|
|
|
|
|
# Container-Widget erstellen
|
|
|
|
|
container = QWidget()
|
|
|
|
|
layout = QHBoxLayout(container)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
|
|
|
|
|
|
|
|
# Progress Bar erstellen (indeterminate mode für pulsierenden Effekt)
|
|
|
|
|
progress_bar = QProgressBar()
|
|
|
|
|
progress_bar.setMinimum(0)
|
|
|
|
|
progress_bar.setMaximum(0) # Pulsierend
|
|
|
|
|
progress_bar.setMaximumWidth(80) # Kompakte Breite
|
|
|
|
|
progress_bar.setMaximumHeight(16) # Kompakte Höhe
|
|
|
|
|
progress_bar.setTextVisible(False)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(progress_bar)
|
|
|
|
|
|
|
|
|
|
return container, progress_bar
|
|
|
|
|
|
|
|
|
|
def _create_centered_diff_icon(self, xml_file_path: Path, xsl_id_str: str) -> QWidget:
|
|
|
|
|
"""
|
|
|
|
|
Erstellt ein linksbündiges, nicht-klickbares Icon für Diff-PDF.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
xml_file_path: Pfad zur XML-Datei (relativ)
|
|
|
|
|
xsl_id_str: XSL-ID als String (z.B. "2002_1_128")
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
QWidget: Container mit Icon
|
|
|
|
|
"""
|
|
|
|
|
# Container-Widget
|
|
|
|
|
container = QWidget()
|
|
|
|
|
layout = QHBoxLayout(container)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
|
|
|
|
|
|
|
|
# Icon-Label
|
|
|
|
|
icon_label = QLabel()
|
|
|
|
|
# Icon für Diff-View mit Fallbacks
|
|
|
|
|
icon = QIcon.fromTheme("view-split-left-right")
|
|
|
|
|
if icon.isNull():
|
|
|
|
|
icon = QIcon.fromTheme("vcs-diff")
|
|
|
|
|
if icon.isNull():
|
|
|
|
|
icon = QIcon.fromTheme("system-search") # Letzter Fallback
|
|
|
|
|
icon_label.setPixmap(icon.pixmap(16, 16))
|
|
|
|
|
icon_label.setToolTip("Diff-PDF vorhanden (wird automatisch geladen bei Selektion)")
|
|
|
|
|
|
|
|
|
|
layout.addWidget(icon_label)
|
|
|
|
|
|
|
|
|
|
return container
|
|
|
|
|
|
|
|
|
|
# Kontextmenü-Aktionen für TreeNode
|
|
|
|
|
def _add_tree_node_child(self, parent_item):
|
|
|
|
|
"""Fügt einen Unterknoten zu einem TreeNode hinzu."""
|
|
|
|
|
logger.debug(f"Unterknoten zu TreeNode hinzufügen: {parent_item.text(0)}")
|
|
|
|
|
# TODO: Dialog zum Eingeben der Node-Daten öffnen
|
|
|
|
|
|
|
|
|
|
def _add_xsl_file_to_node(self, parent_item):
|
|
|
|
|
"""Fügt eine XSL-Datei zu einem TreeNode hinzu."""
|
|
|
|
|
logger.debug(f"XSL-Datei zu TreeNode hinzufügen: {parent_item.text(0)}")
|
|
|
|
|
# TODO: Dialog zum Auswählen der XSL-Datei öffnen
|
|
|
|
|
|
|
|
|
|
def _edit_tree_node(self, item):
|
|
|
|
|
"""
|
|
|
|
|
Bearbeitet einen TreeNode.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das TreeWidgetItem des TreeNode
|
|
|
|
|
"""
|
|
|
|
|
logger.debug(f"TreeNode bearbeiten: {item.text(0)}")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Hole das Node-Objekt aus dem TreeWidgetItem
|
|
|
|
|
node = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not node or not isinstance(node, TreeNode):
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Kein gültiger TreeNode gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Prüfe ob Projekt verfügbar ist
|
|
|
|
|
if not self.pdf_project:
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Keine Projekt-Einstellungen verfügbar.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Sammle Eltern-Parameter
|
|
|
|
|
parent_params = self._collect_parent_params(item)
|
|
|
|
|
|
|
|
|
|
# Erstelle und zeige den Dialog
|
|
|
|
|
dialog = TreeNodeEditDialog(self, node, parent_params)
|
|
|
|
|
if dialog.exec() == TreeNodeEditDialog.DialogCode.Accepted:
|
|
|
|
|
# Hole die bearbeiteten Daten
|
|
|
|
|
data = dialog.get_data()
|
|
|
|
|
if data:
|
|
|
|
|
# Aktualisiere den Node
|
|
|
|
|
node.bez = data["bez"]
|
|
|
|
|
node.xslt_params = data["xslt_params"]
|
|
|
|
|
|
|
|
|
|
logger.info(f"TreeNode '{node.bez}' wurde aktualisiert")
|
|
|
|
|
logger.debug(f"XSLT-Parameter: {node.xslt_params}")
|
|
|
|
|
|
|
|
|
|
# Speichere die Änderungen
|
|
|
|
|
self._save_project_settings()
|
|
|
|
|
|
|
|
|
|
# Aktualisiere das TreeWidget
|
|
|
|
|
self._load_nodes_to_tree()
|
|
|
|
|
|
|
|
|
|
# Wenn Force-Transformation gewünscht, führe sie aus
|
|
|
|
|
if data.get("force_transform", False):
|
|
|
|
|
# Finde das neue Item nach dem Neuladen
|
|
|
|
|
new_item = self._find_item_by_node(node)
|
|
|
|
|
if new_item:
|
|
|
|
|
logger.info(f"Starte Force-Transformation für TreeNode '{node.bez}'")
|
|
|
|
|
self._transform_tree_node(new_item, force=True)
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"Konnte Item für TreeNode '{node.bez}' nicht finden")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = f"Fehler beim Bearbeiten des TreeNode: {str(e)}"
|
|
|
|
|
logger.error(error_msg)
|
|
|
|
|
QMessageBox.critical(self, "Fehler", error_msg)
|
|
|
|
|
|
2026-02-08 21:08:23 +01:00
|
|
|
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
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
def _delete_tree_node(self, item):
|
2026-02-08 21:08:23 +01:00
|
|
|
"""
|
|
|
|
|
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
|
|
|
|
|
"""
|
2026-01-15 18:23:55 +01:00
|
|
|
logger.debug(f"TreeNode löschen: {item.text(0)}")
|
2026-02-08 21:08:23 +01:00
|
|
|
|
|
|
|
|
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)
|
2026-01-15 18:23:55 +01:00
|
|
|
|
|
|
|
|
# Kontextmenü-Aktionen für XslFile
|
|
|
|
|
def _add_xml_file_to_xsl(self, parent_item):
|
|
|
|
|
"""
|
|
|
|
|
Fügt eine XML-Datei zu einer XSL-Datei hinzu.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
parent_item: Das TreeWidgetItem des XslFile-Nodes
|
|
|
|
|
"""
|
|
|
|
|
logger.debug(f"XML-Datei zu XslFile hinzufügen: {parent_item.text(0)}")
|
|
|
|
|
|
|
|
|
|
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 XslFile-Node-Objekt direkt aus dem TreeWidgetItem
|
|
|
|
|
xsl_node = parent_item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not xsl_node or not isinstance(xsl_node, XslFile):
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Keine gültige XSL-Datei-Node gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Öffne Datei-Dialog zum Auswählen der XML-Datei
|
|
|
|
|
xml_file_path, _ = QFileDialog.getOpenFileName(
|
|
|
|
|
self, "XML-Datei auswählen", "", "XML-Dateien (*.xml);;Alle Dateien (*)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not xml_file_path:
|
|
|
|
|
# Benutzer hat abgebrochen
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
xml_file_path = Path(xml_file_path)
|
|
|
|
|
|
|
|
|
|
# Prüfe ob die Datei existiert
|
|
|
|
|
if not xml_file_path.exists():
|
|
|
|
|
QMessageBox.critical(self, "Fehler", f"Die ausgewählte XML-Datei existiert nicht:\n{xml_file_path}")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Erstelle xml-Ordner im Projekt-Verzeichnis falls er nicht existiert
|
|
|
|
|
xml_dir = Path(self.project.project_dir) / "xml"
|
|
|
|
|
xml_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
# Bestimme den Ziel-Pfad in xml-Ordner
|
|
|
|
|
target_xml_path = xml_dir / xml_file_path.name
|
|
|
|
|
|
|
|
|
|
# Prüfe ob eine Datei mit gleichem Namen bereits existiert
|
|
|
|
|
if target_xml_path.exists():
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
"Datei existiert bereits",
|
|
|
|
|
f"Eine XML-Datei mit dem Namen '{xml_file_path.name}' existiert bereits im xml-Ordner.\n\n"
|
|
|
|
|
"Möchten Sie sie überschreiben?",
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
QMessageBox.StandardButton.No,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Kopiere die XML-Datei in den xml-Ordner
|
|
|
|
|
shutil.copy2(xml_file_path, target_xml_path)
|
|
|
|
|
logger.info(f"XML-Datei kopiert: {xml_file_path} -> {target_xml_path}")
|
|
|
|
|
|
|
|
|
|
# Erstelle relatives Path zur XML-Datei (relativ zum xml-Ordner)
|
|
|
|
|
relative_xml_path = Path("xml") / xml_file_path.name
|
|
|
|
|
|
|
|
|
|
# Prüfe ob diese XML-Datei bereits in der XslFile-Node vorhanden ist
|
|
|
|
|
existing_xml = None
|
|
|
|
|
for xml_file in xsl_node.xmls:
|
|
|
|
|
if xml_file.xml == relative_xml_path:
|
|
|
|
|
existing_xml = xml_file
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if existing_xml:
|
|
|
|
|
QMessageBox.information(
|
|
|
|
|
self,
|
|
|
|
|
"XML-Datei bereits vorhanden",
|
|
|
|
|
f"Die XML-Datei '{xml_file_path.name}' ist bereits in dieser XSL-Datei enthalten.",
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Erstelle neues XmlFile-Objekt und füge es zur XslFile-Node hinzu
|
|
|
|
|
new_xml_file = XmlFile(xml=relative_xml_path)
|
|
|
|
|
xsl_node.xmls.append(new_xml_file)
|
|
|
|
|
|
|
|
|
|
logger.info(f"XML-Datei '{xml_file_path.name}' zu XslFile-Node '{xsl_node.bez}' hinzugefügt")
|
|
|
|
|
|
|
|
|
|
# Berechne Hash für die neue XML-Datei
|
|
|
|
|
self._calculate_hash_for_xml_file(new_xml_file)
|
|
|
|
|
|
|
|
|
|
# Speichere die aktualisierten Projekt-Einstellungen
|
|
|
|
|
self._save_project_settings()
|
|
|
|
|
|
|
|
|
|
# Aktualisiere das TreeWidget
|
|
|
|
|
self._load_nodes_to_tree()
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = f"Fehler beim Hinzufügen der XML-Datei: {str(e)}"
|
|
|
|
|
logger.error(error_msg)
|
|
|
|
|
QMessageBox.critical(self, "Fehler", error_msg)
|
|
|
|
|
|
|
|
|
|
def _edit_xsl_file(self, item):
|
|
|
|
|
"""
|
|
|
|
|
Bearbeitet eine XSL-Datei.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das TreeWidgetItem des XslFile
|
|
|
|
|
"""
|
|
|
|
|
logger.debug(f"XslFile bearbeiten: {item.text(0)}")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Hole das Node-Objekt aus dem TreeWidgetItem
|
|
|
|
|
node = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not node or not isinstance(node, XslFile):
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Keine gültige XSL-Datei gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Sammle Eltern-Parameter
|
|
|
|
|
parent_params = self._collect_parent_params(item)
|
|
|
|
|
|
|
|
|
|
# Erstelle und zeige den Dialog
|
|
|
|
|
dialog = XslFileEditDialog(self, node, parent_params)
|
|
|
|
|
if dialog.exec() == XslFileEditDialog.DialogCode.Accepted:
|
|
|
|
|
# Hole die bearbeiteten Daten
|
|
|
|
|
data = dialog.get_data()
|
|
|
|
|
if data:
|
|
|
|
|
# Aktualisiere den Node
|
|
|
|
|
node.bez = data["bez"]
|
|
|
|
|
node.xslt_params = data["xslt_params"]
|
|
|
|
|
|
|
|
|
|
logger.info(f"XslFile '{node.bez}' wurde aktualisiert")
|
|
|
|
|
logger.debug(f"XSLT-Parameter: {node.xslt_params}")
|
|
|
|
|
|
|
|
|
|
# Speichere die Änderungen
|
|
|
|
|
self._save_project_settings()
|
|
|
|
|
|
|
|
|
|
# Aktualisiere das TreeWidget
|
|
|
|
|
self._load_nodes_to_tree()
|
|
|
|
|
|
|
|
|
|
# Wenn Force-Transformation gewünscht, führe sie aus
|
|
|
|
|
if data.get("force_transform", False):
|
|
|
|
|
# Finde das neue Item nach dem Neuladen
|
|
|
|
|
new_item = self._find_item_by_node(node)
|
|
|
|
|
if new_item:
|
|
|
|
|
logger.info(f"Starte Force-Transformation für XslFile '{node.bez}'")
|
|
|
|
|
self._transform_xsl_file(new_item, force=True)
|
|
|
|
|
else:
|
|
|
|
|
logger.warning(f"Konnte Item für XslFile '{node.bez}' nicht finden")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = f"Fehler beim Bearbeiten der XSL-Datei: {str(e)}"
|
|
|
|
|
logger.error(error_msg)
|
|
|
|
|
QMessageBox.critical(self, "Fehler", error_msg)
|
|
|
|
|
|
|
|
|
|
def _delete_xsl_file(self, item):
|
2026-02-08 20:12:24 +01:00
|
|
|
"""
|
|
|
|
|
Löscht eine XSL-Datei aus einem Baumknoten.
|
|
|
|
|
|
|
|
|
|
Die XSL-Datei wird 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 der XSL-Datei
|
|
|
|
|
"""
|
2026-01-15 18:23:55 +01:00
|
|
|
logger.debug(f"XslFile löschen: {item.text(0)}")
|
2026-02-08 20:12:24 +01:00
|
|
|
|
|
|
|
|
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 XslFile-Objekt aus dem TreeWidgetItem
|
|
|
|
|
xsl_file_obj = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not xsl_file_obj or not isinstance(xsl_file_obj, XslFile):
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Keine gültige XSL-Datei gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Hole das Eltern-Item (sollte ein TreeNode sein)
|
|
|
|
|
parent_item = item.parent()
|
|
|
|
|
if not parent_item:
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Eltern-Knoten nicht gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
# Bestätigungsdialog anzeigen
|
|
|
|
|
xml_count = len(xsl_file_obj.xmls)
|
|
|
|
|
xml_info = f"\n\nDer XSL-Datei sind {xml_count} XML-Datei(en) zugeordnet." if xml_count > 0 else ""
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
"XSL-Datei entfernen",
|
|
|
|
|
f"Möchten Sie die XSL-Datei '{xsl_file_obj.bez}' aus dem Baum entfernen?\n\n"
|
|
|
|
|
f"Die XSL-Datei selbst wird nicht physisch gelöscht.{xml_info}",
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
QMessageBox.StandardButton.No,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
|
|
|
logger.debug("Löschung abgebrochen")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# PDF-Bereinigung für alle zugehörigen XML-Dateien
|
|
|
|
|
xsl_id = xsl_file_obj.id
|
|
|
|
|
total_deleted_pdfs = 0
|
|
|
|
|
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 XSL '{xsl_file_obj.bez}' gelöscht")
|
|
|
|
|
|
|
|
|
|
# Sammle XML-Dateien, die nirgends mehr verwendet werden
|
|
|
|
|
unused_xml_files = []
|
|
|
|
|
for xml_file_obj in xsl_file_obj.xmls:
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
# Optionale physische Löschung nicht mehr verwendeter XML-Dateien
|
|
|
|
|
if unused_xml_files:
|
|
|
|
|
file_list = "\n".join(f" - {p.name}" for _, p in unused_xml_files)
|
|
|
|
|
delete_reply = QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
"Physische XML-Dateien löschen",
|
|
|
|
|
f"Folgende {len(unused_xml_files)} XML-Datei(en) werden in keiner anderen "
|
|
|
|
|
f"XSL-Datei mehr verwendet:\n\n{file_list}\n\n"
|
|
|
|
|
"Möchten Sie diese physisch löschen?",
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
QMessageBox.StandardButton.No,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if delete_reply == QMessageBox.StandardButton.Yes:
|
|
|
|
|
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}")
|
|
|
|
|
|
|
|
|
|
# Entferne das XslFile aus dem Eltern-TreeNode
|
|
|
|
|
children_before = len(parent_node.children)
|
|
|
|
|
parent_node.children = [child for child in parent_node.children if child is not xsl_file_obj]
|
|
|
|
|
children_after = len(parent_node.children)
|
|
|
|
|
|
|
|
|
|
if children_before == children_after:
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "XSL-Datei konnte nicht aus dem Knoten entfernt werden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
logger.info(f"XSL-Datei '{xsl_file_obj.bez}' aus Knoten '{parent_node.bez}' entfernt")
|
|
|
|
|
|
|
|
|
|
# Speichere und aktualisiere
|
|
|
|
|
self._save_project_settings()
|
|
|
|
|
self._load_nodes_to_tree()
|
|
|
|
|
|
|
|
|
|
logger.info(f"XSL-Datei '{xsl_file_obj.bez}' erfolgreich entfernt")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = f"Fehler beim Löschen der XSL-Datei: {str(e)}"
|
|
|
|
|
logger.error(error_msg)
|
|
|
|
|
QMessageBox.critical(self, "Fehler", error_msg)
|
2026-01-15 18:23:55 +01:00
|
|
|
|
|
|
|
|
# Kontextmenü-Aktionen für XmlFile
|
|
|
|
|
def _edit_xml_file(self, item):
|
|
|
|
|
"""Bearbeitet eine XML-Datei."""
|
|
|
|
|
logger.debug(f"XmlFile bearbeiten: {item.text(0)}")
|
|
|
|
|
# TODO: Dialog zum Bearbeiten der XML-Datei öffnen
|
|
|
|
|
|
|
|
|
|
def _delete_xml_file(self, item):
|
|
|
|
|
"""
|
|
|
|
|
Löscht eine XML-Datei aus einem XSL-Knoten.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das TreeWidgetItem der XML-Datei
|
|
|
|
|
"""
|
|
|
|
|
logger.debug(f"XmlFile löschen: {item.text(0)}")
|
|
|
|
|
|
|
|
|
|
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 XmlFile-Objekt aus dem TreeWidgetItem
|
|
|
|
|
xml_file_obj = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not xml_file_obj or not isinstance(xml_file_obj, XmlFile):
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Keine gültige XML-Datei gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Hole das Eltern-Item (sollte ein XslFile sein)
|
|
|
|
|
parent_item = item.parent()
|
|
|
|
|
if not parent_item:
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Eltern-XSL-Datei nicht gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Hole das XslFile-Objekt aus dem Eltern-Item
|
|
|
|
|
xsl_file_obj = parent_item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not xsl_file_obj or not isinstance(xsl_file_obj, XslFile):
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "Keine gültige Eltern-XSL-Datei gefunden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Bestätigungsdialog anzeigen
|
|
|
|
|
xml_filename = xml_file_obj.xml.name
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
"XML-Datei löschen",
|
|
|
|
|
f"Möchten Sie die XML-Datei '{xml_filename}' aus der XSL-Datei '{xsl_file_obj.bez}' entfernen?\n\n"
|
|
|
|
|
"Die XML-Datei wird nur aus der Zuordnung entfernt, nicht physisch gelöscht.",
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
QMessageBox.StandardButton.No,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
|
|
|
logger.debug("Löschung abgebrochen")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Entferne die XML-Datei aus der XslFile-Node
|
|
|
|
|
xml_files_before = len(xsl_file_obj.xmls)
|
|
|
|
|
xsl_file_obj.xmls = [xml for xml in xsl_file_obj.xmls if xml.xml != xml_file_obj.xml]
|
|
|
|
|
xml_files_after = len(xsl_file_obj.xmls)
|
|
|
|
|
|
|
|
|
|
if xml_files_before == xml_files_after:
|
|
|
|
|
QMessageBox.warning(self, "Warnung", "XML-Datei konnte nicht aus der XSL-Datei entfernt werden.")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
logger.info(f"XML-Datei '{xml_filename}' aus XSL-Datei '{xsl_file_obj.bez}' entfernt")
|
|
|
|
|
|
2026-02-07 19:47:02 +01:00
|
|
|
# Prüfe ob die XML+XSL-Kombination noch woanders verwendet wird
|
|
|
|
|
# Wenn nicht, lösche die zugehörigen PDF-Dateien
|
|
|
|
|
xsl_id = xsl_file_obj.id
|
|
|
|
|
is_combination_used_elsewhere = self._is_xml_xsl_combination_used_elsewhere(
|
|
|
|
|
xml_file_obj.xml, xsl_id, xsl_file_obj
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not is_combination_used_elsewhere:
|
|
|
|
|
# Lösche alle PDF-Dateien für diese XML+XSL-Kombination
|
|
|
|
|
deleted_pdfs = self._delete_pdf_files_for_xml_xsl_combination(xml_file_obj.xml, xsl_id)
|
|
|
|
|
if deleted_pdfs > 0:
|
|
|
|
|
logger.info(f"{deleted_pdfs} PDF-Datei(en) für '{xml_filename}' (XSL: {xsl_file_obj.bez}) gelöscht")
|
|
|
|
|
else:
|
|
|
|
|
logger.debug(
|
|
|
|
|
f"XML+XSL-Kombination '{xml_filename}' + XSL-ID {xsl_id} wird noch anderswo verwendet - PDFs nicht gelöscht"
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
# Frage ob die physische Datei auch gelöscht werden soll
|
|
|
|
|
xml_file_path = Path(self.project.project_dir) / xml_file_obj.xml
|
|
|
|
|
if xml_file_path.exists():
|
|
|
|
|
# Prüfe ob die XML-Datei noch in anderen XSL-Dateien verwendet wird
|
|
|
|
|
is_used_elsewhere = self._is_xml_file_used_elsewhere(xml_file_obj.xml, xsl_file_obj)
|
|
|
|
|
|
|
|
|
|
if not is_used_elsewhere:
|
|
|
|
|
delete_reply = QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
"Physische Datei löschen",
|
|
|
|
|
f"Die XML-Datei '{xml_filename}' wird in keiner anderen XSL-Datei verwendet.\n\n"
|
|
|
|
|
"Möchten Sie auch die physische Datei aus dem xml-Ordner löschen?",
|
|
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
|
|
|
QMessageBox.StandardButton.No,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if delete_reply == QMessageBox.StandardButton.Yes:
|
|
|
|
|
try:
|
|
|
|
|
xml_file_path.unlink()
|
|
|
|
|
logger.info(f"Physische XML-Datei gelöscht: {xml_file_path}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.warning(self, "Warnung", f"Fehler beim Löschen der physischen Datei:\n{str(e)}")
|
|
|
|
|
else:
|
|
|
|
|
logger.info(
|
|
|
|
|
f"XML-Datei '{xml_filename}' wird noch in anderen XSL-Dateien verwendet - physische Datei nicht gelöscht"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Speichere die aktualisierten Projekt-Einstellungen
|
|
|
|
|
self._save_project_settings()
|
|
|
|
|
|
|
|
|
|
# Aktualisiere das TreeWidget
|
|
|
|
|
self._load_nodes_to_tree()
|
|
|
|
|
|
|
|
|
|
logger.info(f"XML-Datei '{xml_filename}' erfolgreich entfernt")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error_msg = f"Fehler beim Löschen der XML-Datei: {str(e)}"
|
|
|
|
|
logger.error(error_msg)
|
|
|
|
|
QMessageBox.critical(self, "Fehler", error_msg)
|
|
|
|
|
|
|
|
|
|
def _is_xml_file_used_elsewhere(self, xml_path, exclude_xsl_file):
|
|
|
|
|
"""
|
|
|
|
|
Prüft ob eine XML-Datei noch in anderen XSL-Dateien verwendet wird.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
xml_path: Pfad zur XML-Datei (relativ)
|
|
|
|
|
exclude_xsl_file: XSL-Datei die ausgeschlossen werden soll
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True wenn die XML-Datei noch anderswo verwendet wird
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Prüfe ob pdf_project und nodes existieren
|
|
|
|
|
if not self.pdf_project or not self.pdf_project.nodes:
|
|
|
|
|
return False # Keine Nodes vorhanden, also nicht verwendet
|
|
|
|
|
|
|
|
|
|
return self._check_xml_usage_recursive(self.pdf_project.nodes, xml_path, exclude_xsl_file)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Prüfen der XML-Datei-Verwendung: {e}")
|
|
|
|
|
return True # Im Zweifelsfall annehmen, dass sie verwendet wird
|
|
|
|
|
|
|
|
|
|
def _check_xml_usage_recursive(self, nodes, xml_path, exclude_xsl_file):
|
|
|
|
|
"""
|
|
|
|
|
Prüft rekursiv ob eine XML-Datei in den Nodes verwendet wird.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
nodes: Liste der zu prüfenden Nodes
|
|
|
|
|
xml_path: Pfad zur XML-Datei (relativ)
|
|
|
|
|
exclude_xsl_file: XSL-Datei die ausgeschlossen werden soll
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True wenn die XML-Datei gefunden wird
|
|
|
|
|
"""
|
|
|
|
|
for node in nodes:
|
|
|
|
|
if isinstance(node, XslFile) and node != exclude_xsl_file:
|
|
|
|
|
# Prüfe ob diese XSL-Datei die XML-Datei verwendet
|
|
|
|
|
for xml_file in node.xmls:
|
|
|
|
|
if xml_file.xml == xml_path:
|
|
|
|
|
return True
|
|
|
|
|
elif isinstance(node, TreeNode) and node.children:
|
|
|
|
|
# Rekursiv in Knoten suchen
|
|
|
|
|
if self._check_xml_usage_recursive(node.children, xml_path, exclude_xsl_file):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
2026-02-07 19:47:02 +01:00
|
|
|
def _is_xml_xsl_combination_used_elsewhere(self, xml_path: Path, xsl_id: tuple, exclude_xsl_file) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
Prüft ob eine XML+XSL-Kombination noch in anderen XSL-Dateien verwendet wird.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
xml_path: Pfad zur XML-Datei (relativ)
|
|
|
|
|
xsl_id: Tuple der XSL-ID
|
|
|
|
|
exclude_xsl_file: XSL-Datei die ausgeschlossen werden soll
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True wenn die Kombination noch anderswo verwendet wird
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
if not self.pdf_project or not self.pdf_project.nodes:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return self._check_xml_xsl_combination_recursive(self.pdf_project.nodes, xml_path, xsl_id, exclude_xsl_file)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Prüfen der XML+XSL-Kombination: {e}")
|
|
|
|
|
return True # Im Zweifelsfall annehmen, dass sie verwendet wird
|
|
|
|
|
|
|
|
|
|
def _check_xml_xsl_combination_recursive(self, nodes, xml_path: Path, xsl_id: tuple, exclude_xsl_file) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
Prüft rekursiv ob eine XML+XSL-Kombination in den Nodes verwendet wird.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
nodes: Liste der zu prüfenden Nodes
|
|
|
|
|
xml_path: Pfad zur XML-Datei (relativ)
|
|
|
|
|
xsl_id: Tuple der XSL-ID
|
|
|
|
|
exclude_xsl_file: XSL-Datei die ausgeschlossen werden soll
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True wenn die Kombination gefunden wird
|
|
|
|
|
"""
|
|
|
|
|
for node in nodes:
|
|
|
|
|
if isinstance(node, XslFile) and node != exclude_xsl_file:
|
|
|
|
|
# Prüfe ob diese XSL-Datei die gleiche ID hat UND die XML-Datei verwendet
|
|
|
|
|
if node.id == xsl_id:
|
|
|
|
|
for xml_file in node.xmls:
|
|
|
|
|
if xml_file.xml == xml_path:
|
|
|
|
|
return True
|
|
|
|
|
elif isinstance(node, TreeNode) and node.children:
|
|
|
|
|
# Rekursiv in Knoten suchen
|
|
|
|
|
if self._check_xml_xsl_combination_recursive(node.children, xml_path, xsl_id, exclude_xsl_file):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _delete_pdf_files_for_xml_xsl_combination(self, xml_path: Path, xsl_id: tuple) -> int:
|
|
|
|
|
"""
|
|
|
|
|
Löscht alle PDF-Dateien (new, ref, diff) für eine XML+XSL-Kombination.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
xml_path: Pfad zur XML-Datei (relativ)
|
|
|
|
|
xsl_id: Tuple der XSL-ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
int: Anzahl der gelöschten PDF-Dateien
|
|
|
|
|
"""
|
|
|
|
|
deleted_count = 0
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Erzeuge den PDF-Dateinamen basierend auf XML und XSL-ID
|
|
|
|
|
base_name = xml_path.stem
|
|
|
|
|
xsl_id_str = "_".join(str(x) for x in xsl_id)
|
|
|
|
|
pdf_filename = f"{base_name}_xsl_{xsl_id_str}.pdf"
|
|
|
|
|
|
|
|
|
|
# Lösche PDFs in allen drei Verzeichnissen
|
|
|
|
|
pdf_dirs = ["new", "ref", "diff"]
|
|
|
|
|
for dir_name in pdf_dirs:
|
|
|
|
|
pdf_path = self.project.project_dir / dir_name / pdf_filename
|
|
|
|
|
if pdf_path.exists():
|
|
|
|
|
try:
|
|
|
|
|
pdf_path.unlink()
|
|
|
|
|
logger.info(f"PDF-Datei gelöscht: {pdf_path}")
|
|
|
|
|
deleted_count += 1
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning(f"Konnte PDF-Datei nicht löschen: {pdf_path} - {e}")
|
|
|
|
|
|
|
|
|
|
if deleted_count > 0:
|
|
|
|
|
logger.info(f"{deleted_count} PDF-Datei(en) für {xml_path.name} (XSL-ID: {xsl_id}) gelöscht")
|
|
|
|
|
else:
|
|
|
|
|
logger.debug(f"Keine PDF-Dateien für {xml_path.name} (XSL-ID: {xsl_id}) gefunden")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Löschen der PDF-Dateien: {e}")
|
|
|
|
|
|
|
|
|
|
return deleted_count
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
# Kontextmenü-Aktionen für Root-Elemente (Unbekannter Typ)
|
|
|
|
|
def _add_root_tree_node(self):
|
|
|
|
|
"""Fügt einen neuen TreeNode als Root-Element hinzu."""
|
|
|
|
|
logger.debug("Neuen TreeNode als Root-Element hinzufügen")
|
|
|
|
|
# TODO: Dialog zum Eingeben der TreeNode-Daten öffnen
|
|
|
|
|
|
|
|
|
|
def _collect_parent_params(self, item):
|
|
|
|
|
"""
|
|
|
|
|
Sammelt die XSLT-Parameter aller Eltern-Nodes von der Wurzel bis zum angegebenen Item.
|
|
|
|
|
|
|
|
|
|
Parameter werden von oben nach unten gesammelt, wobei tiefere Ebenen höhere Priorität haben.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das TreeWidgetItem (kann TreeNode oder XslFile sein)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
dict: Dictionary mit allen gesammelten Parametern (tiefere Ebenen überschreiben höhere)
|
|
|
|
|
"""
|
|
|
|
|
parent_params = {}
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Sammle alle Eltern-Items in einer Liste (von unten nach oben)
|
|
|
|
|
parents = []
|
|
|
|
|
current_item = item.parent()
|
|
|
|
|
|
|
|
|
|
while current_item:
|
|
|
|
|
parents.append(current_item)
|
|
|
|
|
current_item = current_item.parent()
|
|
|
|
|
|
|
|
|
|
# Kehre Liste um, sodass wir von Wurzel zu Kind iterieren
|
|
|
|
|
parents.reverse()
|
|
|
|
|
|
|
|
|
|
# Sammle Parameter von Wurzel zu Kind (Kind überschreibt Eltern)
|
|
|
|
|
for parent_item in parents:
|
|
|
|
|
parent_node = parent_item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
|
|
|
|
|
if parent_node and hasattr(parent_node, "xslt_params") and parent_node.xslt_params:
|
|
|
|
|
# Update überschreibt vorherige Werte (höhere Priorität für tiefere Ebenen)
|
|
|
|
|
parent_params.update(parent_node.xslt_params)
|
|
|
|
|
|
|
|
|
|
logger.debug(f"Gesammelte Eltern-Parameter: {parent_params}")
|
|
|
|
|
return parent_params
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Sammeln der Eltern-Parameter: {e}")
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
def _save_project_settings(self):
|
|
|
|
|
"""
|
|
|
|
|
Speichert die aktualisierten Projekt-Einstellungen.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Prüfe ob pdf_project und project existieren
|
|
|
|
|
if not self.pdf_project:
|
|
|
|
|
logger.warning("Keine Projekt-Einstellungen zum Speichern verfügbar")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self.project or not self.project.project_dir:
|
|
|
|
|
logger.warning("Kein Projekt-Verzeichnis zum Speichern verfügbar")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
2026-02-01 15:44:55 +01:00
|
|
|
# Speichere Expand-Status der Tree-Knoten vor dem Schreiben
|
|
|
|
|
self._save_expanded_state()
|
|
|
|
|
|
2026-01-15 18:23:55 +01:00
|
|
|
# Speichere in project.yaml im Projekt-Verzeichnis
|
|
|
|
|
self.pdf_project.writeSettings(project_dir=self.project.project_dir)
|
|
|
|
|
|
|
|
|
|
dump_time = time.time() - start_time
|
|
|
|
|
logger.debug(f"Performance: Projekt-Einstellungen gespeichert in {dump_time:.3f}s")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Speichern der Projekt-Einstellungen: {e}")
|
|
|
|
|
raise
|
2026-02-01 15:44:55 +01:00
|
|
|
|
|
|
|
|
def _save_expanded_state(self):
|
|
|
|
|
"""
|
|
|
|
|
Speichert die IDs aller aufgeklappten Knoten (TreeNode und XslFile) in den Projekteinstellungen.
|
|
|
|
|
"""
|
|
|
|
|
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
|
|
|
|
logger.warning("Keine Projekt-Einstellungen zum Speichern des Expand-Status")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
expanded_node_ids = []
|
|
|
|
|
|
|
|
|
|
# Durchlaufe alle Top-Level-Items
|
|
|
|
|
root_count = self.ui.treeWidget.topLevelItemCount()
|
|
|
|
|
for i in range(root_count):
|
|
|
|
|
item = self.ui.treeWidget.topLevelItem(i)
|
|
|
|
|
self._collect_expanded_items(item, expanded_node_ids)
|
|
|
|
|
|
|
|
|
|
# Speichere in Projekteinstellungen
|
|
|
|
|
self.pdf_project.expanded_nodes = expanded_node_ids
|
|
|
|
|
logger.info(f"Expand-Status gespeichert: {len(expanded_node_ids)} aufgeklappte Knoten")
|
|
|
|
|
logger.debug(f"Aufgeklappte Knoten-IDs: {expanded_node_ids}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Speichern des Expand-Status: {e}")
|
|
|
|
|
|
|
|
|
|
def _collect_expanded_items(self, item: QTreeWidgetItem, expanded_ids: list):
|
|
|
|
|
"""
|
|
|
|
|
Sammelt rekursiv die IDs aller aufgeklappten Items.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das zu prüfende TreeWidgetItem
|
|
|
|
|
expanded_ids: Liste zum Sammeln der IDs
|
|
|
|
|
"""
|
|
|
|
|
# Hole Node-Objekt
|
|
|
|
|
node = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not node:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Wenn Item aufgeklappt ist, speichere ID
|
|
|
|
|
if item.isExpanded() and hasattr(node, "id"):
|
|
|
|
|
expanded_ids.append(node.id)
|
|
|
|
|
|
|
|
|
|
# Rekursiv alle Kinder durchlaufen
|
|
|
|
|
child_count = item.childCount()
|
|
|
|
|
for i in range(child_count):
|
|
|
|
|
child_item = item.child(i)
|
|
|
|
|
self._collect_expanded_items(child_item, expanded_ids)
|
|
|
|
|
|
|
|
|
|
def _restore_expanded_state(self):
|
|
|
|
|
"""
|
|
|
|
|
Stellt den Expand-Status aller Knoten aus den Projekteinstellungen wieder her.
|
|
|
|
|
"""
|
|
|
|
|
if not hasattr(self, "pdf_project") or not self.pdf_project:
|
|
|
|
|
logger.warning("Keine Projekt-Einstellungen zum Wiederherstellen des Expand-Status")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self.pdf_project.expanded_nodes:
|
|
|
|
|
logger.debug("Keine gespeicherten Expand-Status-Informationen vorhanden")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
expanded_node_ids = set(self.pdf_project.expanded_nodes)
|
|
|
|
|
logger.info(f"Stelle Expand-Status wieder her: {len(expanded_node_ids)} Knoten")
|
|
|
|
|
logger.debug(f"Aufzuklappende Knoten-IDs: {list(expanded_node_ids)}")
|
|
|
|
|
|
|
|
|
|
# Durchlaufe alle Top-Level-Items
|
|
|
|
|
root_count = self.ui.treeWidget.topLevelItemCount()
|
|
|
|
|
for i in range(root_count):
|
|
|
|
|
item = self.ui.treeWidget.topLevelItem(i)
|
|
|
|
|
self._expand_items_by_id(item, expanded_node_ids)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Fehler beim Wiederherstellen des Expand-Status: {e}")
|
|
|
|
|
|
|
|
|
|
def _expand_items_by_id(self, item: QTreeWidgetItem, expanded_ids: set):
|
|
|
|
|
"""
|
|
|
|
|
Klappt Items rekursiv auf, wenn ihre ID in expanded_ids enthalten ist.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
item: Das zu prüfende TreeWidgetItem
|
|
|
|
|
expanded_ids: Set der IDs, die aufgeklappt werden sollen
|
|
|
|
|
"""
|
|
|
|
|
# Hole Node-Objekt
|
|
|
|
|
node = item.data(0, Qt.ItemDataRole.UserRole)
|
|
|
|
|
if not node or not hasattr(node, "id"):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Wenn ID in der Liste ist, klappe Item auf
|
|
|
|
|
if node.id in expanded_ids:
|
|
|
|
|
item.setExpanded(True)
|
|
|
|
|
|
|
|
|
|
# Rekursiv alle Kinder durchlaufen
|
|
|
|
|
child_count = item.childCount()
|
|
|
|
|
for i in range(child_count):
|
|
|
|
|
child_item = item.child(i)
|
|
|
|
|
self._expand_items_by_id(child_item, expanded_ids)
|