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) -->
|
<!-- Paket-Definition (ersetzt Product in v4) -->
|
||||||
<Package
|
<Package
|
||||||
Name="DocuMentor"
|
Name="DocuMentor"
|
||||||
Version="1.2.8"
|
Version="1.3.0"
|
||||||
Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
|
Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
|
||||||
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
|
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
|
||||||
Language="1031"
|
Language="1031"
|
||||||
|
|||||||
@@ -253,5 +253,5 @@ HINWEISE
|
|||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
Stand: April 2026
|
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
|
; Build-Befehl: iscc installer.iss
|
||||||
|
|
||||||
#define MyAppName "DocuMentor"
|
#define MyAppName "DocuMentor"
|
||||||
#define MyAppVersion "1.2.8"
|
#define MyAppVersion "1.3.0"
|
||||||
#define MyAppPublisher "Ihr Name/Organisation"
|
#define MyAppPublisher "Ihr Name/Organisation"
|
||||||
#define MyAppURL "https://github.com/yourusername/xsl-validator"
|
#define MyAppURL "https://github.com/yourusername/xsl-validator"
|
||||||
#define MyAppExeName "DocuMentor.exe"
|
#define MyAppExeName "DocuMentor.exe"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "DocuMentor"
|
name = "DocuMentor"
|
||||||
version = "1.2.8"
|
version = "1.3.0"
|
||||||
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
|
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|||||||
@@ -71,6 +71,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<widget class="QTreeWidget" name="treeWidget">
|
<widget class="QTreeWidget" name="treeWidget">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
|||||||
QPainter, QPalette, QPixmap, QRadialGradient,
|
QPainter, QPalette, QPixmap, QRadialGradient,
|
||||||
QTransform)
|
QTransform)
|
||||||
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QHeaderView,
|
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QHeaderView,
|
||||||
QLabel, QMainWindow, QMenu, QMenuBar,
|
QLabel, QLineEdit, QMainWindow, QMenu,
|
||||||
QPushButton, QScrollArea, QSizePolicy, QSlider,
|
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
|
||||||
QSpacerItem, QSplitter, QStatusBar, QTreeWidget,
|
QSlider, QSpacerItem, QSplitter, QStatusBar,
|
||||||
QTreeWidgetItem, QVBoxLayout, QWidget)
|
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget)
|
||||||
|
|
||||||
class Ui_MainWindow(object):
|
class Ui_MainWindow(object):
|
||||||
def setupUi(self, MainWindow):
|
def setupUi(self, MainWindow):
|
||||||
@@ -83,6 +83,12 @@ class Ui_MainWindow(object):
|
|||||||
|
|
||||||
self.verticalLayout.addWidget(self.projectPath)
|
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)
|
self.treeWidget = QTreeWidget(self.frame)
|
||||||
__qtreewidgetitem = QTreeWidgetItem()
|
__qtreewidgetitem = QTreeWidgetItem()
|
||||||
__qtreewidgetitem.setText(2, u"3");
|
__qtreewidgetitem.setText(2, u"3");
|
||||||
@@ -295,6 +301,7 @@ class Ui_MainWindow(object):
|
|||||||
self.actionFN2.setText(QCoreApplication.translate("MainWindow", u"FN2", None))
|
self.actionFN2.setText(QCoreApplication.translate("MainWindow", u"FN2", None))
|
||||||
self.actionAus_Datenbank_laden.setText(QCoreApplication.translate("MainWindow", u"Aus Datenbank laden", 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.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.setText("")
|
||||||
self.label_2.setText("")
|
self.label_2.setText("")
|
||||||
self.view_ref_pdf.setText(QCoreApplication.translate("MainWindow", u"Vorher (Referenz)", None))
|
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.valueChanged.connect(self.on_alpha_changed)
|
||||||
self.ui.alpha.mouseDoubleClickEvent = lambda event: self.ui.alpha.setValue(0)
|
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
|
# Menü-Aktionen verbinden
|
||||||
self.ui.actionNeu.triggered.connect(self.open_new_project_dialog)
|
self.ui.actionNeu.triggered.connect(self.open_new_project_dialog)
|
||||||
self.ui.actionEinstellungen.triggered.connect(self.open_settings_dialog)
|
self.ui.actionEinstellungen.triggered.connect(self.open_settings_dialog)
|
||||||
|
|||||||
@@ -484,9 +484,112 @@ class TreeManagerMixin:
|
|||||||
# Stelle Expand-Status wieder her
|
# Stelle Expand-Status wieder her
|
||||||
self._restore_expanded_state()
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Laden der Nodes in TreeWidget: {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):
|
def _create_tree_item_from_node(self, node):
|
||||||
"""
|
"""
|
||||||
Erstellt ein QTreeWidgetItem aus einem TreeNode oder XslFile.
|
Erstellt ein QTreeWidgetItem aus einem TreeNode oder XslFile.
|
||||||
@@ -1622,7 +1725,9 @@ class TreeManagerMixin:
|
|||||||
|
|
||||||
start_time = time.time()
|
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()
|
self._save_expanded_state()
|
||||||
|
|
||||||
# Speichere in project.yaml im Projekt-Verzeichnis
|
# Speichere in project.yaml im Projekt-Verzeichnis
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "documentor"
|
name = "documentor"
|
||||||
version = "1.2.8"
|
version = "1.3.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "connectorx" },
|
{ name = "connectorx" },
|
||||||
|
|||||||
Reference in New Issue
Block a user