From a96bde9642ad6ad8c231b262f5639f4fa98ce3f6 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 26 Jul 2026 15:25:51 +0200 Subject: [PATCH] Document tree panel --- src/bedit_gui/application.py | 2 + .../controllers/document_tree_controller.py | 34 +++++ .../views/models/document_tree_model.py | 123 ++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 src/bedit_gui/controllers/document_tree_controller.py create mode 100644 src/bedit_gui/views/models/document_tree_model.py diff --git a/src/bedit_gui/application.py b/src/bedit_gui/application.py index 2866083..b6fd4a1 100644 --- a/src/bedit_gui/application.py +++ b/src/bedit_gui/application.py @@ -10,6 +10,7 @@ from bedit_gui.controllers.settings_controller import SettingsController from bedit_gui.controllers.undo_controller import UndoController from bedit_gui.controllers.view_menu_controller import ViewMenuController from bedit_gui.controllers.window_state_controller import WindowStateController +from bedit_gui.controllers.document_tree_controller import DocumentTreeController from bedit_gui.documents import Document from bedit_gui.services.application_settings import ApplicationSettings from bedit_gui.views.main_window import MainWindow @@ -29,6 +30,7 @@ def main() -> int: SettingsController(window, settings) UndoController(document, window) ViewMenuController(window) + DocumentTreeController(document, window) window_state_controller = WindowStateController(app, window) window_state_controller.restore() window.showMaximized() diff --git a/src/bedit_gui/controllers/document_tree_controller.py b/src/bedit_gui/controllers/document_tree_controller.py new file mode 100644 index 0000000..4b6d82b --- /dev/null +++ b/src/bedit_gui/controllers/document_tree_controller.py @@ -0,0 +1,34 @@ +from PySide6.QtCore import QObject + +from bedit_core.models import Document as CoreDocument +from bedit_gui.documents import Document +from bedit_gui.views.main_window import MainWindow +from bedit_gui.views.models.document_tree_model import DocumentTreeModel + + +class DocumentTreeController(QObject): + def __init__( + self, + document: Document, + window: MainWindow, + ) -> None: + super().__init__(window) + + self.document = document + self.window = window + self.model = DocumentTreeModel() + + window.ui.documentTree.setModel(self.model) + document.model_changed.connect(self._on_document_changed) + + window.ui.documentTree.setHeaderHidden(True) + + self._on_document_changed(document.model) + + def _on_document_changed(self, model: CoreDocument) -> None: + """Rebuild the tree whenever New/Open replaces the core document.""" + self.model.set_document(model) + + # Optional presentation behavior. Later, you could instead remember + # expanded component IDs and restore only those nodes. + self.window.ui.documentTree.expandAll() diff --git a/src/bedit_gui/views/models/document_tree_model.py b/src/bedit_gui/views/models/document_tree_model.py new file mode 100644 index 0000000..876535c --- /dev/null +++ b/src/bedit_gui/views/models/document_tree_model.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt + +from bedit_core.models import Document as CoreDocument, ComponentID, Component, GraphImplementation + + +@dataclass +class DocumentTreeNode: + name: str + value: object + parent: DocumentTreeNode | None + children: list[DocumentTreeNode] + + +class DocumentTreeModel(QAbstractItemModel): + def __init__(self) -> None: + super().__init__() + self._document: CoreDocument | None = None + self._root = DocumentTreeNode("Document", None, None, []) + + def set_document(self, document: CoreDocument) -> None: + self.beginResetModel() + self._document = document + self._root = self._build_tree(document) + self.endResetModel() + + def rowCount(self, parent: QModelIndex | None = None) -> int: + return len(self._node(parent).children) + + def columnCount(self, _parent: QModelIndex | None = None) -> int: + return 1 + + def index( + self, + row: int, + column: int, + parent: QModelIndex | None = None, + ) -> QModelIndex: + parent_node = self._node(parent) + if ( + column != 0 + or row < 0 + or row >= len(parent_node.children) + ): + return QModelIndex() + return self.createIndex(row, column, parent_node.children[row]) + + def parent(self, index: QModelIndex) -> QModelIndex: + if not index.isValid(): + return QModelIndex() + + node = index.internalPointer() + if not isinstance(node, DocumentTreeNode): + return QModelIndex() + + parent_node = node.parent + if parent_node is None or parent_node is self._root: + return QModelIndex() + + grandparent = parent_node.parent + if grandparent is None: + return QModelIndex() + row = grandparent.children.index(parent_node) + return self.createIndex(row, 0, parent_node) + + def data( + self, + index: QModelIndex, + role: int = Qt.ItemDataRole.DisplayRole, + ) -> object | None: + if not index.isValid(): + return None + + node = index.internalPointer() + if ( + role == Qt.ItemDataRole.DisplayRole + and isinstance(node, DocumentTreeNode) + ): + return node.name + return None + + def _node(self, index: QModelIndex | None) -> DocumentTreeNode: + if index is None or not index.isValid(): + return self._root + + node = index.internalPointer() + return node if isinstance(node, DocumentTreeNode) else self._root + + def _build_tree(self, document: CoreDocument) -> DocumentTreeNode: + # QT's invisible root + root = DocumentTreeNode( + name="", + value=None, + parent=None, + children=[], + ) + # Add itself as a child so the document root is visible in the tree + document_root = DocumentTreeNode( + name = document.name, + value = document, + parent = root, + children = [] + ) + root.children.append(document_root) + + def _list_children(root: DocumentTreeNode, components: dict[ComponentID, Component]) -> None: + for component_id, component in components.items(): + component_node = DocumentTreeNode( + name = component.name, + value = (component_id, component), + parent = root, + children = [] + ) + root.children.append(component_node) + + if isinstance(component.implementation, GraphImplementation): + _list_children(component_node, component.implementation.graph.components) + _list_children(document_root, document.root) + + return root