0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 03:31:50 +02:00

feat(widgets): added device_browser widget

This commit is contained in:
2024-08-17 12:49:49 +02:00
parent 4790afde3d
commit 73f5a2f085
8 changed files with 275 additions and 0 deletions

View File

@ -0,0 +1,107 @@
import os
import re
from typing import Optional
from bec_lib.callback_handler import EventType
from pyqtgraph import SignalProxy
from qtpy.QtCore import Signal, Slot
from qtpy.QtWidgets import QLineEdit, QListWidgetItem, QVBoxLayout, QWidget
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.device_browser.device_item import DeviceItem
class DeviceBrowser(BECWidget, QWidget):
device_update: Signal = Signal()
def __init__(
self,
parent: Optional[QWidget] = None,
config=None,
client=None,
gui_id: Optional[str] = None,
) -> None:
super().__init__(client=client, config=config, gui_id=gui_id)
QWidget.__init__(self, parent)
self.get_bec_shortcuts()
self.ui = None
self.ini_ui()
self.proxy_device_update = SignalProxy(
self.ui.filter_input.textChanged, rateLimit=500, slot=self.update_device_list
)
self.bec_dispatcher.client.callbacks.register(
EventType.DEVICE_UPDATE, self.on_device_update
)
self.device_update.connect(self.update_device_list)
self.update_device_list()
def ini_ui(self) -> None:
"""
Initialize the UI by loading the UI file and setting the layout.
"""
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
ui_file_path = os.path.join(os.path.dirname(__file__), "device_browser.ui")
self.ui = UILoader(self).loader(ui_file_path)
layout.addWidget(self.ui)
self.setLayout(layout)
def on_device_update(self, action: str, content: dict) -> None:
"""
Callback for device update events. Triggers the device_update signal.
Args:
action (str): The action that triggered the event.
content (dict): The content of the config update.
"""
if action in ["add", "remove", "reload"]:
self.device_update.emit()
@Slot()
def update_device_list(self) -> None:
"""
Update the device list based on the filter input.
There are two ways to trigger this function:
1. By changing the text in the filter input.
2. By emitting the device_update signal.
Either way, the function will filter the devices based on the filter input text and update the device list.
"""
filter_text = self.ui.filter_input.text()
try:
regex = re.compile(filter_text, re.IGNORECASE)
except re.error:
regex = None # Invalid regex, disable filtering
dev_list = self.ui.device_list
dev_list.clear()
for device in self.dev:
if regex is None or regex.search(device):
item = QListWidgetItem(dev_list)
device_item = DeviceItem(device)
# pylint: disable=protected-access
tooltip = self.dev[device]._config.get("description", "")
device_item.setToolTip(tooltip)
item.setSizeHint(device_item.sizeHint())
dev_list.setItemWidget(item, device_item)
dev_list.addItem(item)
if __name__ == "__main__": # pragma: no cover
import sys
from qtpy.QtWidgets import QApplication
from bec_widgets.utils.colors import apply_theme
app = QApplication(sys.argv)
apply_theme("light")
widget = DeviceBrowser()
widget.show()
sys.exit(app.exec_())

View File

@ -0,0 +1 @@
{'files': ['device_browser.py']}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>406</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="browser_group_box">
<property name="title">
<string>Device Browser</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="filter_layout">
<item>
<widget class="QLineEdit" name="filter_input">
<property name="placeholderText">
<string>Filter</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="device_list"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,54 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from bec_widgets.widgets.device_browser.device_browser import DeviceBrowser
DOM_XML = """
<ui language='c++'>
<widget class='DeviceBrowser' name='device_browser'>
</widget>
</ui>
"""
class DeviceBrowserPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
t = DeviceBrowser(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return ""
def icon(self):
return QIcon()
def includeFile(self):
return "device_browser"
def initialize(self, form_editor):
self._form_editor = form_editor
def isContainer(self):
return False
def isInitialized(self):
return self._form_editor is not None
def name(self):
return "DeviceBrowser"
def toolTip(self):
return "DeviceBrowser"
def whatsThis(self):
return self.toolTip()

View File

@ -0,0 +1 @@
from .device_item import DeviceItem

View File

@ -0,0 +1,53 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from qtpy.QtCore import QMimeData, Qt
from qtpy.QtGui import QDrag
from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget
if TYPE_CHECKING:
from qtpy.QtGui import QMouseEvent
class DeviceItem(QWidget):
def __init__(self, device: str) -> None:
super().__init__()
self.device = device
layout = QHBoxLayout()
layout.setContentsMargins(10, 2, 10, 2)
self.label = QLabel(device)
layout.addWidget(self.label)
self.setLayout(layout)
self.setStyleSheet(
"""
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
"""
)
def mousePressEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
drag = QDrag(self)
mime_data = QMimeData()
mime_data.setText(self.device)
drag.setMimeData(mime_data)
drag.exec_(Qt.MoveAction)
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
print("Double Clicked")
# TODO: Implement double click action for opening the device properties dialog
return super().mouseDoubleClickEvent(event)
if __name__ == "__main__":
import sys
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
widget = DeviceItem("Device")
widget.show()
sys.exit(app.exec_())

View File

@ -0,0 +1,15 @@
def main(): # pragma: no cover
from qtpy import PYSIDE6
if not PYSIDE6:
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
return
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from bec_widgets.widgets.device_browser.device_browser_plugin import DeviceBrowserPlugin
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceBrowserPlugin())
if __name__ == "__main__": # pragma: no cover
main()