wip widget
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
import sys
|
||||
from typing import Literal, Optional
|
||||
|
||||
import xraydb
|
||||
from bec_lib import bec_logger
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.utils.colors import apply_theme, get_accent_colors
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
from qtpy.QtCore import Signal
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
ELEMENTS = {
|
||||
# period 1
|
||||
"H": (1, 1),
|
||||
"He": (1, 18),
|
||||
# period 2
|
||||
"Li": (2, 1),
|
||||
"Be": (2, 2),
|
||||
"B": (2, 13),
|
||||
"C": (2, 14),
|
||||
"N": (2, 15),
|
||||
"O": (2, 16),
|
||||
"F": (2, 17),
|
||||
"Ne": (2, 18),
|
||||
# period 3
|
||||
"Na": (3, 1),
|
||||
"Mg": (3, 2),
|
||||
"Al": (3, 13),
|
||||
"Si": (3, 14),
|
||||
"P": (3, 15),
|
||||
"S": (3, 16),
|
||||
"Cl": (3, 17),
|
||||
"Ar": (3, 18),
|
||||
# period 4
|
||||
"K": (4, 1),
|
||||
"Ca": (4, 2),
|
||||
"Sc": (4, 3),
|
||||
"Ti": (4, 4),
|
||||
"V": (4, 5),
|
||||
"Cr": (4, 6),
|
||||
"Mn": (4, 7),
|
||||
"Fe": (4, 8),
|
||||
"Co": (4, 9),
|
||||
"Ni": (4, 10),
|
||||
"Cu": (4, 11),
|
||||
"Zn": (4, 12),
|
||||
"Ga": (4, 13),
|
||||
"Ge": (4, 14),
|
||||
"As": (4, 15),
|
||||
"Se": (4, 16),
|
||||
"Br": (4, 17),
|
||||
"Kr": (4, 18),
|
||||
# period 5
|
||||
"Rb": (5, 1),
|
||||
"Sr": (5, 2),
|
||||
"Y": (5, 3),
|
||||
"Zr": (5, 4),
|
||||
"Nb": (5, 5),
|
||||
"Mo": (5, 6),
|
||||
"Tc": (5, 7),
|
||||
"Ru": (5, 8),
|
||||
"Rh": (5, 9),
|
||||
"Pd": (5, 10),
|
||||
"Ag": (5, 11),
|
||||
"Cd": (5, 12),
|
||||
"In": (5, 13),
|
||||
"Sn": (5, 14),
|
||||
"Sb": (5, 15),
|
||||
"Te": (5, 16),
|
||||
"I": (5, 17),
|
||||
"Xe": (5, 18),
|
||||
# period 6
|
||||
"Cs": (6, 1),
|
||||
"Ba": (6, 2),
|
||||
"La": (6, 3),
|
||||
# lanthanides (row 8)
|
||||
"Ce": (8, 4),
|
||||
"Pr": (8, 5),
|
||||
"Nd": (8, 6),
|
||||
"Pm": (8, 7),
|
||||
"Sm": (8, 8),
|
||||
"Eu": (8, 9),
|
||||
"Gd": (8, 10),
|
||||
"Tb": (8, 11),
|
||||
"Dy": (8, 12),
|
||||
"Ho": (8, 13),
|
||||
"Er": (8, 14),
|
||||
"Tm": (8, 15),
|
||||
"Yb": (8, 16),
|
||||
"Lu": (8, 17),
|
||||
# period 6 continued
|
||||
"Hf": (6, 4),
|
||||
"Ta": (6, 5),
|
||||
"W": (6, 6),
|
||||
"Re": (6, 7),
|
||||
"Os": (6, 8),
|
||||
"Ir": (6, 9),
|
||||
"Pt": (6, 10),
|
||||
"Au": (6, 11),
|
||||
"Hg": (6, 12),
|
||||
"Tl": (6, 13),
|
||||
"Pb": (6, 14),
|
||||
"Bi": (6, 15),
|
||||
"Po": (6, 16),
|
||||
"At": (6, 17),
|
||||
"Rn": (6, 18),
|
||||
# period 7
|
||||
"Fr": (7, 1),
|
||||
"Ra": (7, 2),
|
||||
"Ac": (7, 3),
|
||||
# actinides (row 9)
|
||||
"Th": (9, 4),
|
||||
"Pa": (9, 5),
|
||||
"U": (9, 6),
|
||||
"Np": (9, 7),
|
||||
"Pu": (9, 8),
|
||||
"Am": (9, 9),
|
||||
"Cm": (9, 10),
|
||||
"Bk": (9, 11),
|
||||
"Cf": (9, 12),
|
||||
}
|
||||
|
||||
|
||||
class EdgeSelector(QDialog):
|
||||
"""BEC widget to display a selection dialog where the user can select and edge and an element"""
|
||||
|
||||
def __init__(self, parent=None, llim: int | float = 0, hlim: int | float = 1e6):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Absorption Edge Selector")
|
||||
self.buttons = {}
|
||||
self.active_edge = None
|
||||
self.active_element = None
|
||||
self.active_energy = None
|
||||
self.llim = llim
|
||||
self.hlim = hlim
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
text = QLabel("Select edge and element.")
|
||||
|
||||
edge_layout = QHBoxLayout(self)
|
||||
edge_label = QLabel("Edge")
|
||||
self.edge = QComboBox()
|
||||
self.edge.addItems(["K", "L1", "L2", "L3"])
|
||||
edge_layout.addWidget(edge_label)
|
||||
edge_layout.addWidget(self.edge)
|
||||
edge_layout.addStretch()
|
||||
|
||||
grid_layout = QGridLayout(self)
|
||||
grid_layout.setContentsMargins(0, 0, 0, 0)
|
||||
grid_layout.setSpacing(1)
|
||||
|
||||
for element, (row, col) in ELEMENTS.items():
|
||||
button = QPushButton(element)
|
||||
button.setFixedSize(42, 32)
|
||||
button.setCheckable(True)
|
||||
button.setAutoDefault(False)
|
||||
button.setDefault(False)
|
||||
button.clicked.connect(lambda checked=False, e=element: self._element_changed(e))
|
||||
grid_layout.addWidget(button, row - 1, col - 1)
|
||||
self.buttons[element] = button
|
||||
|
||||
res_layout = QHBoxLayout(self)
|
||||
self.res = QLabel("")
|
||||
res_layout.addStretch()
|
||||
res_layout.addWidget(self.res)
|
||||
res_layout.addStretch()
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
self.cancel_button = QPushButton("Cancel")
|
||||
self.cancel_button.clicked.connect(self.reject)
|
||||
self.cancel_button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
self.select_button = QPushButton("Select")
|
||||
self.select_button.setEnabled(False)
|
||||
self.select_button.clicked.connect(self.accept)
|
||||
self.select_button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
button_layout.addStretch()
|
||||
button_layout.addWidget(self.cancel_button)
|
||||
button_layout.addWidget(self.select_button)
|
||||
button_layout.addStretch()
|
||||
|
||||
layout.addWidget(text)
|
||||
layout.addLayout(edge_layout)
|
||||
layout.addLayout(grid_layout)
|
||||
layout.addLayout(res_layout)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
self.edge.activated.connect(self._edge_changed)
|
||||
self._edge_changed()
|
||||
|
||||
@SafeSlot()
|
||||
def _edge_changed(self, *_):
|
||||
self.active_edge = self.edge.currentText()
|
||||
for element, button in self.buttons.items():
|
||||
# logger.info(f"{element}")
|
||||
if "L" in self.active_edge and element in ["H", "He"]:
|
||||
button.setDisabled(True)
|
||||
button.setChecked(False)
|
||||
elif self.active_edge in ["L2", "L3"] and element == "Li":
|
||||
button.setDisabled(True)
|
||||
button.setChecked(False)
|
||||
else:
|
||||
energy = xraydb.xray_edge(element, self.active_edge, True)
|
||||
if self.active_element == element:
|
||||
self.active_energy = energy
|
||||
if energy is not None:
|
||||
if energy < self.llim or energy > self.hlim:
|
||||
button.setChecked(False)
|
||||
button.setDisabled(True)
|
||||
else:
|
||||
button.setEnabled(True)
|
||||
else:
|
||||
button.setChecked(False)
|
||||
button.setDisabled(True)
|
||||
self._set_select_text()
|
||||
|
||||
@SafeSlot()
|
||||
def _element_changed(self, element: str | None = None):
|
||||
if element is None:
|
||||
if self.active_element is None:
|
||||
return
|
||||
element = self.active_element
|
||||
# Uncheck all other buttons so only the picked one stays highlighted
|
||||
for e, btn in self.buttons.items():
|
||||
if e != element:
|
||||
btn.setChecked(False)
|
||||
self.buttons[element].setChecked(True)
|
||||
self.active_element = element
|
||||
self.active_energy = xraydb.xray_edge(element, self.active_edge, True)
|
||||
self.select_button.setEnabled(True)
|
||||
self._set_select_text()
|
||||
|
||||
def _set_select_text(self):
|
||||
if self.active_energy is not None:
|
||||
if self.buttons[self.active_element].isChecked():
|
||||
self._enable_button(self.select_button, True)
|
||||
self.select_button.setText(
|
||||
f"Selected {self.active_element}, {self.active_edge}-edge, {self.active_energy:0.1f} eV"
|
||||
)
|
||||
else:
|
||||
self._enable_button(self.select_button, False)
|
||||
self.select_button.setText("Select")
|
||||
else:
|
||||
self._enable_button(self.select_button, False)
|
||||
|
||||
@staticmethod
|
||||
def _enable_button(button: QPushButton, enable: bool):
|
||||
if enable:
|
||||
button.setEnabled(True)
|
||||
button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
else:
|
||||
button.setDisabled(True)
|
||||
button.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
|
||||
|
||||
ROW_MARGINS = (4, 0, 4, 0)
|
||||
ROW_SPACING = 6
|
||||
|
||||
|
||||
class ElementSelector(QWidget):
|
||||
|
||||
dialogClosed = Signal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(*ROW_MARGINS)
|
||||
layout.setSpacing(ROW_SPACING)
|
||||
self.label = QLabel("Element")
|
||||
layout_selection = QHBoxLayout(self)
|
||||
self.button = QPushButton("Select")
|
||||
self.button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
layout_selection.addWidget(self.button)
|
||||
layout.addWidget(self.label)
|
||||
layout.addLayout(layout_selection)
|
||||
self.current_element = ""
|
||||
|
||||
self.button.clicked.connect(self.open_table)
|
||||
|
||||
def open_table(self):
|
||||
dialog = EdgeSelector(self, llim=4500, hlim=60000)
|
||||
if dialog.exec_():
|
||||
# self.current_element = dialog.selected
|
||||
self.dialogClosed.emit()
|
||||
|
||||
def currentElement(self):
|
||||
return self.current_element
|
||||
|
||||
def apply_theme(self, theme: Optional[Literal["dark", "light"]] = None):
|
||||
"""
|
||||
Apply the theme
|
||||
|
||||
Args:
|
||||
theme (Optional[str]): Theme, either "dark", "light", or None. Defaults to None.
|
||||
"""
|
||||
if theme is None:
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
apply_theme("light")
|
||||
dispatcher = BECDispatcher(gui_id="scan_control_advanced")
|
||||
win = ElementSelector()
|
||||
win.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -23,6 +23,40 @@ ROW_MARGINS = (4, 0, 4, 0)
|
||||
ROW_SPACING = 6
|
||||
|
||||
|
||||
class Button(QWidget):
|
||||
def __init__(self, label=None, label_button: str = "", enabled=False):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(*ROW_MARGINS)
|
||||
layout.setSpacing(ROW_SPACING)
|
||||
if label is not None:
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(LABEL_WIDTH)
|
||||
layout.addWidget(self.label)
|
||||
self.button = QPushButton(label_button)
|
||||
self.enable_button(enabled)
|
||||
layout.addWidget(self.button)
|
||||
|
||||
def clicked_connect(self, func):
|
||||
"""Connect a function to the button press."""
|
||||
self.button.clicked.connect(func)
|
||||
|
||||
def enable_button(self, enable: bool = False):
|
||||
if enable:
|
||||
self.button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
self.button.setEnabled(True)
|
||||
else: # disabled
|
||||
self.button.setStyleSheet(
|
||||
"QPushButton {{background-color: rgb(120, 120, 120); color: white;}}"
|
||||
)
|
||||
self.button.setDisabled(True)
|
||||
|
||||
def setText(self, text):
|
||||
self.button.setText(text)
|
||||
|
||||
|
||||
class ComboBox(QWidget):
|
||||
def __init__(self, identifier="", label="", enums=None):
|
||||
super().__init__()
|
||||
|
||||
@@ -11,7 +11,7 @@ import xraydb
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.utils.colors import Colors, apply_theme
|
||||
from bec_widgets.utils.colors import Colors, apply_theme, get_accent_colors
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
|
||||
from qtpy.QtCore import Qt
|
||||
@@ -19,9 +19,12 @@ from qtpy.QtCore import Qt
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QFrame,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
@@ -30,7 +33,8 @@ from qtpy.QtWidgets import (
|
||||
|
||||
# pylint: disable=E0402
|
||||
from ....devices.mo1_bragg.mo1_bragg_utils import compute_spline
|
||||
from .qt_widgets import ComboBox, ElementSelector, TextIndicator
|
||||
from ..edge_selector import EdgeSelector
|
||||
from .qt_widgets import Button, ComboBox, ElementSelector, TextIndicator
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -141,9 +145,26 @@ class MotionProfileWidget(QWidget):
|
||||
outer_layout = QVBoxLayout(self)
|
||||
outer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.element = ElementSelector()
|
||||
self.edge = ComboBox("Edge", "Edge", ["K", "L1", "L2", "L3"])
|
||||
self.edge_text = TextIndicator("", "Selected Edge", "No edge selected")
|
||||
# self.element = ElementSelector()
|
||||
# self.edge = ComboBox("Edge", "Edge", ["K", "L1", "L2", "L3"])
|
||||
# self.edge_text = TextIndicator("", "Selected Edge", "No edge selected")
|
||||
|
||||
edge_selector_layout = QHBoxLayout(self)
|
||||
edge_selector_label = QLabel("Absorption edge:")
|
||||
self.edge_selector_button = QPushButton("Choose")
|
||||
self.edge_label = QLabel("No edge selected")
|
||||
edge_selector_layout.addWidget(edge_selector_label)
|
||||
edge_selector_layout.addWidget(self.edge_label)
|
||||
edge_selector_layout.addWidget(self.edge_selector_button)
|
||||
edge_selector_layout.addStretch()
|
||||
|
||||
scan_rng_layout = QHBoxLayout(self)
|
||||
self.scan_rng_ckbox = QCheckBox(self)
|
||||
self.scan_rng_ckbox.setCheckState(Qt.CheckState.Checked)
|
||||
scan_rng_label = QLabel("Auto adjust scan range to absorption edge")
|
||||
scan_rng_layout.addWidget(self.scan_rng_ckbox)
|
||||
scan_rng_layout.addWidget(scan_rng_label)
|
||||
scan_rng_layout.addStretch()
|
||||
|
||||
plot_container = QWidget()
|
||||
plot_layout = QHBoxLayout(plot_container)
|
||||
@@ -169,20 +190,25 @@ class MotionProfileWidget(QWidget):
|
||||
scroll.setWidget(plot_container)
|
||||
|
||||
self.edge_selection = QHBoxLayout()
|
||||
self.edge_selection.addWidget(self.element)
|
||||
self.edge_selection.addWidget(self.edge)
|
||||
self.edge_selection.addWidget(self.edge_text)
|
||||
# self.edge_selection.addWidget(self.element)
|
||||
# self.edge_selection.addWidget(self.edge)
|
||||
# self.edge_selection.addWidget(self.edge_text)
|
||||
self.edge_selection.addLayout(edge_selector_layout)
|
||||
self.edge_selection.addStretch(1)
|
||||
outer_layout.addLayout(self.edge_selection)
|
||||
|
||||
outer_layout.addLayout(scan_rng_layout)
|
||||
outer_layout.addWidget(scroll, stretch=1)
|
||||
|
||||
self.scan_name = None
|
||||
self.scan_parameters = None
|
||||
self.d_spacing = None
|
||||
|
||||
self.element.dialogClosed.connect(self._update_edge_selection)
|
||||
self.edge.activated_connect(self._update_edge_selection)
|
||||
self.edge_selector_button.clicked.connect(self._update_edge)
|
||||
self.scan_rng_ckbox.stateChanged.connect(self._update_auto_scan_rng)
|
||||
# self.element.dialogClosed.connect(self._update_edge_selection)
|
||||
# self.edge.activated_connect(self._update_edge_selection)
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def _make_plot(self, left_label: str, bottom_label: str) -> pg.PlotWidget:
|
||||
plot = pg.PlotWidget()
|
||||
@@ -217,27 +243,48 @@ class MotionProfileWidget(QWidget):
|
||||
)
|
||||
plot.addItem(scatter, ignoreBounds=True)
|
||||
|
||||
text = f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV"
|
||||
label = pg.TextItem(text, anchor=(0, 1.5), color="r")
|
||||
label = pg.TextItem(self.edge_label.text(), anchor=(0, 1.5), color="r")
|
||||
label.setPos(x_marker, y_marker)
|
||||
plot.addItem(label, ignoreBounds=True)
|
||||
|
||||
@SafeSlot()
|
||||
def _update_edge_selection(self, *_, **__):
|
||||
if self.element.currentElement() == "":
|
||||
return
|
||||
if self.edge.currentText() == "":
|
||||
return
|
||||
self.edge_energy = xraydb.xray_edge(
|
||||
self.element.currentElement(), self.edge.currentText(), True
|
||||
)
|
||||
if self.edge_energy is not None:
|
||||
self.edge_text.setText(
|
||||
f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV"
|
||||
def _update_edge(self, *_):
|
||||
dlg = EdgeSelector(self, llim=4500, hlim=60000)
|
||||
if dlg.exec_():
|
||||
self.edge_energy = dlg.active_energy
|
||||
self.edge_label.setText(
|
||||
f"{dlg.active_element}, {dlg.active_edge}-edge, {dlg.active_energy:0.1f} eV"
|
||||
)
|
||||
else:
|
||||
self.edge_text.setText("No valid edge selected!")
|
||||
self.update_plot()
|
||||
self._update_auto_scan_rng()
|
||||
self.update_plot()
|
||||
|
||||
@SafeSlot()
|
||||
def _update_auto_scan_rng(self, *_):
|
||||
if self.scan_rng_ckbox.isChecked() and self.edge_energy is not None:
|
||||
self._parent_widget._restore_kwargs(
|
||||
{
|
||||
"start": self.edge_energy - 200,
|
||||
"stop": self.edge_energy + 1000,
|
||||
"e_kink": self.edge_energy + 1000,
|
||||
}
|
||||
)
|
||||
|
||||
# @SafeSlot()
|
||||
# def _update_edge_selection(self, *_, **__):
|
||||
# if self.element.currentElement() == "":
|
||||
# return
|
||||
# if self.edge.currentText() == "":
|
||||
# return
|
||||
# self.edge_energy = xraydb.xray_edge(
|
||||
# self.element.currentElement(), self.edge.currentText(), True
|
||||
# )
|
||||
# if self.edge_energy is not None:
|
||||
# self.edge_text.setText(
|
||||
# f"{self.element.currentElement()}, {self.edge.currentText()}-edge, {self.edge_energy:0.1f} eV"
|
||||
# )
|
||||
# else:
|
||||
# self.edge_text.setText("No valid edge selected!")
|
||||
# self.update_plot()
|
||||
|
||||
@SafeSlot()
|
||||
def update_plot(self, *_, scan_name=None, scan_parameters=None, d_spacing=None, **__):
|
||||
@@ -331,6 +378,10 @@ class MotionProfileWidget(QWidget):
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
self.edge_selector_button.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().default.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
bg_color = pg.getConfigOption("background")
|
||||
fg_color = pg.getConfigOption("foreground")
|
||||
|
||||
@@ -346,7 +397,7 @@ class MotionProfileWidget(QWidget):
|
||||
for curve, color in zip(curves, colors):
|
||||
if not isinstance(curve, pg.ScatterPlotItem):
|
||||
curve.setPen(pg.mkPen(color=color, width=2))
|
||||
self.element.apply_theme(theme)
|
||||
# self.element.apply_theme(theme)
|
||||
|
||||
def _energy_to_angle(self, energy: int | float):
|
||||
if self.d_spacing is None:
|
||||
|
||||
Reference in New Issue
Block a user