Source code for pySimBlocks.gui.widgets.block_list
# ******************************************************************************
# pySimBlocks
# Copyright (c) 2026 Université de Lille & INRIA
# ******************************************************************************
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ******************************************************************************
# Authors: see Authors.txt
# ******************************************************************************
from typing import Callable
from PySide6.QtWidgets import QLineEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
from PySide6.QtCore import Qt, QMimeData
from PySide6.QtGui import QDrag
from pySimBlocks.gui.blocks.block_meta import BlockMeta
from pySimBlocks.gui.dialogs.block_dialog import BlockDialog
from pySimBlocks.gui.models.block_instance import BlockInstance
class _PreviewBlock:
"""Minimal preview wrapper used by the block dialog."""
def __init__(self, instance):
"""Initialize the preview wrapper."""
self.instance = instance
def refresh_ports(self):
"""No-op refresh hook for the preview block."""
pass
class _BlockTree(QTreeWidget):
"""Tree widget listing block categories and block types."""
def __init__(self,
get_categories: Callable[[], list[str]],
get_blocks: Callable[[str], list[str]],
resolve_block_meta: Callable[[str, str], BlockMeta]):
"""Initialize the block tree.
Args:
get_categories: Callable returning available block categories.
get_blocks: Callable returning block types for a category.
resolve_block_meta: Callable resolving block metadata from names.
Raises:
None.
"""
super().__init__()
self.setHeaderHidden(True)
self.setDragEnabled(True)
self.get_categories = get_categories
self.get_blocks = get_blocks
self.resolve_block_meta = resolve_block_meta
for category in self.get_categories():
operators = QTreeWidgetItem(self, [category])
operators.setExpanded(False)
for block_type in self.get_blocks(category):
block = QTreeWidgetItem(operators, [block_type])
block.setData(0, Qt.UserRole, (category, block_type))
self.itemDoubleClicked.connect(self.on_item_double_clicked)
# --------------------------------------------------------------------------
# Public Methods
# --------------------------------------------------------------------------
def startDrag(self, supportedActions):
"""Start a drag operation for the currently selected block item."""
item = self.currentItem()
if not item or item.childCount() > 0:
return
category, block_type = item.data(0, Qt.UserRole)
mime = QMimeData()
mime.setText(f"{category}:{block_type}")
drag = QDrag(self)
drag.setMimeData(mime)
drag.exec(Qt.MoveAction)
def on_item_double_clicked(self, item, column):
"""Open a read-only preview dialog for a leaf block item."""
if item.childCount() > 0:
return
data = item.data(0, Qt.UserRole)
if not data:
return
category, block_type = data
meta = self.resolve_block_meta(category, block_type)
instance = BlockInstance(meta=meta)
dialog = BlockDialog(_PreviewBlock(instance), readonly=True)
dialog.exec()
[docs]
class BlockList(QWidget):
"""Sidebar widget listing blocks and filtering them by search text.
Attributes:
search: Search field used to filter categories and block names.
tree: Tree widget showing available blocks grouped by category.
"""
def __init__(self,
get_categories: Callable[[], list[str]],
get_blocks: Callable[[str], list[str]],
resolve_block_meta: Callable[[str, str], BlockMeta]):
"""Initialize the block list widget.
Args:
get_categories: Callable returning available block categories.
get_blocks: Callable returning block types for a category.
resolve_block_meta: Callable resolving block metadata from names.
Raises:
None.
"""
super().__init__()
self.search = QLineEdit(self)
self.search.setPlaceholderText("Search by category or block name...")
self.tree = _BlockTree(get_categories, get_blocks, resolve_block_meta)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
layout.addWidget(self.search)
layout.addWidget(self.tree)
self.search.textChanged.connect(self._apply_filter)
# --------------------------------------------------------------------------
# Private Methods
# --------------------------------------------------------------------------
def _apply_filter(self, text: str):
"""Filter visible categories and blocks using the search text."""
query = text.strip().lower()
for i in range(self.tree.topLevelItemCount()):
category_item = self.tree.topLevelItem(i)
category_name = category_item.text(0)
category_match = query in category_name.lower() # ← startswith → in
visible_children = 0
for j in range(category_item.childCount()):
block_item = category_item.child(j)
block_name = block_item.text(0)
block_match = query in block_name.lower() # ← startswith → in
visible = (not query) or category_match or block_match
block_item.setHidden(not visible)
if visible:
visible_children += 1
category_visible = (not query) or category_match or (visible_children > 0)
category_item.setHidden(not category_visible)
category_item.setExpanded(bool(query and category_visible))