0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

feat(motor_widget): Motor Widget with toolbar WIP

This commit is contained in:
2024-07-01 16:05:39 +02:00
parent c069f3e1b3
commit 51f54ca22e
4 changed files with 175 additions and 98 deletions

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#FFFFFF">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M18 7V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v3H5v6l3 6v3h8v-3l3-6V7h-1zM8 4h8v3h-2.01V5h-1v2H11V5h-1v2H8V4zm9 8.53l-3 6V20h-4v-1.47l-3-6V9h10v3.53z"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#FFFFFF">
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73 0 .21-.02.43-.05.73l-.14 1.13.89.7 1.08.84-.7 1.21-1.27-.51-1.04-.42-.9.68c-.43.32-.84.56-1.25.73l-1.06.43-.16 1.13-.2 1.35h-1.4l-.19-1.35-.16-1.13-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7-1.06.43-1.27.51-.7-1.21 1.08-.84.89-.7-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13-.89-.7-1.08-.84.7-1.21 1.27.51 1.04.42.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43.16-1.13.2-1.35h1.39l.19 1.35.16 1.13 1.06.43c.43.18.83.41 1.23.71l.91.7 1.06-.43 1.27-.51.7 1.21-1.07.85-.89.7.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,117 @@
import os
from qtpy.QtCore import QSize, Slot
from qtpy.QtGui import QAction, QIcon
from qtpy.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
from bec_widgets.utils import BECConnector
from bec_widgets.widgets.device_inputs import DeviceComboBox
from bec_widgets.widgets.figure import BECFigure
from bec_widgets.widgets.figure.plots.motor_map.motor_map import MotorMapConfig
from bec_widgets.widgets.toolbar import ModularToolBar
from bec_widgets.widgets.toolbar.toolbar import ToolBarAction
class SettingsAction(ToolBarAction):
def add_to_toolbar(self, toolbar, target):
current_path = os.path.dirname(__file__)
icon = QIcon()
icon.addFile(os.path.join(current_path, "assets", "settings.svg"), size=QSize(20, 20))
action = QAction(icon, "Config", target)
action.triggered.connect(lambda: print(target.config_dict))
toolbar.addAction(action)
class DeviceSelectionAction(ToolBarAction):
def __init__(self, label: str):
self.label = label
self.device_combobox = DeviceComboBox(device_filter="Positioner")
def add_to_toolbar(self, toolbar, target):
widget = QWidget()
layout = QHBoxLayout(widget)
label = QLabel(f"{self.label}")
layout.addWidget(label)
layout.addWidget(self.device_combobox)
toolbar.addWidget(widget)
class ConnectAction(ToolBarAction):
def add_to_toolbar(self, toolbar, target):
current_path = os.path.dirname(__file__)
icon = QIcon()
icon.addFile(os.path.join(current_path, "assets", "connection.svg"), size=QSize(20, 20))
self.action = QAction(icon, "Connect Motors", target)
toolbar.addAction(self.action)
class BECMotorMapWidget(BECConnector, QWidget):
USER_ACCESS = []
def __init__(
self,
parent: QWidget | None = None,
config: MotorMapConfig | None = None,
client=None,
gui_id: str | None = None,
) -> None:
if config is None:
config = MotorMapConfig(widget_class=self.__class__.__name__)
else:
if isinstance(config, dict):
config = MotorMapConfig(**config)
super().__init__(client=client, gui_id=gui_id)
QWidget.__init__(self, parent)
self.layout = QVBoxLayout(self)
self.layout.setSpacing(0)
self.layout.setContentsMargins(0, 0, 0, 0)
self.fig = BECFigure()
self.toolbar = ModularToolBar(
actions={
"motor_x": DeviceSelectionAction("Motor X:"),
"motor_y": DeviceSelectionAction("Motor Y:"),
"connect": ConnectAction(),
"config": SettingsAction(),
},
target_widget=self,
)
self.layout.addWidget(self.toolbar)
self.layout.addWidget(self.fig)
self.map = self.fig.motor_map()
self.map.apply_config(config)
self.config = config
self._hook_actions()
def _hook_actions(self):
self.toolbar.widgets["connect"].action.triggered.connect(self.pass_motors)
def pass_motors(self):
motor_x = self.toolbar.widgets["motor_x"].device_combobox.currentText()
motor_y = self.toolbar.widgets["motor_y"].device_combobox.currentText()
self.change_motors(motor_x, motor_y)
@Slot(str, str)
def change_motors(self, motor_x, motor_y):
self.map.change_motors(motor_x, motor_y)
def set(self, **kwargs):
self.map.set(**kwargs)
if __name__ == "__main__":
import sys
from PySide6.QtWidgets import QApplication
app = QApplication(sys.argv)
widget = BECMotorMapWidget()
widget.show()
sys.exit(app.exec_())

View File

@ -1,4 +1,7 @@
from abc import ABC, abstractmethod
from collections import defaultdict
from PySide6.QtWidgets import QHBoxLayout, QLabel, QSpinBox
# pylint: disable=no-name-in-module
from qtpy.QtCore import QSize, QTimer
@ -7,117 +10,63 @@ from qtpy.QtWidgets import QApplication, QStyle, QToolBar, QWidget
class ToolBarAction(ABC):
"""Abstract base class for action creators for the toolbar."""
@abstractmethod
def create(self, target: QWidget):
"""Creates and returns an action to be added to a toolbar.
This method must be implemented by subclasses.
def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
"""Adds an action or widget to a toolbar.
Args:
target (QWidget): The widget that the action will target.
Returns:
QAction: The action created for the toolbar.
toolbar (QToolBar): The toolbar to add the action or widget to.
target (QWidget): The target widget for the action.
"""
class OpenFileAction: # (ToolBarAction):
"""Action creator for the 'Open File' action in the toolbar."""
class ColumnAdjustAction(ToolBarAction):
"""Toolbar spinbox to adjust number of columns in the plot layout"""
def create(self, target: QWidget):
"""Creates an 'Open File' action for the toolbar.
def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
"""Creates a access history button for the toolbar.
Args:
target (QWidget): The widget that the 'Open File' action will be targeted.
toolbar (QToolBar): The toolbar to add the action to.
target (QWidget): The widget that the 'Access Scan History' action will be targeted.
Returns:
QAction: The 'Open File' action created for the toolbar.
QAction: The 'Access Scan History' action created for the toolbar.
"""
icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DialogOpenButton)
action = QAction(icon, "Open File", target)
# action = QAction("Open File", target)
action.triggered.connect(target.open_file)
return action
widget = QWidget()
layout = QHBoxLayout(widget)
label = QLabel("Columns:")
spin_box = QSpinBox()
spin_box.setMinimum(1) # Set minimum value
spin_box.setMaximum(10) # Set maximum value
spin_box.setValue(target.get_column_count()) # Initial value
spin_box.valueChanged.connect(lambda value: target.set_column_count(value))
class SaveFileAction:
"""Action creator for the 'Save File' action in the toolbar."""
def create(self, target):
"""Creates a 'Save File' action for the toolbar.
Args:
target (QWidget): The widget that the 'Save File' action will be targeted.
Returns:
QAction: The 'Save File' action created for the toolbar.
"""
icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton)
action = QAction(icon, "Save File", target)
# action = QAction("Save File", target)
action.triggered.connect(target.save_file)
return action
class RunScriptAction:
"""Action creator for the 'Run Script' action in the toolbar."""
def create(self, target):
"""Creates a 'Run Script' action for the toolbar.
Args:
target (QWidget): The widget that the 'Run Script' action will be targeted.
Returns:
QAction: The 'Run Script' action created for the toolbar.
"""
icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay)
action = QAction(icon, "Run Script", target)
# action = QAction("Run Script", target)
action.triggered.connect(target.run_script)
return action
layout.addWidget(label)
layout.addWidget(spin_box)
toolbar.addWidget(widget)
class ModularToolBar(QToolBar):
"""Modular toolbar with optional automatic initialization.
Args:
parent (QWidget, optional): The parent widget of the toolbar. Defaults to None.
auto_init (bool, optional): If True, automatically populates the toolbar based on the parent widget.
"""
def __init__(self, parent=None, auto_init=True):
def __init__(self, parent=None, actions=None, target_widget=None):
super().__init__(parent)
self.auto_init = auto_init
self.handler = {
"BECEditor": [OpenFileAction(), SaveFileAction(), RunScriptAction()],
# BECMonitor: [SomeOtherAction(), AnotherAction()], # Example for another widget
}
self.setStyleSheet("QToolBar { background: transparent; }")
# Set the icon size for the toolbar
self.setIconSize(QSize(20, 20))
self.widgets = defaultdict(dict)
if self.auto_init:
QTimer.singleShot(0, self.auto_detect_and_populate)
if actions is not None and target_widget is not None:
self.populate_toolbar(actions, target_widget)
# QTimer.singleShot(0, lambda :self.set_manual_actions(actions, target_widget))
def auto_detect_and_populate(self):
"""Automatically detects the parent widget and populates the toolbar with relevant actions."""
if not self.auto_init:
return
parent_widget = self.parent()
if parent_widget is None:
return
parent_widget_class_name = type(parent_widget).__name__
for widget_type_name, actions in self.handler.items():
if parent_widget_class_name == widget_type_name:
self.populate_toolbar(actions, parent_widget)
return
def populate_toolbar(self, actions, target_widget):
def populate_toolbar(self, actions: dict, target_widget):
"""Populates the toolbar with a set of actions.
Args:
@ -125,20 +74,23 @@ class ModularToolBar(QToolBar):
target_widget (QWidget): The widget that the actions will target.
"""
self.clear()
for action_creator in actions:
action = action_creator.create(target_widget)
self.addAction(action)
for action_id, action in actions.items():
action.add_to_toolbar(self, target_widget)
self.widgets[action_id] = action
def set_manual_actions(self, actions, target_widget):
"""Manually sets the actions for the toolbar.
# for action in actions:
# action.add_to_toolbar(self, target_widget)
Args:
actions (list[QAction or ToolBarAction]): A list of actions or action creators to populate the toolbar.
target_widget (QWidget): The widget that the actions will target.
"""
self.clear()
for action in actions:
if isinstance(action, QAction):
self.addAction(action)
elif isinstance(action, ToolBarAction):
self.addAction(action.create(target_widget))
# def set_manual_actions(self, actions, target_widget):
# """Manually sets the actions for the toolbar.
#
# Args:
# actions (list[QAction or ToolBarAction]): A list of actions or action creators to populate the toolbar.
# target_widget (QWidget): The widget that the actions will target.
# """
# self.clear()
# for action in actions:
# if isinstance(action, QAction):
# self.addAction(action)
# elif isinstance(action, ToolBarAction):
# self.addAction(action.add_to_toolbar(self, target_widget))