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.document_controller import DocumentController
|
||||
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.simulation_settings_controller import SimulationSettingsController
|
||||
from bedit_gui.controllers.simulation_controller import SimulationController
|
||||
@@ -52,12 +53,14 @@ def main() -> int:
|
||||
|
||||
LogController(window, settings.log_level)
|
||||
DocumentController(document, window)
|
||||
SettingsController(window, settings)
|
||||
settings_controller = SettingsController(window, settings)
|
||||
SimulationSettingsController(document, window)
|
||||
SimulationController(document, window)
|
||||
UndoController(document, window)
|
||||
ViewMenuController(window)
|
||||
document_tree_controller = DocumentTreeController(document, window)
|
||||
library_controller = LibraryController(window, settings)
|
||||
settings_controller.library_paths_changed.connect(library_controller.reload)
|
||||
clipboard = ClipboardService(app)
|
||||
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 typing import Protocol
|
||||
|
||||
from PySide6.QtCore import QObject
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from PySide6.QtWidgets import QDialog
|
||||
|
||||
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):
|
||||
"""Opens the settings dialog and applies accepted preferences."""
|
||||
|
||||
library_paths_changed = Signal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
window: MainWindow,
|
||||
@@ -51,9 +53,12 @@ class SettingsController(QObject):
|
||||
if dialog.exec() != QDialog.DialogCode.Accepted:
|
||||
return
|
||||
|
||||
old_library_paths = self.settings.library_paths
|
||||
self.settings.log_level = dialog.log_level
|
||||
self.settings.snap_to_grid_size = dialog.snap_to_grid_size
|
||||
self.settings.library_paths = dialog.library_paths
|
||||
self.window.graph_editor.set_snap_to_grid_size(dialog.snap_to_grid_size)
|
||||
set_log_level(dialog.log_level)
|
||||
if self.settings.library_paths != old_library_paths:
|
||||
self.library_paths_changed.emit()
|
||||
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_component_requested = Signal(Component, str)
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, editable: bool = True) -> None:
|
||||
super().__init__()
|
||||
self._document: CoreDocument | None = None
|
||||
self._editable = editable
|
||||
self._root = DocumentTreeNode("Document", None, None, None, [])
|
||||
self._component_icons: dict[ComponentID, QIcon] = {}
|
||||
self._component_nodes: dict[ComponentID, DocumentTreeNode] = {}
|
||||
|
||||
def set_document(self, document: CoreDocument) -> None:
|
||||
self.set_documents([document])
|
||||
|
||||
def set_documents(self, documents: list[CoreDocument]) -> None:
|
||||
self.beginResetModel()
|
||||
self._document = document
|
||||
self._component_icons = {}
|
||||
self._component_nodes = {}
|
||||
self._root = self._build_tree(document)
|
||||
self._root = self._build_tree(documents)
|
||||
self.endResetModel()
|
||||
|
||||
def set_component_icon(self, component_id: ComponentID, icon: QIcon) -> None:
|
||||
@@ -146,12 +148,12 @@ class DocumentTreeModel(QAbstractItemModel):
|
||||
node = index.internalPointer()
|
||||
|
||||
# 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
|
||||
|
||||
return flags
|
||||
|
||||
def _build_tree(self, document: CoreDocument) -> DocumentTreeNode:
|
||||
def _build_tree(self, documents: list[CoreDocument]) -> DocumentTreeNode:
|
||||
# QT's invisible root
|
||||
root = DocumentTreeNode(
|
||||
name="",
|
||||
@@ -160,19 +162,18 @@ class DocumentTreeModel(QAbstractItemModel):
|
||||
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, component_id=None, parent=root, children=[])
|
||||
root.children.append(document_root)
|
||||
|
||||
def _list_children(root: DocumentTreeNode, components: dict[ComponentID, Component]) -> None:
|
||||
def _list_children(parent: 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]))):
|
||||
component_node = DocumentTreeNode(name=component.name, value=component, component_id=component_id, parent=root, children=[])
|
||||
root.children.append(component_node)
|
||||
component_node = DocumentTreeNode(name=component.name, value=component, component_id=component_id, parent=parent, children=[])
|
||||
parent.children.append(component_node)
|
||||
self._component_nodes[component_id] = component_node
|
||||
|
||||
if isinstance(component.implementation, GraphImplementation):
|
||||
_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
|
||||
|
||||
Reference in New Issue
Block a user