diff --git a/DocuMentor.wxs b/DocuMentor.wxs index 4dcccf7..22cffcd 100644 --- a/DocuMentor.wxs +++ b/DocuMentor.wxs @@ -4,7 +4,7 @@ + + + + Knoten oder XSL-Datei filtern... + + + true + + + diff --git a/src/ui/MainWinddow_ui.py b/src/ui/MainWinddow_ui.py index 3456478..c07df68 100644 --- a/src/ui/MainWinddow_ui.py +++ b/src/ui/MainWinddow_ui.py @@ -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)) diff --git a/src/ui/MainWindow.py b/src/ui/MainWindow.py index c54156a..e6c1330 100644 --- a/src/ui/MainWindow.py +++ b/src/ui/MainWindow.py @@ -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) diff --git a/src/ui/mixins/tree_manager.py b/src/ui/mixins/tree_manager.py index 8150567..f233268 100644 --- a/src/ui/mixins/tree_manager.py +++ b/src/ui/mixins/tree_manager.py @@ -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) diff --git a/uv.lock b/uv.lock index 83a5053..02c5dcb 100644 --- a/uv.lock +++ b/uv.lock @@ -34,7 +34,7 @@ wheels = [ [[package]] name = "documentor" -version = "1.2.8" +version = "1.3.0" source = { virtual = "." } dependencies = [ { name = "connectorx" },