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:
+1
-1
@@ -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"
|
||||
|
||||
@@ -253,5 +253,5 @@ HINWEISE
|
||||
|
||||
================================================================================
|
||||
Stand: April 2026
|
||||
Erstellt für: DocuMentor v1.2.8
|
||||
Erstellt für: DocuMentor v1.3.0
|
||||
================================================================================
|
||||
|
||||
+1
-1
@@ -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
@@ -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"}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,7 +1725,9 @@ class TreeManagerMixin:
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Speichere Expand-Status der Tree-Knoten vor dem Schreiben
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user