Library tree filled with items
This commit is contained in:
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import QApplication, QMessageBox
|
|||||||
from bedit_gui.controllers.clipboard_controller import ClipboardController, DocumentTreeClipboardHandler, GraphEditorClipboardHandler, TextClipboardHandler
|
from bedit_gui.controllers.clipboard_controller import ClipboardController, DocumentTreeClipboardHandler, GraphEditorClipboardHandler, TextClipboardHandler
|
||||||
from bedit_gui.controllers.document_controller import DocumentController
|
from bedit_gui.controllers.document_controller import DocumentController
|
||||||
from bedit_gui.controllers.log_controller import LogController
|
from bedit_gui.controllers.log_controller import LogController
|
||||||
|
from bedit_gui.controllers.library_controller import LibraryController
|
||||||
from bedit_gui.controllers.settings_controller import SettingsController
|
from bedit_gui.controllers.settings_controller import SettingsController
|
||||||
from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController
|
from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController
|
||||||
from bedit_gui.controllers.simulation_controller import SimulationController
|
from bedit_gui.controllers.simulation_controller import SimulationController
|
||||||
@@ -52,12 +53,14 @@ def main() -> int:
|
|||||||
|
|
||||||
LogController(window, settings.log_level)
|
LogController(window, settings.log_level)
|
||||||
DocumentController(document, window)
|
DocumentController(document, window)
|
||||||
SettingsController(window, settings)
|
settings_controller = SettingsController(window, settings)
|
||||||
SimulationSettingsController(document, window)
|
SimulationSettingsController(document, window)
|
||||||
SimulationController(document, window)
|
SimulationController(document, window)
|
||||||
UndoController(document, window)
|
UndoController(document, window)
|
||||||
ViewMenuController(window)
|
ViewMenuController(window)
|
||||||
document_tree_controller = DocumentTreeController(document, window)
|
document_tree_controller = DocumentTreeController(document, window)
|
||||||
|
library_controller = LibraryController(window, settings)
|
||||||
|
settings_controller.library_paths_changed.connect(library_controller.reload)
|
||||||
clipboard = ClipboardService(app)
|
clipboard = ClipboardService(app)
|
||||||
ClipboardController(window, clipboard, [TextClipboardHandler(clipboard), DocumentTreeClipboardHandler(document, window.ui.documentTree, document_tree_controller.model, clipboard), GraphEditorClipboardHandler(document, window.graph_editor, clipboard)])
|
ClipboardController(window, clipboard, [TextClipboardHandler(clipboard), DocumentTreeClipboardHandler(document, window.ui.documentTree, document_tree_controller.model, clipboard), GraphEditorClipboardHandler(document, window.graph_editor, clipboard)])
|
||||||
|
|
||||||
|
|||||||
48
src/bedit_gui/controllers/library_controller.py
Normal file
48
src/bedit_gui/controllers/library_controller.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from PySide6.QtCore import QObject, QSize
|
||||||
|
from PySide6.QtWidgets import QAbstractItemView, QHeaderView
|
||||||
|
|
||||||
|
from bedit_core.models import Component, ComponentID, GraphImplementation
|
||||||
|
from bedit_gui.models import Icon, IconDatabase
|
||||||
|
from bedit_gui.services.application_settings import ApplicationSettings
|
||||||
|
from bedit_gui.services.libraries import load_library_documents
|
||||||
|
from bedit_gui.utils.icon import render_fitted_icon
|
||||||
|
from bedit_gui.views.main_window import MainWindow
|
||||||
|
from bedit_gui.views.models.document_tree_model import DocumentTreeModel
|
||||||
|
|
||||||
|
ICON_SIZE = QSize(32, 32)
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryController(QObject):
|
||||||
|
def __init__(self, window: MainWindow, settings: ApplicationSettings) -> None:
|
||||||
|
super().__init__(window)
|
||||||
|
self.window = window
|
||||||
|
self.settings = settings
|
||||||
|
self.model = DocumentTreeModel(editable=False)
|
||||||
|
|
||||||
|
tree = window.ui.libraryTree
|
||||||
|
tree.setModel(self.model)
|
||||||
|
tree.setHeaderHidden(True)
|
||||||
|
tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
tree.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
|
tree.setIconSize(QSize(48, 48))
|
||||||
|
tree.header().setStretchLastSection(False)
|
||||||
|
tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
|
||||||
|
tree.header().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
|
||||||
|
tree.setColumnWidth(1, 56)
|
||||||
|
|
||||||
|
self.reload()
|
||||||
|
|
||||||
|
def reload(self) -> None:
|
||||||
|
libraries = load_library_documents(self.settings.library_paths)
|
||||||
|
self.model.set_documents([library.document for library in libraries])
|
||||||
|
for library in libraries:
|
||||||
|
database = library.document.metadata.get("icon_database") if library.document.metadata is not None else None
|
||||||
|
icons = database.icons if isinstance(database, IconDatabase) else {}
|
||||||
|
self._set_component_icons(library.document.root, icons)
|
||||||
|
self.window.ui.libraryTree.expandAll()
|
||||||
|
|
||||||
|
def _set_component_icons(self, components: dict[ComponentID, Component], icons: dict[ComponentID, Icon]) -> None:
|
||||||
|
for component_id, component in components.items():
|
||||||
|
self.model.set_component_icon(component_id, render_fitted_icon(icons.get(component_id, Icon()), component.interface.ports, ICON_SIZE))
|
||||||
|
if isinstance(component.implementation, GraphImplementation):
|
||||||
|
self._set_component_icons(component.implementation.graph.components, icons)
|
||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
from PySide6.QtCore import QObject
|
from PySide6.QtCore import QObject, Signal
|
||||||
from PySide6.QtWidgets import QDialog
|
from PySide6.QtWidgets import QDialog
|
||||||
|
|
||||||
from bedit_gui.services.application_logging import get_logger, set_log_level
|
from bedit_gui.services.application_logging import get_logger, set_log_level
|
||||||
@@ -33,6 +33,8 @@ SettingsDialogFactory = Callable[[int, int, list[str], MainWindow], SettingsDial
|
|||||||
class SettingsController(QObject):
|
class SettingsController(QObject):
|
||||||
"""Opens the settings dialog and applies accepted preferences."""
|
"""Opens the settings dialog and applies accepted preferences."""
|
||||||
|
|
||||||
|
library_paths_changed = Signal()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
window: MainWindow,
|
window: MainWindow,
|
||||||
@@ -51,9 +53,12 @@ class SettingsController(QObject):
|
|||||||
if dialog.exec() != QDialog.DialogCode.Accepted:
|
if dialog.exec() != QDialog.DialogCode.Accepted:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
old_library_paths = self.settings.library_paths
|
||||||
self.settings.log_level = dialog.log_level
|
self.settings.log_level = dialog.log_level
|
||||||
self.settings.snap_to_grid_size = dialog.snap_to_grid_size
|
self.settings.snap_to_grid_size = dialog.snap_to_grid_size
|
||||||
self.settings.library_paths = dialog.library_paths
|
self.settings.library_paths = dialog.library_paths
|
||||||
self.window.graph_editor.set_snap_to_grid_size(dialog.snap_to_grid_size)
|
self.window.graph_editor.set_snap_to_grid_size(dialog.snap_to_grid_size)
|
||||||
set_log_level(dialog.log_level)
|
set_log_level(dialog.log_level)
|
||||||
|
if self.settings.library_paths != old_library_paths:
|
||||||
|
self.library_paths_changed.emit()
|
||||||
logger.info("Application settings updated")
|
logger.info("Application settings updated")
|
||||||
|
|||||||
42
src/bedit_gui/services/libraries.py
Normal file
42
src/bedit_gui/services/libraries.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from bedit_core.models import Document
|
||||||
|
from bedit_gui.services import document_files
|
||||||
|
from bedit_gui.services.application_logging import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LoadedLibrary:
|
||||||
|
path: Path
|
||||||
|
document: Document
|
||||||
|
|
||||||
|
|
||||||
|
def list_library_files(library_paths: list[str]) -> list[Path]:
|
||||||
|
files = []
|
||||||
|
seen = set()
|
||||||
|
for configured_path in library_paths:
|
||||||
|
path = Path(configured_path).expanduser()
|
||||||
|
candidates = [path] if path.is_file() else sorted(path.rglob("*"), key=lambda candidate: str(candidate).casefold()) if path.is_dir() else []
|
||||||
|
for candidate in candidates:
|
||||||
|
if not candidate.is_file() or candidate.suffix.lower() not in (".beb", ".json"):
|
||||||
|
continue
|
||||||
|
resolved = candidate.resolve()
|
||||||
|
if resolved not in seen:
|
||||||
|
seen.add(resolved)
|
||||||
|
files.append(resolved)
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def load_library_documents(library_paths: list[str]) -> list[LoadedLibrary]:
|
||||||
|
libraries = []
|
||||||
|
for path in list_library_files(library_paths):
|
||||||
|
try:
|
||||||
|
libraries.append(LoadedLibrary(path, document_files.load(path)))
|
||||||
|
except (KeyError, OSError, TypeError, ValueError) as exc:
|
||||||
|
logger.warning("Could not load library %s: %s", path, exc)
|
||||||
|
return libraries
|
||||||
@@ -22,19 +22,21 @@ class DocumentTreeModel(QAbstractItemModel):
|
|||||||
rename_document_requested = Signal(str)
|
rename_document_requested = Signal(str)
|
||||||
rename_component_requested = Signal(Component, str)
|
rename_component_requested = Signal(Component, str)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, editable: bool = True) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._document: CoreDocument | None = None
|
self._editable = editable
|
||||||
self._root = DocumentTreeNode("Document", None, None, None, [])
|
self._root = DocumentTreeNode("Document", None, None, None, [])
|
||||||
self._component_icons: dict[ComponentID, QIcon] = {}
|
self._component_icons: dict[ComponentID, QIcon] = {}
|
||||||
self._component_nodes: dict[ComponentID, DocumentTreeNode] = {}
|
self._component_nodes: dict[ComponentID, DocumentTreeNode] = {}
|
||||||
|
|
||||||
def set_document(self, document: CoreDocument) -> None:
|
def set_document(self, document: CoreDocument) -> None:
|
||||||
|
self.set_documents([document])
|
||||||
|
|
||||||
|
def set_documents(self, documents: list[CoreDocument]) -> None:
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
self._document = document
|
|
||||||
self._component_icons = {}
|
self._component_icons = {}
|
||||||
self._component_nodes = {}
|
self._component_nodes = {}
|
||||||
self._root = self._build_tree(document)
|
self._root = self._build_tree(documents)
|
||||||
self.endResetModel()
|
self.endResetModel()
|
||||||
|
|
||||||
def set_component_icon(self, component_id: ComponentID, icon: QIcon) -> None:
|
def set_component_icon(self, component_id: ComponentID, icon: QIcon) -> None:
|
||||||
@@ -146,12 +148,12 @@ class DocumentTreeModel(QAbstractItemModel):
|
|||||||
node = index.internalPointer()
|
node = index.internalPointer()
|
||||||
|
|
||||||
# Make the document root node editable
|
# Make the document root node editable
|
||||||
if index.column() == 0 and isinstance(node, DocumentTreeNode) and isinstance(node.value, (CoreDocument, Component)):
|
if self._editable and index.column() == 0 and isinstance(node, DocumentTreeNode) and isinstance(node.value, (CoreDocument, Component)):
|
||||||
flags |= Qt.ItemFlag.ItemIsEditable
|
flags |= Qt.ItemFlag.ItemIsEditable
|
||||||
|
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
def _build_tree(self, document: CoreDocument) -> DocumentTreeNode:
|
def _build_tree(self, documents: list[CoreDocument]) -> DocumentTreeNode:
|
||||||
# QT's invisible root
|
# QT's invisible root
|
||||||
root = DocumentTreeNode(
|
root = DocumentTreeNode(
|
||||||
name="",
|
name="",
|
||||||
@@ -160,19 +162,18 @@ class DocumentTreeModel(QAbstractItemModel):
|
|||||||
parent=None,
|
parent=None,
|
||||||
children=[],
|
children=[],
|
||||||
)
|
)
|
||||||
# Add itself as a child so the document root is visible in the tree
|
def _list_children(parent: DocumentTreeNode, components: dict[ComponentID, Component]) -> None:
|
||||||
document_root = DocumentTreeNode(name=document.name, value=document, component_id=None, parent=root, children=[])
|
|
||||||
root.children.append(document_root)
|
|
||||||
|
|
||||||
def _list_children(root: DocumentTreeNode, components: dict[ComponentID, Component]) -> None:
|
|
||||||
for component_id, component in sorted(components.items(), key=lambda item: (item[1].name.casefold(), item[1].name, str(item[0]))):
|
for component_id, component in sorted(components.items(), key=lambda item: (item[1].name.casefold(), item[1].name, str(item[0]))):
|
||||||
component_node = DocumentTreeNode(name=component.name, value=component, component_id=component_id, parent=root, children=[])
|
component_node = DocumentTreeNode(name=component.name, value=component, component_id=component_id, parent=parent, children=[])
|
||||||
root.children.append(component_node)
|
parent.children.append(component_node)
|
||||||
self._component_nodes[component_id] = component_node
|
self._component_nodes[component_id] = component_node
|
||||||
|
|
||||||
if isinstance(component.implementation, GraphImplementation):
|
if isinstance(component.implementation, GraphImplementation):
|
||||||
_list_children(component_node, component.implementation.graph.components)
|
_list_children(component_node, component.implementation.graph.components)
|
||||||
|
|
||||||
_list_children(document_root, document.root)
|
for document in documents:
|
||||||
|
document_root = DocumentTreeNode(name=document.name, value=document, component_id=None, parent=root, children=[])
|
||||||
|
root.children.append(document_root)
|
||||||
|
_list_children(document_root, document.root)
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
|||||||
Reference in New Issue
Block a user