Source code for pyrogue.pydm.widgets.line_edit

from __future__ import annotations

#-----------------------------------------------------------------------------
# Company    : SLAC National Accelerator Laboratory
#-----------------------------------------------------------------------------
#  Description:
#
#-----------------------------------------------------------------------------
# This file is part of the rogue software platform. It is subject to
# the license terms in the LICENSE.txt file found in the top-level directory
# of this distribution and at:
#    https://confluence.slac.stanford.edu/display/ppareg/LICENSE.html.
# No part of the rogue software platform, including this file, may be
# copied, modified, propagated, or distributed except according to the terms
# contained in the LICENSE.txt file.
#-----------------------------------------------------------------------------
import locale
import numpy as np
import ast
import logging
from pydm.widgets import PyDMLineEdit, PyDMLabel
from qtpy.QtCore import Property, Qt
from qtpy.QtGui import QFocusEvent
from qtpy.QtWidgets import QHBoxLayout, QWidget
from pydm.widgets.base import refresh_style, str_types
from pydm.widgets.display_format import DisplayFormat, parse_value_for_display

logger = logging.getLogger(__name__)

[docs] class PyRogueLineEdit(PyDMLineEdit): """PyDM line edit with Rogue-specific display and unit handling. Parameters ---------- parent : QWidget Parent Qt widget. init_channel : str | None, optional Initial Rogue channel address. show_units : bool, optional If ``True``, display channel units in a dedicated unit label. read_only : bool, optional Unused compatibility parameter retained for legacy call sites. """ def __init__( self, parent: QWidget, init_channel: str | None = None, show_units: bool = True, read_only: bool = False, ) -> None: super().__init__(parent, init_channel=init_channel) self._show_units = show_units self.textEdited.connect(self.text_edited) self._dirty = False self.setStyleSheet("*[dirty='true']\ {background-color: orange;}\ *[readOnly='true'] { color: black; background-color: WhiteSmoke;}") self._unitWidget = PyDMLabel(parent) self._unitWidget.setStyleSheet("QLabel { color : DimGrey; }") self._unitWidget.setAlignment(Qt.AlignRight) self._unitWidget.setText("") hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(self._unitWidget) hbox.setContentsMargins(0, 0, 0, 0) self.setLayout(hbox)
[docs] def text_edited(self) -> None: """Mark widget dirty when user edits text.""" self._dirty = True refresh_style(self)
[docs] def focusOutEvent(self, event: QFocusEvent) -> None: """Clear dirty state when focus leaves the widget.""" self._dirty = False super(PyRogueLineEdit, self).focusOutEvent(event) refresh_style(self)
[docs] def check_enable_state(self) -> None: """Update read-only state based on channel write access.""" status = self._write_access and self._connected self.setReadOnly(not status) refresh_style(self)
@Property(bool) def dirty(self) -> bool: """Whether user-edited text is pending commit.""" return self._dirty
[docs] def unit_changed(self, new_unit: str) -> None: """Update displayed units text.""" super().unit_changed(new_unit) if self._show_units: self._unitWidget.setText(new_unit)
[docs] def send_value(self) -> None: """ Emit a :attr:`send_value_signal` to update channel value. The text is cleaned of all units, user-formatting and scale values before being sent back to the channel. This function is attached the ReturnPressed signal of the PyDMLineEdit """ send_value = str(self.text()) try: if self.channeltype not in [str, np.ndarray, bool]: scale = self._scale if scale is None or scale == 0: scale = 1.0 if self._display_format_type in [DisplayFormat.Default, DisplayFormat.String]: if self.channeltype == float: num_value = locale.atof(send_value) else: num_value = self.channeltype(send_value) scale = self.channeltype(scale) elif self._display_format_type == DisplayFormat.Hex: num_value = int(send_value, 16) elif self._display_format_type == DisplayFormat.Binary: num_value = int(send_value, 2) elif self._display_format_type in [DisplayFormat.Exponential, DisplayFormat.Decimal]: num_value = locale.atof(send_value) num_value = num_value / scale self.send_value_signal[self.channeltype].emit(num_value) elif self.channeltype == np.ndarray: # Arrays will be in the [1.2 3.4 22.214] format if self._display_format_type == DisplayFormat.String: self.send_value_signal[str].emit(send_value) else: arr_value = list( filter(None, ast.literal_eval(str(send_value.replace("[", "").replace("]", "").split())))) arr_value = np.array(arr_value, dtype=self.subtype) self.send_value_signal[np.ndarray].emit(arr_value) elif self.channeltype == bool: try: val = bool(PyDMLineEdit.strtobool(send_value)) self.send_value_signal[bool].emit(val) # might want to add error to application screen except ValueError: logger.error("Not a valid boolean: %r", send_value) else: # Channel Type is String # Lets just send what we have after all self.send_value_signal[str].emit(send_value) except ValueError: logger.exception("Error trying to set data '{0}' with type '{1}' and format '{2}' at widget '{3}'." .format(self.text(), self.channeltype, self._display_format_type, self.objectName())) self.clearFocus() self.set_display()
[docs] def set_display(self) -> None: """ Set the text display of the PyDMLineEdit. The original value given by the PV is converted to a text entry based on the current settings for scale value, precision, a user-defined format, and the current units. If the user is currently entering a value in the PyDMLineEdit the text will not be changed. """ if self.value is None: return if self.hasFocus(): return new_value = self.value if self._display_format_type in [DisplayFormat.Default, DisplayFormat.Decimal, DisplayFormat.Exponential, DisplayFormat.Hex, DisplayFormat.Binary]: if self.channeltype not in (str, np.ndarray): try: new_value *= self.channeltype(self._scale) except TypeError: logger.error("Cannot convert the value '{0}', for channel '{1}', to type '{2}'. ".format( self._scale, self._channel, self.channeltype)) new_value = parse_value_for_display(value=new_value, precision=self.precision, display_format_type=self._display_format_type, string_encoding=self._string_encoding, widget=self) if type(new_value) in str_types: self._display = new_value else: self._display = str(new_value) if self._display_format_type == DisplayFormat.Default: if isinstance(new_value, (int, float)): self._display = str(self.format_string.format(new_value)) self.setText(self._display) return self.setText(self._display)