Source code for hyspecppt.hppt.hppt_view
"""Widgets for the main window"""
from typing import Optional, Union
from matplotlib.backends.backend_qtagg import FigureCanvas
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from qtpy.QtCore import QObject, Signal
from qtpy.QtGui import QDoubleValidator, QValidator
from qtpy.QtWidgets import (
QButtonGroup,
QComboBox,
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QLineEdit,
QRadioButton,
QSizePolicy,
QSpacerItem,
QVBoxLayout,
QWidget,
)
from .experiment_settings import INVALID_QLINEEDIT, MAX_MODQ, PLOT_TYPES, alpha, beta, gamma
from .hppt_view_validators import AbsValidator, AngleValidator
[docs]
class HyspecPPTView(QWidget):
"""Main widget"""
def __init__(self, parent: Optional["QObject"] = None) -> None:
"""Constructor for the main widget
Args:
parent (QObject): Optional parent
"""
super().__init__(parent)
# callback functions defined by the presenter
self.fields_callback = None
self.powder_mode_switch_callback = None
self.sc_mode_switch_callback = None
layout = QHBoxLayout()
self.setLayout(layout)
left_side_layout = QVBoxLayout()
layout.addLayout(left_side_layout)
self.experiment_widget = ExperimentWidget(self)
left_side_layout.addWidget(self.experiment_widget)
self.sc_widget = SingleCrystalWidget(self)
self.crosshair_widget = CrosshairWidget(self)
self.selection_widget = SelectorWidget(self)
left_side_layout.addWidget(self.selection_widget)
left_side_layout.addWidget(self.sc_widget)
left_side_layout.addWidget(self.crosshair_widget)
spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
left_side_layout.addItem(spacer)
self.plot_widget = PlotWidget(self)
layout.addWidget(self.plot_widget)
# signal handling for every valid field update
self.experiment_widget.valid_signal.connect(self.values_update)
self.sc_widget.valid_signal.connect(self.values_update)
self.crosshair_widget.valid_signal.connect(self.values_update)
# plot update
self.crosshair_widget.valid_signal.connect(self.plot_widget.update_plot_crosshair)
[docs]
def connect_fields_update(self, callback):
"""Callback for the fields update - set by the presenter"""
self.fields_callback = callback
[docs]
def connect_powder_mode_switch(self, callback):
"""Callback function setup for the switching to Powder mode from the radio button
- function defined and set by the presenter
"""
self.powder_mode_switch_callback = callback
[docs]
def connect_sc_mode_switch(self, callback):
"""Callback function setup for the switching to Single Crystal mode from the radio button
- function defined and set by the presenter
"""
self.sc_mode_switch_callback = callback
[docs]
def switch_to_sc(self) -> None:
"""Switch to Single Crystal mode"""
if self.sc_mode_switch_callback:
self.sc_mode_switch_callback()
[docs]
def switch_to_powder(self) -> None:
"""Switch to Powder mode"""
if self.powder_mode_switch_callback:
self.powder_mode_switch_callback()
[docs]
def field_visibility_in_SC(self) -> None:
"""Set visibility for Single Crystal mode"""
self.sc_widget.setVisible(True)
self.crosshair_widget.set_Qmod_enabled(False)
[docs]
def field_visibility_in_Powder(self) -> None:
"""Set visibility for Powder mode"""
self.sc_widget.setVisible(False)
self.crosshair_widget.set_Qmod_enabled(True)
[docs]
class PlotWidget(QWidget):
"""Widget that displays the plot"""
def __init__(self, parent: Optional["QObject"] = None) -> None:
"""Constructor for the plotting widget
Args:
parent (QObject): Optional parent
"""
super().__init__(parent)
layoutRight = QVBoxLayout()
self.figure = Figure(figsize=(6, 4.5))
self.static_canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.static_canvas, self)
layoutRight.addWidget(self.static_canvas)
layoutRight.addWidget(self.toolbar)
self.setLayout(layoutRight)
# heatmap initialization
self.ax = self.static_canvas.figure.subplots()
self.heatmap = self.ax.pcolormesh([[0, 0]], [[0, 0]], [[0, 0]])
self.qmin_line = self.ax.plot([0, 0], [0, 0])[0]
self.qmax_line = self.ax.plot([0, 0], [0, 0])[0]
self.cb = self.figure.colorbar(self.heatmap, ax=self.ax, pad=0.0)
# crosshair initialization
self.eline_data = 0
self.qline_data = 0
self.eline = self.ax.axhline(y=self.eline_data)
self.qline = self.ax.axvline(x=self.qline_data)
# draw the plot
self.static_canvas.draw()
[docs]
def update_plot_crosshair(self, crosshair_data: dict) -> None:
"""Update the plot with valid crosshair_data
Args:
crosshair_data: dictionary with: eline (float) x, qline (float) y
"""
self.update_crosshair(crosshair_data["data"]["DeltaE"], crosshair_data["data"]["modQ"])
[docs]
def update_crosshair(self, eline: float, qline: float) -> None:
"""Update the plot with crosshair lines
Args:
eline (float): x
qline (float): y
"""
self.eline_data = eline
self.qline_data = qline
self.eline.set_data([0, 1], [self.eline_data, self.eline_data])
self.qline.set_data([self.qline_data, self.qline_data], [0, 1])
self.set_axes_meta_and_draw_plot()
[docs]
def update_plot(
self,
q_min: list[float],
q_max: list[float],
energy_transfer: list[float],
q2d: list[list[float]],
e2d: list[list[float]],
scharpf_angle: list[list[float]],
plot_label: str,
):
"""Update the colormesh, colorbar and redraw the crosshair
Args:
q_min: list of float numbers,
q_max: list of float numbers,
energy_transfer: list of float numbers,
q2d: list of lists of float numbers,
e2d: list of lists of float numbers,
scharpf_angle: list of lists of float numbers,
plot_label: used for colormap label,
"""
# clear
self.cb.remove()
self.ax.clear()
# update heatmap
self.heatmap = self.ax.pcolormesh(q2d, e2d, scharpf_angle, cmap="jet")
self.ax.plot(q_min, energy_transfer)
self.ax.plot(q_max, energy_transfer)
# Add colorbar
self.cb = self.figure.colorbar(self.heatmap, ax=self.ax, pad=0.0)
self.cb.set_label(plot_label)
# redraw crosshair
self.eline = self.ax.axhline(y=self.eline_data)
self.qline = self.ax.axvline(x=self.qline_data)
self.set_axes_meta_and_draw_plot()
[docs]
def set_axes_meta_and_draw_plot(self):
"""Set labels, color and draw static canvas
Args:
"""
self.ax.set_ylabel(r"$\Delta E$")
self.ax.set_xlabel("$|Q|$")
self.ax.relim()
self.ax.autoscale()
# set the croshair color
self.qline.set_color("darkgrey")
self.eline.set_color("darkgrey")
self.static_canvas.draw()
[docs]
class SelectorWidget(QWidget):
"""Widget that selects Powder/Single Crystal mode"""
def __init__(self, parent: Optional["QObject"] = None) -> None:
"""Constructor for the selector widget
Args:
parent (QObject): Optional parent
"""
super().__init__(parent)
selector_layout = QHBoxLayout()
self.powder_label = "Po&wder"
self.sc_label = "Single C&rystal"
self.powder_rb = QRadioButton(self.powder_label)
self.sc_rb = QRadioButton(self.sc_label)
tooltip = (
"Selection of cursor momentum transfer calculation:"
+ "\n - for Powder user enters |Q| directly"
+ "\n - for Single Crystal user enters |Q| via lattice parameters and H, K, L values"
)
self.powder_rb.setToolTip(tooltip)
self.sc_rb.setToolTip(tooltip)
selector_group = QButtonGroup(self)
selector_group.addButton(self.powder_rb)
selector_group.addButton(self.sc_rb)
selector_layout.addWidget(self.powder_rb)
selector_layout.addWidget(self.sc_rb)
self.setLayout(selector_layout)
if parent:
self.powder_rb.toggled.connect(self.sc_toggle)
self.sc_rb.toggled.connect(self.sc_toggle)
[docs]
def selector_init(self, selected_label: str):
"""Initialize the default selected mode
Args:
selected_label: it contains either sc_label or powder_label
based on the selected label the mode is set during initialization
"""
if selected_label == self.sc_label:
self.sc_rb.setChecked(True)
else:
self.powder_rb.setChecked(True)
[docs]
def sc_toggle(self) -> None:
"""Update fields based on selected mode
Args:
"""
if self.parent():
sender = self.sender().text()
if sender == self.powder_label and self.powder_rb.isChecked():
self.parent().switch_to_powder()
if sender == self.sc_label and self.sc_rb.isChecked():
self.parent().switch_to_sc()
[docs]
def get_selected_mode_label(self) -> str:
"""Return the label of the selected mode
Args:
"""
if self.powder_rb.isChecked():
return self.powder_label
else:
return self.sc_label
[docs]
class SingleCrystalWidget(QWidget):
"""Widget for inputting single crystal parameters"""
valid_signal = Signal(dict)
def __init__(self, parent: Optional["QObject"] = None) -> None:
"""Constructor for the single crystal input parameters widget
Args:
parent (QObject): Optional parent
"""
super().__init__(parent)
layout = QVBoxLayout()
groupBox = QGroupBox("Lattice parameters")
lattice_layout = QGridLayout()
self.lattice_length_validator = QDoubleValidator(bottom=1, top=100, parent=self)
self.lattice_length_validator.setNotation(QDoubleValidator.StandardNotation)
self.lattice_angle_validator = QDoubleValidator(bottom=30, top=150, parent=self)
self.lattice_angle_validator.setNotation(QDoubleValidator.StandardNotation)
self.rlu_validator = QDoubleValidator(bottom=-100, top=100, parent=self)
self.rlu_validator.setNotation(QDoubleValidator.StandardNotation)
# tooltips
tooltip_lattice_length = (
"Lattice parameter {} in Angstroms, a number between 1 and 100." + "\nSee help for validation rules."
)
tooltip_lattice_angle = (
"Lattice angle {} in degrees, a number between 30 and 150." + "\nSee help for validation rules."
)
tooltip_hkl = (
"Single crystal {} coordinate in r.l.u., a number between -100 and 100" + "\nSee help for validation rules."
)
self.a_edit = QLineEdit(self)
self.a_label = QLabel("&a:", self)
self.a_label.setBuddy(self.a_edit)
self.a_edit.setToolTip(tooltip_lattice_length.format("a"))
self.a_label.setToolTip(tooltip_lattice_length.format("a"))
self.a_edit.setValidator(self.lattice_length_validator)
self.b_edit = QLineEdit(self)
self.b_label = QLabel("b:", self)
self.b_label.setBuddy(self.b_edit)
self.b_edit.setToolTip(tooltip_lattice_length.format("b"))
self.b_label.setToolTip(tooltip_lattice_length.format("b"))
self.b_edit.setValidator(self.lattice_length_validator)
self.c_edit = QLineEdit(self)
self.c_label = QLabel("c:", self)
self.c_label.setBuddy(self.c_edit)
self.c_edit.setToolTip(tooltip_lattice_length.format("c"))
self.c_label.setToolTip(tooltip_lattice_length.format("c"))
self.c_edit.setValidator(self.lattice_length_validator)
self.alpha_edit = QLineEdit(self)
self.alpha_label = QLabel(alpha + ":", self)
self.alpha_label.setBuddy(self.alpha_edit)
self.alpha_edit.setToolTip(tooltip_lattice_angle.format(alpha))
self.alpha_label.setToolTip(tooltip_lattice_angle.format(alpha))
self.alpha_edit.setObjectName("alpha")
self.beta_edit = QLineEdit(self)
self.beta_label = QLabel(beta + ":", self)
self.beta_label.setBuddy(self.beta_edit)
self.beta_edit.setToolTip(tooltip_lattice_angle.format(beta))
self.beta_label.setToolTip(tooltip_lattice_angle.format(beta))
self.beta_edit.setObjectName("beta")
self.gamma_edit = QLineEdit(self)
self.gamma_label = QLabel(gamma + ":", self)
self.gamma_label.setBuddy(self.gamma_edit)
self.gamma_edit.setToolTip(tooltip_lattice_angle.format(gamma))
self.gamma_label.setToolTip(tooltip_lattice_angle.format(gamma))
self.gamma_edit.setObjectName("gamma")
self.h_edit = QLineEdit(self)
self.h_label = QLabel("H:", self)
self.h_label.setBuddy(self.h_edit)
self.h_edit.setToolTip(tooltip_hkl.format("H"))
self.h_label.setToolTip(tooltip_hkl.format("H"))
self.h_edit.setValidator(self.rlu_validator)
self.k_edit = QLineEdit(self)
self.k_label = QLabel("K:", self)
self.k_label.setBuddy(self.k_edit)
self.k_edit.setToolTip(tooltip_hkl.format("K"))
self.k_label.setToolTip(tooltip_hkl.format("K"))
self.k_edit.setValidator(self.rlu_validator)
self.l_edit = QLineEdit(self)
self.l_label = QLabel("L:", self)
self.l_label.setBuddy(self.l_edit)
self.l_edit.setToolTip(tooltip_hkl.format("L"))
self.l_label.setToolTip(tooltip_hkl.format("L"))
self.l_edit.setValidator(self.rlu_validator)
# cumulative angle validator
# including the validation of each individual field
self.angle_validator = AngleValidator(
parent=self,
alpha=self.alpha_edit,
beta=self.beta_edit,
gamma=self.gamma_edit,
individual=self.lattice_angle_validator,
)
self.alpha_edit.setValidator(self.angle_validator)
self.beta_edit.setValidator(self.angle_validator)
self.gamma_edit.setValidator(self.angle_validator)
lattice_layout.addWidget(self.a_label, 0, 0)
lattice_layout.addWidget(self.a_edit, 0, 1)
lattice_layout.addWidget(self.b_label, 0, 2)
lattice_layout.addWidget(self.b_edit, 0, 3)
lattice_layout.addWidget(self.c_label, 0, 4)
lattice_layout.addWidget(self.c_edit, 0, 5)
lattice_layout.addWidget(self.alpha_label, 1, 0)
lattice_layout.addWidget(self.alpha_edit, 1, 1)
lattice_layout.addWidget(self.beta_label, 1, 2)
lattice_layout.addWidget(self.beta_edit, 1, 3)
lattice_layout.addWidget(self.gamma_label, 1, 4)
lattice_layout.addWidget(self.gamma_edit, 1, 5)
lattice_layout.addWidget(self.h_label, 2, 0)
lattice_layout.addWidget(self.h_edit, 2, 1)
lattice_layout.addWidget(self.k_label, 2, 2)
lattice_layout.addWidget(self.k_edit, 2, 3)
lattice_layout.addWidget(self.l_label, 2, 4)
lattice_layout.addWidget(self.l_edit, 2, 5)
groupBox.setLayout(lattice_layout)
layout.addWidget(groupBox)
self.setLayout(layout)
# connections
self.a_edit.editingFinished.connect(self.validate_all_inputs)
self.a_edit.textChanged.connect(self.validate_inputs)
self.b_edit.editingFinished.connect(self.validate_all_inputs)
self.b_edit.textChanged.connect(self.validate_inputs)
self.c_edit.editingFinished.connect(self.validate_all_inputs)
self.c_edit.textChanged.connect(self.validate_inputs)
self.alpha_edit.editingFinished.connect(self.validate_all_inputs)
self.alpha_edit.textChanged.connect(self.validate_inputs)
self.beta_edit.editingFinished.connect(self.validate_all_inputs)
self.beta_edit.textChanged.connect(self.validate_inputs)
self.gamma_edit.editingFinished.connect(self.validate_all_inputs)
self.gamma_edit.textChanged.connect(self.validate_inputs)
self.h_edit.editingFinished.connect(self.validate_all_inputs)
self.h_edit.textChanged.connect(self.validate_inputs)
self.k_edit.editingFinished.connect(self.validate_all_inputs)
self.k_edit.textChanged.connect(self.validate_inputs)
self.l_edit.editingFinished.connect(self.validate_all_inputs)
self.l_edit.textChanged.connect(self.validate_inputs)
[docs]
def set_values(self, values: dict[str, float]) -> None:
"""Sets widget display based on the values dictionary
Args:
values (dict): a dictionary that contains
a, b, c, alpha, beta, gamma lattice parameters
and h, k, l reciprocal lattice coordinates
"""
self.a_edit.setText(str(values["a"]))
self.b_edit.setText(str(values["b"]))
self.c_edit.setText(str(values["c"]))
self.alpha_edit.setText(str(values["alpha"]))
self.beta_edit.setText(str(values["beta"]))
self.gamma_edit.setText(str(values["gamma"]))
self.h_edit.setText(str(values["h"]))
self.k_edit.setText(str(values["k"]))
self.l_edit.setText(str(values["l"]))
[docs]
def validate_inputs(self, *_, **__) -> None:
"""Check validity of the fields and set the stylesheet"""
if not self.sender().hasAcceptableInput():
self.sender().setStyleSheet(INVALID_QLINEEDIT)
else:
self.sender().setStyleSheet("")
# cumulative validation style for angles
if self.sender().objectName() in ["alpha", "beta", "gamma"]:
self.validate_angles()
[docs]
def validate_angles(self) -> None:
"""Check validity of the angles and set the stylesheet"""
fields = [
self.alpha_edit,
self.beta_edit,
self.gamma_edit,
]
for field in fields:
state = field.validator().validate(field.text(), 0)[0]
if state != QValidator.Acceptable:
field.setStyleSheet(INVALID_QLINEEDIT)
else:
field.setStyleSheet("")
def validate_all_inputs(self):
inputs = [
self.a_edit,
self.b_edit,
self.c_edit,
self.alpha_edit,
self.beta_edit,
self.gamma_edit,
self.h_edit,
self.k_edit,
self.l_edit,
]
keys = ["a", "b", "c", "alpha", "beta", "gamma", "h", "k", "l"]
out_signal = dict(name="sc_lattice", data=dict())
for k, edit in zip(keys, inputs):
if edit.hasAcceptableInput():
out_signal["data"][k] = float(edit.text())
if len(out_signal["data"]) == 9:
self.valid_signal.emit(out_signal)
[docs]
class ExperimentWidget(QWidget):
"""Widget for setting experiment parameters"""
valid_signal = Signal(dict)
def __init__(self, parent: Optional["QObject"] = None) -> None:
"""Constructor for the experiment input parameters widget
Args:
parent (QObject): Optional parent
"""
super().__init__(parent)
self.Ei_edit = QLineEdit(self)
self.Ei_label = QLabel("Incident energy &Ei:", self)
self.Ei_label.setBuddy(self.Ei_edit)
tooltip_Ei = "Incident energy in meV, a number between 0 and 100."
self.Ei_edit.setToolTip(tooltip_Ei)
self.Ei_label.setToolTip(tooltip_Ei)
self.Ei_validator = QDoubleValidator(bottom=0, top=100, parent=self)
self.Ei_validator.setNotation(QDoubleValidator.StandardNotation)
self.Ei_edit.setValidator(self.Ei_validator)
self.Pangle_edit = QLineEdit(self)
self.Pangle_label = QLabel("&Polarization angle:", self)
self.Pangle_label.setBuddy(self.Pangle_edit)
tooltip_P_angle = "Polarization angle with respect to incident beam, in degrees, a number between -180 and 180."
self.Pangle_edit.setToolTip(tooltip_P_angle)
self.Pangle_label.setToolTip(tooltip_P_angle)
self.Pangle_validator = QDoubleValidator(bottom=-180, top=180, parent=self)
self.Pangle_validator.setNotation(QDoubleValidator.StandardNotation)
self.Pangle_edit.setValidator(self.Pangle_validator)
self.S2_edit = QLineEdit(self)
self.S2_label = QLabel("Detector angle &S2:", self)
self.S2_label.setBuddy(self.S2_edit)
tooltip_S2 = (
"Detector tank angle in degrees, a number between -100 and 100,"
+ "\nbut not between -30 and 30. The detector coverage is +/- 30 degrees around S2"
)
self.S2_edit.setToolTip(tooltip_S2)
self.S2_label.setToolTip(tooltip_S2)
self.S2_validator = AbsValidator(bottom=30, top=100, parent=self)
self.S2_validator.setNotation(QDoubleValidator.StandardNotation)
self.S2_edit.setValidator(self.S2_validator)
self.Type_combobox = QComboBox(self)
self.Type_label = QLabel("&Type:", self)
self.Type_label.setBuddy(self.Type_combobox)
tooltip_type = (
"Quantity to plot. "
+ PLOT_TYPES[0]
+ " is the angle between "
+ "momentum transfer vector Q and the polarization direction."
)
self.Type_combobox.setToolTip(tooltip_type)
self.Type_label.setToolTip(tooltip_type)
layout = QGridLayout()
self.setLayout(layout)
layout.addWidget(self.Ei_label, 0, 0)
layout.addWidget(self.Ei_edit, 0, 1)
layout.addWidget(self.Pangle_label, 0, 2)
layout.addWidget(self.Pangle_edit, 0, 3)
layout.addWidget(self.S2_label, 1, 0)
layout.addWidget(self.S2_edit, 1, 1)
layout.addWidget(self.Type_label, 1, 2)
layout.addWidget(self.Type_combobox, 1, 3)
# connections
self.Ei_edit.editingFinished.connect(self.validate_all_inputs)
self.Ei_edit.textChanged.connect(self.validate_inputs)
self.S2_edit.editingFinished.connect(self.validate_all_inputs)
self.S2_edit.textChanged.connect(self.validate_inputs)
self.Pangle_edit.editingFinished.connect(self.validate_all_inputs)
self.Pangle_edit.textChanged.connect(self.validate_inputs)
self.Type_combobox.currentIndexChanged.connect(self.validate_all_inputs)
[docs]
def initializeCombo(self, options: list[str]) -> None:
"""Initialize the plot types in the combo box
Args:
options (list): list of strings describing what the user can show
in the plot
"""
self.Type_combobox.addItems(options)
[docs]
def validate_inputs(self, *_, **__) -> None:
"""Check validity of the fields and set the stylesheet"""
if not self.sender().hasAcceptableInput():
self.sender().setStyleSheet(INVALID_QLINEEDIT)
else:
self.sender().setStyleSheet("")
[docs]
def validate_all_inputs(self) -> None:
"""If all inputs are valid emit a valid_signal"""
inputs = [self.Ei_edit, self.S2_edit, self.Pangle_edit]
keys = ["Ei", "S2", "alpha_p"]
out_signal = dict(name="experiment", data=dict())
out_signal["data"] = dict(plot_type=self.Type_combobox.currentText())
for k, edit in zip(keys, inputs):
if edit.hasAcceptableInput():
out_signal["data"][k] = float(edit.text())
if len(out_signal["data"]) == 4:
self.valid_signal.emit(out_signal)
[docs]
def set_values(self, values: dict[str, Union[float, str]]) -> None:
"""Sets widget display based on the values dictionary
Args:
values (dict): a dictionary that contains
Ei, S2, alpha_p, plot_types values
"""
self.Ei_edit.setText(str(values["Ei"]))
self.S2_edit.setText(str(values["S2"]))
self.Pangle_edit.setText(str(values["alpha_p"]))
self.Type_combobox.setCurrentIndex(PLOT_TYPES.index(values["plot_type"]))
[docs]
class CrosshairWidget(QWidget):
"""Widget to enter/display crosshair parameters"""
valid_signal = Signal(dict)
def __init__(self, parent: Optional["QObject"] = None) -> None:
"""Constructor for the crosshair input parameters widget
Args:
parent (QObject): Optional parent
"""
super().__init__(parent)
layout = QVBoxLayout()
self.DeltaE_edit = QLineEdit(self)
self.DeltaE_label = QLabel("&DeltaE:", self)
self.DeltaE_label.setBuddy(self.DeltaE_edit)
tooltip_DeltaE = "Energy transfer in meV"
self.DeltaE_edit.setToolTip(tooltip_DeltaE)
self.DeltaE_label.setToolTip(tooltip_DeltaE)
self.modQ_edit = QLineEdit(self)
self.modQ_label = QLabel("|&Q|:", self)
self.modQ_label.setBuddy(self.modQ_edit)
tooltip_modQ = (
"Magnitude of momentum transfer in inverse Angstroms, "
+ f"\na number between 0 and {MAX_MODQ}. Users can enter it directly in powder mode,"
+ "or calculate it based on lattice parameters and H, K, L for single crystal experiments."
)
self.modQ_edit.setToolTip(tooltip_modQ)
self.modQ_label.setToolTip(tooltip_modQ)
self.QZ_angle_edit = QLineEdit(self)
self.QZ_angle_label = QLabel("Q-Beam Angle:", self)
self.QZ_angle_label.setBuddy(self.QZ_angle_edit)
tooltip_QZ_angle = "Angle between Q and beam in degrees."
self.QZ_angle_edit.setToolTip(tooltip_QZ_angle)
self.QZ_angle_label.setToolTip(tooltip_QZ_angle)
self.QZ_angle_edit.setEnabled(False)
self.QZ_angle_edit.setStyleSheet("color: black;")
box_layout = QHBoxLayout()
box_layout.addWidget(self.DeltaE_label)
box_layout.addWidget(self.DeltaE_edit)
box_layout.addWidget(self.modQ_label)
box_layout.addWidget(self.modQ_edit)
box_layout.addWidget(self.QZ_angle_label)
box_layout.addWidget(self.QZ_angle_edit)
self.DeltaE_validator = QDoubleValidator(parent=self)
self.DeltaE_validator.setNotation(QDoubleValidator.StandardNotation)
self.DeltaE_edit.setValidator(self.DeltaE_validator)
self.modQ_validator = QDoubleValidator(bottom=0, top=MAX_MODQ, parent=self)
self.modQ_validator.setNotation(QDoubleValidator.StandardNotation)
self.modQ_edit.setValidator(self.modQ_validator)
# connections
self.DeltaE_edit.editingFinished.connect(self.validate_all_inputs)
self.DeltaE_edit.textChanged.connect(self.validate_inputs)
self.modQ_edit.editingFinished.connect(self.validate_all_inputs)
self.modQ_edit.textChanged.connect(self.validate_inputs)
groupBox = QGroupBox("Crosshair position")
groupBox.setLayout(box_layout)
layout.addWidget(groupBox)
self.setLayout(layout)
[docs]
def set_Qmod_enabled(self, state: bool) -> None:
"""Enable/disable the modQ line edit
Args:
state (bool): enable editing modQ (True) or disable it(False)
"""
self.modQ_edit.setEnabled(state)
[docs]
def set_values(self, values: dict[str, float]) -> None:
"""Sets widget display based on the values dictionary
Args:
values (dict): a dictionary that contains
deltaE and mod_Q values
"""
self.DeltaE_edit.setText(str(values["DeltaE"]))
self.modQ_edit.setText("{:.3f}".format(values["modQ"]))
[docs]
def set_QZ_values(self, angle: float) -> None:
"""Displays the angle between momentum transfer and beam direction
Args:
angle (float): the angle between momentum transfer and beam direction
"""
self.QZ_angle_edit.setText(f"{angle:.3f}")
[docs]
def validate_inputs(self, *_, **__) -> None:
"""Check validity of the fields and set the stylesheet"""
if not self.sender().hasAcceptableInput():
self.sender().setStyleSheet(INVALID_QLINEEDIT)
else:
self.sender().setStyleSheet("")
[docs]
def validation_status_all_inputs(self) -> bool:
"""Return validation status of all inpus, if all are valid returns True, else False"""
inputs = [self.DeltaE_edit, self.modQ_edit]
for edit in inputs:
if not edit.hasAcceptableInput():
return False
return True
[docs]
def validate_all_inputs(self):
"""If all inputs are valid emit a valid_signal"""
inputs = [self.DeltaE_edit, self.modQ_edit]
keys = ["DeltaE", "modQ"]
out_signal = dict(name="crosshair", data=dict())
for k, edit in zip(keys, inputs):
if edit.hasAcceptableInput():
out_signal["data"][k] = float(edit.text())
if len(out_signal["data"]) == 2:
self.valid_signal.emit(out_signal)