Compare commits

...

3 Commits

Author SHA1 Message Date
wyzula_j d820d5adb5 fix(digital-twin): resolve offsets file from package
CI for debye_bec / test (push) Successful in 1m0s
CI for debye_bec / test (pull_request) Successful in 1m6s
Load x01da_offsets.yaml relative to digital_twin.py instead of the process working directory so the widget starts correctly from any launch location.
2026-06-09 15:42:39 +02:00
wyzula_j dc6966ee31 refactor(digital-twin): compact widget layout
Wrap side panels in vertical scroll areas, tighten row and group spacing, use a balanced 1:1 plot area, and avoid assigning a second top-level layout.

Also aligns MotionWorker signal signatures with the slots connected to them as part of the widget cleanup.
2026-06-09 15:42:26 +02:00
wyzula_j 6bce6f8907 fix(digital-twin): use exact string comparisons
Replace substring membership checks with equality for modes, stripes, scene names, and plot identifiers so partial strings cannot select the wrong branch.
2026-06-09 15:41:10 +02:00
11 changed files with 135 additions and 184 deletions
@@ -183,7 +183,7 @@ def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]:
if cfg["fm_stripe"] in ("Rh (toroid)", "Pt (toroid)"):
# TRY
if cfg["fm_stripe"] in "Rh (toroid)":
if cfg["fm_stripe"] == "Rh (toroid)":
r = bl.fm.r[0]
h_cyl = bl.fm.hToroid[0]
else: # PT toroid
@@ -199,7 +199,7 @@ def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]:
pos["fm_try"] = {"value": fm_height}
# TRX
if cfg["fm_stripe"] in "Rh (toroid)":
if cfg["fm_stripe"] == "Rh (toroid)":
x_cyl = -bl.fm.xToroid[0]
else:
x_cyl = -bl.fm.xToroid[1]
@@ -213,7 +213,7 @@ def calc_positions(cfg: ConfigDict) -> dict[str, dict[str, float]]:
pos["fm_try"] = {"value": fm_height}
# TRX
if cfg["fm_stripe"] in "Rh (flat)":
if cfg["fm_stripe"] == "Rh (flat)":
x_flat = -bl.fm.xFlat[0]
else:
x_flat = -bl.fm.xFlat[1]
@@ -27,7 +27,7 @@ def calc_sideview(cfg: ConfigDict) -> DataDict:
beam["y"].append(bl.sourceHeight)
beam["x"].append(bl.cm.center[1]) # CM
beam["y"].append(bl.sourceHeight)
if cfg["mo1_mode"] in "Monochromatic":
if cfg["mo1_mode"] == "Monochromatic":
diag = bl.mo1.xtalGap[0] / np.sin(cfg["mo1_bragg"]) # Calculations for Mono
dy = diag * np.sin(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"]))
dz = diag * np.cos(2 * (cfg["cm_pitch"] + cfg["mo1_bragg"]))
@@ -65,7 +65,7 @@ def calc_surfaces(cfg: ConfigDict) -> SurfaceDict:
height_beam = 2 * bl.cm.center[1] * np.tan(cfg["v_acc"])
w = height_beam / np.sin(cfg["mo1_bragg"])
if cfg["mo1_mode"] in "Monochromatic":
if cfg["mo1_mode"] == "Monochromatic":
out["mo1_1"]["x"] = [
xtal_pos - width_beam / 2,
xtal_pos + width_beam / 2,
@@ -258,7 +258,7 @@ def fm_ideal_pitch(
"""
p = bl.fm.center[1] # posFM
q = smpl - bl.fm.center[1] # dist posFM to posEX
if fm_focus in "Defocused":
if fm_focus == "Defocused":
assert sldi_hacc is not None, "sldi_hacc must be provided for Defocused mode"
assert sldi_vacc is not None, "sldi_vacc must be provided for Defocused mode"
assert fm_focx is not None, "fm_focx must be provided for Defocused mode"
@@ -294,9 +294,9 @@ def cm_critical_angle(cm_stripe: Literal["Si", "Pt", "Rh"], energy) -> float:
Returns:
float: Critical angle in rad
"""
if cm_stripe in "Si":
if cm_stripe == "Si":
stripe = bl.stripeSi
elif cm_stripe in "Pt":
elif cm_stripe == "Pt":
stripe = bl.stripePt
else:
stripe = bl.stripeRh
@@ -320,15 +320,15 @@ def mirror_surface_geometries(
dict[str, tuple[float, float, float, float]]: Dictionary mapping surface
names to tuples of (x, y, width, height).
"""
if mirror in "cm":
if mirror == "cm":
surface = bl.cm.surface
lim_opt_x = bl.cm.limOptX
lim_opt_y = bl.cm.limOptY
elif mirror in "fm_toroid":
elif mirror == "fm_toroid":
surface = bl.fm.surfaceToroid
lim_opt_x = bl.fm.limOptXToroid
lim_opt_y = bl.fm.limOptYToroid
elif mirror in "fm_flat":
elif mirror == "fm_flat":
surface = bl.fm.surfaceFlat
lim_opt_x = bl.fm.limOptXFlat
lim_opt_y = bl.fm.limOptYFlat
@@ -354,7 +354,7 @@ def mo_surface_geometries(
dict[str, tuple[float, float, float, float]]: Dictionary mapping surface
names to tuples of (x, y, width, height).
"""
if mo in "mo1":
if mo == "mo1":
xtal = bl.mo1.xtal
xtal_width = bl.mo1.xtalWidth
xtal_offset_x = bl.mo1.xtalOffsetX
@@ -22,10 +22,13 @@ from qtpy.QtWidgets import (
QApplication,
QDialog,
QDialogButtonBox,
QFrame,
QHBoxLayout,
QLabel,
QPlainTextEdit,
QPushButton,
QScrollArea,
QSizePolicy,
QStyle,
QVBoxLayout,
QWidget,
@@ -55,7 +58,7 @@ from debye_bec.bec_widgets.widgets.digital_twin.types import ConfigDict
logger = bec_logger.logger
OFFSET_FILE = "debye_bec/debye_bec/bec_widgets/widgets/digital_twin/x01da_offsets.yaml"
OFFSET_FILE = Path(__file__).with_name("x01da_offsets.yaml")
class DigitalTwin(BECWidget, QWidget):
@@ -74,32 +77,47 @@ class DigitalTwin(BECWidget, QWidget):
self.check_config()
self.bec_dispatcher.connect_slot(self.check_config, MessageEndpoints.device_config_update())
central = QWidget()
self.root_layout = QHBoxLayout(central)
self.content_widget = QWidget(self)
self.root_layout = QHBoxLayout(self.content_widget)
self.root_layout.setContentsMargins(6, 6, 6, 6)
self.root_layout.setSpacing(6)
self.input_widget = QWidget()
self.input_layout = QVBoxLayout(self.input_widget)
self.input_layout.setContentsMargins(4, 4, 4, 4)
self.input_layout.setSpacing(6)
self.input = InputPanel()
self.settings = SettingsPanel()
self.input_layout.addWidget(self.input)
self.input_layout.addWidget(self.settings)
self.input_layout.addStretch()
self.plot_widget = QWidget()
self.plot_layout = QVBoxLayout(self.plot_widget)
self.plot_layout.setContentsMargins(4, 4, 4, 4)
self.plot_layout.setSpacing(6)
self.sideview_plot = SideviewPlot()
self.surface_plots = SurfacePlots()
self.plot_layout.addWidget(self.sideview_plot)
self.plot_layout.addWidget(self.surface_plots)
self.plot_layout.addWidget(self.sideview_plot, stretch=1)
self.plot_layout.addWidget(self.surface_plots, stretch=1)
self.plot_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.mover = MoverPanel(self.dev)
self.root_layout.addWidget(self.input_widget, alignment=Qt.AlignmentFlag.AlignTop)
self.root_layout.addWidget(self.plot_widget, alignment=Qt.AlignmentFlag.AlignTop)
self.root_layout.addWidget(self.mover, alignment=Qt.AlignmentFlag.AlignTop)
self.input_scroll = self._scroll_area(self.input_widget, min_width=320, max_width=360)
self.mover_scroll = self._scroll_area(self.mover, min_width=380, max_width=460)
self.setLayout(self.root_layout)
self.root_layout.addWidget(self.input_scroll)
self.root_layout.addWidget(self.plot_widget, stretch=1)
self.root_layout.addWidget(self.mover_scroll)
widget_layout = self.layout()
if widget_layout is None:
widget_layout = QVBoxLayout(self)
widget_layout.setContentsMargins(0, 0, 0, 0)
widget_layout.setSpacing(0)
widget_layout.addWidget(self.content_widget)
self.setWindowTitle("Digital Twin")
self.resize(1800, 800)
self.resize(1450, 760)
self.input.energy.value_changed_connect(self.calc_assistant)
self.input.sldi_hacc.value_changed_connect(self.calc_assistant)
@@ -127,12 +145,26 @@ class DigitalTwin(BECWidget, QWidget):
self.load_offsets(recalculate=False)
self.calc_assistant(identifier="init")
# Timer: update plots every 1 second
# Timer: update reality plots every 1 second
self._timer = QTimer(self)
self._timer.setInterval(100)
self._timer.setInterval(1000)
self._timer.timeout.connect(self.calc_reality)
self._timer.start()
@staticmethod
def _scroll_area(widget: QWidget, min_width: int, max_width: int) -> QScrollArea:
"""Wrap a side panel in a compact vertical scroll area."""
scroll = QScrollArea()
scroll.setWidgetResizable(True)
widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.MinimumExpanding)
scroll.setWidget(widget)
scroll.setFrameShape(QFrame.Shape.NoFrame)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
scroll.setMinimumWidth(min_width)
scroll.setMaximumWidth(max_width)
scroll.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
return scroll
def apply_theme(self, theme: Literal["dark", "light"]):
"""
Apply the theme
@@ -152,8 +184,8 @@ class DigitalTwin(BECWidget, QWidget):
BEC dispatcher whenever there is a config update, stop the timer
that updates the plot in the background.
"""
reload = (args[0] if args else {}).get("action") == "reload"
if reload:
reload_config = (args[0] if args else {}).get("action") == "reload"
if reload_config:
self._timer.stop()
devices = [
"abs",
@@ -234,7 +266,7 @@ class DigitalTwin(BECWidget, QWidget):
running_app = QApplication.instance()
if running_app is not None:
running_app.exit(0)
if reload:
if reload_config:
self._timer.start()
@SafeSlot()
@@ -309,10 +341,10 @@ class DigitalTwin(BECWidget, QWidget):
ConfigDict: config of the assistant
"""
fm_focus = self.input.fm_focus.currentText()
if fm_focus in "Manual":
if fm_focus == "Manual":
fm_rotx = self.input.fm_rotx.value()
fm_qy = None
elif fm_focus in "Focused":
elif fm_focus == "Focused":
fm_rotx = self.input.fm_rotx_ideal.value()
fm_qy = None
else: # Focused
@@ -523,11 +555,10 @@ class DigitalTwin(BECWidget, QWidget):
if self.offsets == {}:
# Load offsets
file = Path(OFFSET_FILE)
if not file.exists():
if not OFFSET_FILE.exists():
raise FileNotFoundError(f"Offset file not found: {OFFSET_FILE}")
with file.open("r", encoding="utf-8") as f:
with OFFSET_FILE.open("r", encoding="utf-8") as f:
data = yaml.safe_load(f)
if not isinstance(data, dict):
@@ -568,7 +599,7 @@ class DigitalTwin(BECWidget, QWidget):
intro_label.setWordWrap(True)
layout.addWidget(intro_label)
file = QLabel(OFFSET_FILE)
file = QLabel(str(OFFSET_FILE))
file.setWordWrap(True)
font = QFont()
font.setItalic(True)
@@ -600,13 +631,13 @@ class DigitalTwin(BECWidget, QWidget):
selection of the focus strategy.
"""
fm_focus = self.input.fm_focus.currentText()
if fm_focus in "Manual":
if fm_focus == "Manual":
self.input.fm_rotx.setVisible(True)
self.input.fm_rotx_ideal.setVisible(True)
self.input.fm_focx.setVisible(False)
self.input.fm_focy.setVisible(False)
self.input.fm_rotx_ideal.setLabel("Incidence Angle for focused beam")
elif fm_focus in "Focused":
elif fm_focus == "Focused":
self.input.fm_rotx.setVisible(False)
self.input.fm_rotx_ideal.setVisible(True)
self.input.fm_focx.setVisible(False)
@@ -659,7 +690,7 @@ class DigitalTwin(BECWidget, QWidget):
"""
fm_stripe = self.input.fm_stripe.currentText()
fm_focus = self.input.fm_focus.currentText()
if fm_focus in "Manual":
if fm_focus == "Manual":
fm_rotx = -self.input.fm_rotx.value() * 1e-3
else:
fm_rotx = -self.input.fm_rotx_ideal.value() * 1e-3
@@ -745,11 +776,11 @@ class DigitalTwin(BECWidget, QWidget):
Calculates bragg angle in rad
"""
xtal = self.input.mo1_xtal.currentText()
if xtal in "Si(111)":
if xtal == "Si(111)":
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si111.read(cached=True)[
"mo1_bragg_crystal_d_spacing_si111"
]["value"]
elif xtal in "Si(311)":
elif xtal == "Si(311)":
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si311.read(cached=True)[
"mo1_bragg_crystal_d_spacing_si311"
]["value"]
@@ -767,7 +798,7 @@ class DigitalTwin(BECWidget, QWidget):
Updates the monochromator input group based on the
selection of the mode.
"""
if self.input.mo1_mode.currentText() in "Monochromatic":
if self.input.mo1_mode.currentText() == "Monochromatic":
self.input.mo1_xtal.setVisible(True)
self.input.mo1_bragg_angle.setVisible(True)
self.input.mo1_eres.setVisible(True)
@@ -3,7 +3,7 @@ Panel for user inputs of the digital twin widget
"""
# pylint: disable=E0611
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
from qtpy.QtWidgets import QVBoxLayout, QWidget
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import (
Button,
@@ -20,7 +20,8 @@ class InputPanel(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._layout = QVBoxLayout(self)
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
self._layout.setContentsMargins(4, 4, 4, 4)
self._layout.setSpacing(4)
# Adapt to reality
self.adapt_reality = Button(label_button="Adapt to reality", enabled=True)
@@ -5,7 +5,7 @@ Panel to move an axis to a certain position
from typing import Literal
# pylint: disable=E0611
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
from qtpy.QtWidgets import QVBoxLayout, QWidget
from debye_bec.bec_widgets.widgets.digital_twin.widgets.move_widget import (
AbsorberWidget,
@@ -20,7 +20,8 @@ class MoverPanel(QWidget):
def __init__(self, dev, parent=None):
super().__init__(parent)
self._layout = QVBoxLayout(self)
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
self._layout.setContentsMargins(4, 4, 4, 4)
self._layout.setSpacing(4)
self.mover_widgets = []
@@ -189,7 +190,7 @@ class MoverPanel(QWidget):
)
self.mover_widgets.append(self.es0wi_try)
self.es0_mov_group = Group("Expperimental Station 0", [self.es0wi_try])
self.es0_mov_group = Group("Experimental Station 0", [self.es0wi_try])
# Experimental Station 1
self.ot_es1_trz = MoveWidget(
@@ -197,7 +198,7 @@ class MoverPanel(QWidget):
)
self.mover_widgets.append(self.ot_es1_trz)
self.es1_mov_group = Group("Expperimental Station 1", [self.ot_es1_trz])
self.es1_mov_group = Group("Experimental Station 1", [self.ot_es1_trz])
# Assemble complete mover group
self.mover_group = Group(
@@ -33,6 +33,8 @@ class SurfacePlots(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self._layout = QHBoxLayout(self)
self._layout.setContentsMargins(4, 4, 4, 4)
self._layout.setSpacing(6)
self.surfaces: dict[str, SurfaceDict] = {
"assistant": {
@@ -74,7 +76,7 @@ class SurfacePlots(QWidget):
# Create surfaces
for idx, scene in enumerate(self.surfaces):
for name, _ in self.surfaces[scene].items():
if scene in "assistant":
if scene == "assistant":
brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern)
pen = pg.mkPen(
QColor(*self.colors[idx], 255), width=1, style=Qt.PenStyle.DashLine
@@ -130,7 +132,7 @@ class SurfacePlots(QWidget):
for idx, scene in enumerate(self.surfaces):
for name, _ in self.surfaces[scene].items():
if scene in "assistant":
if scene == "assistant":
brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern)
pen = pg.mkPen(
QColor(*self.colors[idx], 255), width=1, style=Qt.PenStyle.DashLine
@@ -165,13 +167,13 @@ class SurfacePlots(QWidget):
self.texts.append(text)
for name, plot in self.plots.items():
if name in "cm":
if name == "cm":
plot_surface(plot["widget"], mirror_surface_geometries("cm"))
elif name in "mo1_1":
elif name == "mo1_1":
plot_surface(plot["widget"], mo_surface_geometries("mo1", 0))
elif name in "mo1_2":
elif name == "mo1_2":
plot_surface(plot["widget"], mo_surface_geometries("mo1", 1))
elif name in "fm":
elif name == "fm":
plot_surface(plot["widget"], mirror_surface_geometries("fm_flat"))
plot_surface(plot["widget"], mirror_surface_geometries("fm_toroid"))
else:
@@ -202,7 +204,8 @@ class SideviewPlot(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self._layout = QVBoxLayout(self)
# self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
self._layout.setContentsMargins(4, 4, 4, 4)
self._layout.setSpacing(0)
self.plot_widget = pg.PlotWidget()
self.plot_widget.getAxis("bottom").enableAutoSIPrefix(False)
@@ -223,7 +226,7 @@ class SideviewPlot(QWidget):
self.walls = []
for idx, scene in enumerate(self.data.keys()):
if scene in "assistant":
if scene == "assistant":
pen = pg.mkPen(color=self.colors[idx], width=2, style=Qt.PenStyle.DotLine)
z_value = 2
else:
@@ -243,7 +246,6 @@ class SideviewPlot(QWidget):
self.plot_widget.hideButtons()
self._layout.addWidget(self.plot_group)
self._layout.addStretch()
self.plot_vacuum_pipes()
self.plot_walls()
@@ -281,7 +283,7 @@ class SideviewPlot(QWidget):
self.text_color = (0, 0, 0)
for idx, scene in enumerate(self.data):
if scene in "assistant":
if scene == "assistant":
brush = QBrush(QColor(*self.colors[idx], 255), Qt.BrushStyle.DiagCrossPattern)
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3, style=Qt.PenStyle.DashLine)
else:
@@ -3,7 +3,7 @@ Settings panel for the digital twin widget
"""
# pylint: disable=E0611
from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget
from qtpy.QtWidgets import QVBoxLayout, QWidget
from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import (
Button,
@@ -18,7 +18,8 @@ class SettingsPanel(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._layout = QVBoxLayout(self)
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
self._layout.setContentsMargins(4, 4, 4, 4)
self._layout.setSpacing(4)
# Reload offsets
self.load_offsets = Button(label="Load Offsets", label_button="Load", enabled=True)
@@ -128,8 +128,8 @@ class MotionWorker(QObject):
"""
position_changed = Signal(float)
error = Signal(bool) # True = error
finished = Signal(bool) # True = reached target, False = stopped
error = Signal()
finished = Signal()
def __init__(self, dev, motor, target_pos: float):
super().__init__()
@@ -284,7 +284,7 @@ class MotionWorker(QObject):
fb = surv_ax["device"].read(cached=True)[surv_ax["name"]]["value"]
if abs(fb - surv_ax["old_value"]) > surv_ax["abs_tol"]:
self.dev[self.motor].stop()
self.error.emit(1)
self.error.emit()
break
self.finished.emit()
@@ -315,35 +315,33 @@ class MoveWidget(QWidget):
self.decimals = decimals
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(4, 0, 4, 0)
layout.setSpacing(4)
# Name
self.label = QLabel(label)
self.label.setFixedWidth(100)
self.label.setContentsMargins(0, 0, 10, 0)
self.label.setFixedWidth(76)
self.label.setWordWrap(True)
layout.addWidget(self.label)
# Target
self.target_label = QLabel("-")
self.target_label.setFixedWidth(100)
self.target_label.setFixedWidth(84)
layout.addWidget(self.target_label)
# Feedback
self.fb_label = QLabel("-")
self.fb_label.setFixedWidth(100)
self.fb_label.setFixedWidth(84)
layout.addWidget(self.fb_label)
# Status icon
self.status_icon = StatusIcon()
self.status_icon.setFixedWidth(30)
self.status_icon.setContentsMargins(0, 0, 10, 0)
self.status_icon.setFixedWidth(24)
layout.addWidget(self.status_icon)
# Start / Stop button
self.btn_action = QPushButton("Move")
self.btn_action.setFixedWidth(90)
self.btn_action.setFixedWidth(64)
self.btn_action.setFixedHeight(20)
self.btn_action.clicked.connect(self._on_button_clicked)
layout.addWidget(self.btn_action)
@@ -485,7 +483,7 @@ class MoveWidget(QWidget):
def _on_motion_finished(self):
"""Finished a movement"""
target = self.target
if self.status not in Status.ERROR:
if self.status != Status.ERROR:
if abs(self.fb - target) <= self.deadband:
self._set_status(Status.IN_POSITION)
else:
@@ -522,35 +520,33 @@ class AbsorberWidget(QWidget):
self.text_color = (0, 0, 0)
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(4, 0, 4, 0)
layout.setSpacing(4)
# Name
self.label = QLabel(label)
self.label.setFixedWidth(100)
self.label.setContentsMargins(0, 0, 10, 0)
self.label.setFixedWidth(76)
self.label.setWordWrap(True)
layout.addWidget(self.label)
# Blank
self.blank_label = QLabel("")
self.blank_label.setFixedWidth(100)
self.blank_label.setFixedWidth(84)
layout.addWidget(self.blank_label)
# Feedback
self.fb_label = QLabel("-")
self.fb_label.setFixedWidth(100)
self.fb_label.setFixedWidth(84)
layout.addWidget(self.fb_label)
# Blank icon
self.blank_icon = QLabel("")
self.blank_icon.setFixedWidth(30)
self.blank_icon.setContentsMargins(0, 0, 10, 0)
self.blank_icon.setFixedWidth(24)
layout.addWidget(self.blank_icon)
# Open
self.btn_action = QPushButton("Open")
self.btn_action.setFixedWidth(90)
self.btn_action.setFixedWidth(64)
self.btn_action.setFixedHeight(20)
self.btn_action.clicked.connect(self._on_button_clicked)
layout.addWidget(self.btn_action)
@@ -21,11 +21,17 @@ from qtpy.QtWidgets import (
QWidget,
)
LABEL_WIDTH = 118
ROW_MARGINS = (4, 0, 4, 0)
ROW_SPACING = 6
class Group(QGroupBox):
def __init__(self, label, widgets):
super().__init__(label)
self.layout = QVBoxLayout(self) # type: ignore
self.layout.setContentsMargins(6, 6, 6, 6)
self.layout.setSpacing(4)
for widget in widgets:
self.layout.addWidget(widget) # type: ignore
@@ -34,16 +40,14 @@ class NumberIndicator(QWidget):
def __init__(self, label="", unit=None, highlight=False, decimals=3):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
self.label = QLabel(label)
self.label.setFixedWidth(140)
self.label.setContentsMargins(0, 0, 10, 0)
self.label.setFixedWidth(LABEL_WIDTH)
self.label.setWordWrap(True)
layout.addWidget(self.label)
self.val = QLabel("-")
self.val.setAlignment(Qt.AlignTop) # type: ignore
# self.val.setFixedWidth(140)
layout.addWidget(self.val)
self.unit = unit
self.highlight = highlight
@@ -85,12 +89,11 @@ class InputNumberField(QWidget):
):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
self.identifier = identifier
self.label = QLabel(label)
self.label.setFixedWidth(140)
self.label.setContentsMargins(0, 0, 10, 0)
self.label.setFixedWidth(LABEL_WIDTH)
self.label.setWordWrap(True)
layout.addWidget(self.label)
self.val = QDoubleSpinBox()
@@ -102,7 +105,6 @@ class InputNumberField(QWidget):
self.val.setSuffix(" " + unit)
if prefix is not None:
self.val.setPrefix(prefix + " ")
# self.val.setFixedWidth(140)
layout.addWidget(self.val)
def set_number(self, number):
@@ -124,19 +126,18 @@ class InputNumberField(QWidget):
class ComboBox(QWidget):
def __init__(self, identifier="", label="", enums=[]):
def __init__(self, identifier="", label="", enums=None):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
self.identifier = identifier
self.label = QLabel(label)
self.label.setFixedWidth(140)
self.label.setContentsMargins(0, 0, 10, 0)
self.label.setFixedWidth(LABEL_WIDTH)
self.label.setWordWrap(True)
layout.addWidget(self.label)
self.value = QComboBox()
for entry in enums:
for entry in enums or []:
self.value.addItem(entry)
layout.addWidget(self.value)
@@ -168,15 +169,15 @@ class Button(QWidget):
def __init__(self, label=None, label_button: str = "", enabled=False):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
if label is not None:
self.label = QLabel(label)
self.label.setFixedWidth(140)
self.label.setFixedWidth(LABEL_WIDTH)
layout.addWidget(self.label)
self.button = QPushButton(label_button)
if label is not None:
self.button.setFixedWidth(160)
self.button.setFixedWidth(130)
self.enable_button(enabled)
layout.addWidget(self.button)
@@ -204,11 +205,10 @@ class TextIndicator(QWidget):
def __init__(self, label):
super().__init__()
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 0, 0, 0)
layout.setSpacing(0)
layout.setContentsMargins(*ROW_MARGINS)
layout.setSpacing(ROW_SPACING)
self.label = QLabel(label)
self.label.setFixedWidth(140)
self.label.setContentsMargins(0, 0, 10, 0)
self.label.setFixedWidth(LABEL_WIDTH)
self.label.setWordWrap(True)
layout.addWidget(self.label)
self.text = QLabel("-")
@@ -223,84 +223,3 @@ class TextIndicator(QWidget):
def setColor(self, color: str):
self.text.setStyleSheet(f"QLabel {{color:{color}}}")
# class Button(QWidget):
# def __init__(self, label, label_button):
# super().__init__()
# layout = QHBoxLayout(self)
# layout.setContentsMargins(10, 0, 0, 0)
# layout.setSpacing(0)
# self.label = QLabel(label)
# self.label.setFixedWidth(150)
# layout.addWidget(self.label)
# self.button = QPushButton(label_button)
# self.button.setStyleSheet("color: black; background-color: dodgerblue;")
# self.button.setFixedWidth(160)
# layout.addWidget(self.button)
# def set_on_press(self, func):
# """Connect a function to the button press."""
# self.button.clicked.connect(func)
# def enable_button(self):
# self.button.setEnabled(True)
# self.button.setStyleSheet("color: black; background-color: dodgerblue;")
# def disable_button(self):
# self.button.setEnabled(False)
# self.button.setStyleSheet("color: black; background-color: grey;")
# def set_button_text(self, text):
# self.button.setText(text)
# class LED(QWidget):
# def __init__(self, states, colors, label):
# super().__init__()
# self.states = states
# self.colors = colors
# layout = QHBoxLayout(self)
# layout.setContentsMargins(10, 0, 0, 0)
# layout.setSpacing(0)
# self.label = QLabel(label)
# self.label.setFixedWidth(150)
# layout.addWidget(self.label)
# self.led = QLabel()
# self.led.setFixedWidth(160)
# layout.addWidget(self.led)
# def apply_color(self, val):
# color = self.colors[self.states.index(val)]
# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;")
# class InputTextField(QWidget):
# def __init__(self, topic, label):
# super().__init__()
# self.topic = topic
# layout = QHBoxLayout(self)
# layout.setContentsMargins(10, 0, 0, 0)
# layout.setSpacing(0)
# self.label = QLabel(label)
# self.label.setFixedWidth(140)
# self.label.setContentsMargins(0, 0, 10, 0)
# self.label.setWordWrap(True)
# layout.addWidget(self.label)
# self.val = QLineEdit()
# self.val.setPlaceholderText('0')
# # self.val.setFixedWidth(140)
# layout.addWidget(self.val)
# def set_text(self, text):
# self.val.setText(text)
# def has_focus(self) -> bool:
# return self.val.hasFocus()
# def text(self) -> str:
# return self.val.text()
# def set_on_return(self, func):
# """Connect a function to the Enter/Return key press."""
# self.val.returnPressed.connect(
# partial(func, self.val, self.topic, lambda: self.val.text())
# )