Feat: Suchfilter für TreeNodes und XSL-Dateien im Hauptfenster (v1.3.0)

Neues Suchfeld über dem Baum filtert Knoten und XSL-Dateien per
case-insensitive Textsuche. Übergeordnete Knoten bleiben bei
Kind-Treffern sichtbar und werden automatisch expandiert. Der
gespeicherte Expand-Status wird beim Leeren der Suche wiederhergestellt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 19:54:20 +02:00
parent 0560dbafe4
commit d1def05607
9 changed files with 136 additions and 11 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<!-- Paket-Definition (ersetzt Product in v4) -->
<Package
Name="DocuMentor"
Version="1.2.8"
Version="1.3.0"
Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
Language="1031"
+1 -1
View File
@@ -253,5 +253,5 @@ HINWEISE
================================================================================
Stand: April 2026
Erstellt für: DocuMentor v1.2.8
Erstellt für: DocuMentor v1.3.0
================================================================================
+1 -1
View File
@@ -10,7 +10,7 @@
; Build-Befehl: iscc installer.iss
#define MyAppName "DocuMentor"
#define MyAppVersion "1.2.8"
#define MyAppVersion "1.3.0"
#define MyAppPublisher "Ihr Name/Organisation"
#define MyAppURL "https://github.com/yourusername/xsl-validator"
#define MyAppExeName "DocuMentor.exe"
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "DocuMentor"
version = "1.2.8"
version = "1.3.0"
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
readme = "README.md"
license = {text = "MIT"}
+10
View File
@@ -71,6 +71,16 @@
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Knoten oder XSL-Datei filtern...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="sizePolicy">
+11 -4
View File
@@ -17,10 +17,10 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
QPainter, QPalette, QPixmap, QRadialGradient,
QTransform)
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QHeaderView,
QLabel, QMainWindow, QMenu, QMenuBar,
QPushButton, QScrollArea, QSizePolicy, QSlider,
QSpacerItem, QSplitter, QStatusBar, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget)
QLabel, QLineEdit, QMainWindow, QMenu,
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
QSlider, QSpacerItem, QSplitter, QStatusBar,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
@@ -83,6 +83,12 @@ class Ui_MainWindow(object):
self.verticalLayout.addWidget(self.projectPath)
self.searchEdit = QLineEdit(self.frame)
self.searchEdit.setObjectName(u"searchEdit")
self.searchEdit.setClearButtonEnabled(True)
self.verticalLayout.addWidget(self.searchEdit)
self.treeWidget = QTreeWidget(self.frame)
__qtreewidgetitem = QTreeWidgetItem()
__qtreewidgetitem.setText(2, u"3");
@@ -295,6 +301,7 @@ class Ui_MainWindow(object):
self.actionFN2.setText(QCoreApplication.translate("MainWindow", u"FN2", None))
self.actionAus_Datenbank_laden.setText(QCoreApplication.translate("MainWindow", u"Aus Datenbank laden", None))
self.projectPath.setText(QCoreApplication.translate("MainWindow", u"Kein Projekt geladen", None))
self.searchEdit.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Knoten oder XSL-Datei filtern...", None))
self.label.setText("")
self.label_2.setText("")
self.view_ref_pdf.setText(QCoreApplication.translate("MainWindow", u"Vorher (Referenz)", None))
+3
View File
@@ -356,6 +356,9 @@ class MainWindow(
self.ui.alpha.valueChanged.connect(self.on_alpha_changed)
self.ui.alpha.mouseDoubleClickEvent = lambda event: self.ui.alpha.setValue(0)
# Suchfeld für Tree-Filter verbinden
self.ui.searchEdit.textChanged.connect(self._filter_tree)
# Menü-Aktionen verbinden
self.ui.actionNeu.triggered.connect(self.open_new_project_dialog)
self.ui.actionEinstellungen.triggered.connect(self.open_settings_dialog)
+107 -2
View File
@@ -484,9 +484,112 @@ class TreeManagerMixin:
# Stelle Expand-Status wieder her
self._restore_expanded_state()
# Suchfilter erneut anwenden, falls aktiv
search_text = self.ui.searchEdit.text()
if search_text:
self._filter_tree(search_text)
except Exception as e:
logger.error(f"Fehler beim Laden der Nodes in TreeWidget: {e}")
def _filter_tree(self, text: str):
"""
Filtert das TreeWidget nach TreeNode- und XslFile-Bezeichnungen.
Sichtbarkeitsregeln:
- TreeNode/XslFile wird angezeigt, wenn .bez den Suchtext enthält (case-insensitive)
- Wenn ein Kind matcht, bleibt der übergeordnete Knoten sichtbar und wird expandiert
- Wenn ein TreeNode matcht, bleiben alle Kinder sichtbar
- XmlFile-Items folgen der Sichtbarkeit ihres Eltern-XslFile
- Leerer Suchtext blendet alles ein und stellt den Expand-Status wieder her
Args:
text: Suchtext für den Filter
"""
search_lower = text.strip().lower()
if not search_lower:
# Alles einblenden, kollabieren und gespeicherten Expand-Status wiederherstellen
for i in range(self.ui.treeWidget.topLevelItemCount()):
item = self.ui.treeWidget.topLevelItem(i)
self._reset_filter_recursive(item)
self._restore_expanded_state()
logger.debug("Baumfilter zurückgesetzt")
return
for i in range(self.ui.treeWidget.topLevelItemCount()):
item = self.ui.treeWidget.topLevelItem(i)
self._apply_filter_recursive(item, search_lower)
logger.debug(f"Baumfilter angewendet: '{text}'")
def _apply_filter_recursive(self, item: QTreeWidgetItem, search_lower: str) -> bool:
"""
Wendet den Suchfilter rekursiv auf ein Item und seine Kinder an.
Args:
item: Das zu prüfende TreeWidgetItem
search_lower: Suchtext in Kleinbuchstaben
Returns:
bool: True wenn dieses Item oder ein Kind den Suchtext enthält
"""
node = item.data(0, Qt.ItemDataRole.UserRole)
# Prüfe ob dieses Item selbst matcht (nur TreeNode und XslFile haben .bez)
self_matches = False
if isinstance(node, (TreeNode, XslFile)):
bez_text = str(node.bez).lower() if node.bez else ""
self_matches = search_lower in bez_text
# Wenn dieses Item matcht, sichtbar machen und alle Kinder einblenden
if self_matches:
item.setHidden(False)
self._set_item_visible_recursive(item, True)
item.setExpanded(True)
return True
# Prüfe Kinder rekursiv
any_child_matches = False
for child_idx in range(item.childCount()):
child = item.child(child_idx)
if self._apply_filter_recursive(child, search_lower):
any_child_matches = True
# Item sichtbar lassen wenn ein Kind matcht, expandieren für Übersicht
if any_child_matches:
item.setHidden(False)
item.setExpanded(True)
else:
item.setHidden(True)
return any_child_matches
def _set_item_visible_recursive(self, item: QTreeWidgetItem, visible: bool):
"""
Setzt die Sichtbarkeit eines Items und aller seiner Kinder.
Args:
item: Das TreeWidgetItem
visible: Ob das Item sichtbar sein soll
"""
item.setHidden(not visible)
for child_idx in range(item.childCount()):
self._set_item_visible_recursive(item.child(child_idx), visible)
def _reset_filter_recursive(self, item: QTreeWidgetItem):
"""
Blendet ein Item und alle Kinder ein und kollabiert sie.
Kombiniert Sichtbarkeit und Expand-Reset in einem Durchlauf.
Args:
item: Das TreeWidgetItem
"""
item.setHidden(False)
item.setExpanded(False)
for child_idx in range(item.childCount()):
self._reset_filter_recursive(item.child(child_idx))
def _create_tree_item_from_node(self, node):
"""
Erstellt ein QTreeWidgetItem aus einem TreeNode oder XslFile.
@@ -1622,8 +1725,10 @@ class TreeManagerMixin:
start_time = time.time()
# Speichere Expand-Status der Tree-Knoten vor dem Schreiben
self._save_expanded_state()
# Expand-Status nur speichern, wenn kein Suchfilter aktiv ist
# (sonst werden die durch die Suche erzwungenen Expansionen gespeichert)
if not self.ui.searchEdit.text().strip():
self._save_expanded_state()
# Speichere in project.yaml im Projekt-Verzeichnis
self.pdf_project.writeSettings(project_dir=self.project.project_dir)
Generated
+1 -1
View File
@@ -34,7 +34,7 @@ wheels = [
[[package]]
name = "documentor"
version = "1.2.8"
version = "1.3.0"
source = { virtual = "." }
dependencies = [
{ name = "connectorx" },