Source code for pydm.widgets.byte

from qtpy.QtWidgets import QWidget, QTabWidget, QGridLayout, QLabel, QStyle, QStyleOption
from qtpy.QtGui import QColor, QPen, QFontMetrics, QPainter, QPaintEvent, QBrush
from qtpy.QtCore import Property, Qt, QSize, QPoint
from typing import List, Optional
from .base import PyDMWidget


class PyDMBitIndicator(QWidget):
    """
    A QWidget which draws a colored circle or rectangle

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Label

    """

    def __init__(self, parent: Optional[QWidget] = None, circle: bool = False):
        super(PyDMBitIndicator, self).__init__(parent)
        self.circle = circle
        self._painter = QPainter()
        self._brush = QBrush(Qt.SolidPattern)
        self._pen = QPen(Qt.SolidLine)

    def paintEvent(self, event: QPaintEvent) -> None:
        """
        Paint events are sent to widgets that need to update themselves,
        for instance when part of a widget is exposed because a covering
        widget was moved.

        Parameters
        ----------
        event : QPaintEvent
        """
        self._painter.begin(self)
        opt = QStyleOption()
        opt.initFrom(self)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, self._painter, self)
        self._painter.setRenderHint(QPainter.Antialiasing)
        self._painter.setBrush(self._brush)
        self._painter.setPen(self._pen)
        if self.circle:
            rect = self.rect()
            w = rect.width()
            h = rect.height()
            r = int(min(w, h) / 2.0 - 2.0 * max(self._pen.widthF(), 1.0))
            self._painter.drawEllipse(QPoint(w // 2, h // 2), r, r)
        else:
            self._painter.drawRect(self.rect())
        self._painter.end()

    def setColor(self, color: QColor) -> None:
        """
        Property for the color to be used when drawing

        Parameters
        ----------
        QColor
        """
        self._brush.setColor(color)
        self.update()

    def minimumSizeHint(self) -> QSize:
        fm = QFontMetrics(self.font())
        return QSize(fm.height(), fm.height())


[docs]class PyDMByteIndicator(QWidget, PyDMWidget): """ Widget for graphical representation of bits from an integer number with support for Channels and more from PyDM Parameters ---------- parent : QWidget The parent widget for the Label init_channel : str, optional The channel to be used by the widget. """ def __init__(self, parent: Optional[QWidget] = None, init_channel=None): QWidget.__init__(self, parent) PyDMWidget.__init__(self, init_channel=init_channel) self.value = 0 self.setLayout(QGridLayout(self)) self._on_color = QColor(0, 255, 0) self._off_color = QColor(100, 100, 100) self._disconnected_color = QColor(255, 255, 255) self._invalid_color = QColor(255, 0, 255) self._pen_style = Qt.SolidLine self._line_pen = QPen(self._pen_style) self._orientation = Qt.Vertical # This is kind of ridiculous, importing QTabWidget just to get a 4-item enum thats usable in Designer. # PyQt5 lets you define custom enums that you can use in designer with QtCore.Q_ENUMS(), doesn't exist in PyQt4. self._labels = [] self._show_labels = True self._label_position = QTabWidget.East self._num_bits = 1 self._indicators = [] self._circles = False self.set_spacing() self.layout().setOriginCorner(Qt.TopLeftCorner) self._big_endian = False self._shift = 0 self.numBits = 1 # Need to set the property to initialize # _labels and _indicators setting numBits there also performs # the first rebuild_layout.
[docs] def init_for_designer(self) -> None: """ Method called after the constructor to tweak configurations for when using the widget with the Qt Designer """ self._connected = True self.value = 5 self.update_indicators()
[docs] def connection_changed(self, connected: bool) -> None: """ Callback invoked when the connection state of the Channel is changed. This callback acts on the connection state to enable/disable the widget and also trigger the change on alarm severity to ALARM_DISCONNECTED. Parameters ---------- connected : bool When this value is False the channel is disconnected, True otherwise. """ super(PyDMByteIndicator, self).connection_changed(connected) self.update_indicators()
[docs] def rebuild_layout(self) -> None: """ Method to reorganize the top-level widget and its contents according to the layout property values. """ self.clear() pairs = zip(self._labels, self._indicators) # Hide labels until they are in the layout for label in self._labels: label.setVisible(False) # This is a horrendous mess of if statements # for every possible case. Ugh. # There is probably a more clever way to do this. if self.orientation == Qt.Vertical: for i, (label, indicator) in enumerate(pairs): if self.labelPosition == QTabWidget.East: self.layout().addWidget(indicator, i, 0) self.layout().addWidget(label, i, 1, 1, 1, Qt.AlignVCenter) label.setVisible(self._show_labels) elif self.labelPosition == QTabWidget.West: self.layout().addWidget(label, i, 0, 1, 1, Qt.AlignVCenter) self.layout().addWidget(indicator, i, 1) label.setVisible(self._show_labels) else: self.layout().addWidget(indicator, i, 0) # Invalid combo of orientation and label position, # so we don't reset label visibility here. elif self.orientation == Qt.Horizontal: for i, (label, indicator) in enumerate(pairs): if self.labelPosition == QTabWidget.North: self.layout().addWidget(label, 0, i, 1, 1, Qt.AlignHCenter) self.layout().addWidget(indicator, 1, i) label.setVisible(self._show_labels) elif self.labelPosition == QTabWidget.South: self.layout().addWidget(indicator, 0, i) self.layout().addWidget(label, 1, i, 1, 1, Qt.AlignHCenter) label.setVisible(self._show_labels) else: self.layout().addWidget(indicator, 0, i) # Invalid combo of orientation and label position, so # we don't reset label visibility here. self.update_indicators()
[docs] def clear(self) -> None: """ Remove all inner widgets from the layout """ for col in range(0, self.layout().columnCount()): for row in range(0, self.layout().rowCount()): item = self.layout().itemAtPosition(row, col) if item is not None: w = item.widget() if w is not None: self.layout().removeWidget(w)
[docs] def update_indicators(self) -> None: """ Update the inner bit indicators accordingly with the new value. """ if self._shift < 0: value = int(self.value) << abs(self._shift) else: value = int(self.value) >> self._shift if value < 0: value = 0 bits = [(value >> i) & 1 for i in range(self._num_bits)] for bit, indicator in zip(bits, self._indicators): if self._connected: if self._alarm_state == 3: c = self._invalid_color else: c = self._on_color if bit else self._off_color else: c = self._disconnected_color indicator.setColor(c)
@Property(QColor) def onColor(self) -> QColor: """ The color for a bit in the 'on' state. Returns ------- QColor """ return self._on_color @onColor.setter def onColor(self, new_color: QColor) -> None: """ The color for a bit in the 'on' state. Parameters ---------- new_color : QColor """ if new_color != self._on_color: self._on_color = new_color self.update_indicators() @Property(QColor) def offColor(self) -> QColor: """ The color for a bit in the 'off' state. Returns ------- QColor """ return self._off_color @offColor.setter def offColor(self, new_color: QColor) -> None: """ The color for a bit in the 'off' state. Parameters ---------- new_color : QColor """ if new_color != self._off_color: self._off_color = new_color self.update_indicators() @Property(Qt.Orientation) def orientation(self) -> Qt.Orientation: """ Whether to lay out the bit indicators vertically or horizontally. Returns ------- int """ return self._orientation @orientation.setter def orientation(self, new_orientation: Qt.Orientation) -> None: """ Whether to lay out the bit indicators vertically or horizontally. Parameters ------- new_orientation : Qt.Orientation, int """ self._orientation = new_orientation self.set_spacing() self.rebuild_layout()
[docs] def set_spacing(self) -> None: """ Configures the correct spacing given the selected orientation. """ label_spacing = 5 if self._circles: indicator_spacing = 5 else: indicator_spacing = 0 self.layout().setContentsMargins(0, 0, 0, 0) if self._orientation == Qt.Horizontal: self.layout().setHorizontalSpacing(indicator_spacing) self.layout().setVerticalSpacing(label_spacing) elif self._orientation == Qt.Vertical: self.layout().setHorizontalSpacing(label_spacing) self.layout().setVerticalSpacing(indicator_spacing)
@Property(bool) def showLabels(self) -> bool: """ Whether or not to show labels next to each bit indicator. Returns ------- bool """ return self._show_labels @showLabels.setter def showLabels(self, show: bool) -> None: """ Whether or not to show labels next to each bit indicator. Parameters ------- show : bool If True the widget will show a label next to the bit indicator """ self._show_labels = show for label in self._labels: label.setVisible(show) @Property(bool) def bigEndian(self) -> bool: """ Whether the most significant bit is at the start or end of the widget. Returns ------- bool """ return self._big_endian @bigEndian.setter def bigEndian(self, is_big_endian: bool) -> None: """ Whether the most significant bit is at the start or end of the widget. Parameters ------- is_big_endian : bool If True, the Big Endian will be used, Little Endian otherwise """ self._big_endian = is_big_endian origin_map = { (Qt.Vertical, True): Qt.BottomLeftCorner, (Qt.Vertical, False): Qt.TopLeftCorner, (Qt.Horizontal, True): Qt.TopRightCorner, (Qt.Horizontal, False): Qt.TopLeftCorner, } origin = origin_map[(self.orientation, self.bigEndian)] self.layout().setOriginCorner(origin) self.rebuild_layout() @Property(bool) def circles(self) -> bool: """ Draw indicators as circles, rather than rectangles. Returns ------- bool """ return self._circles @circles.setter def circles(self, draw_circles: bool) -> None: """ Draw indicators as circles, rather than rectangles. Parameters ---------- draw_circles : bool If True, bits will be represented as circles """ self._circles = draw_circles self.set_spacing() for indicator in self._indicators: indicator.circle = self._circles self.update_indicators() @Property(QTabWidget.TabPosition) def labelPosition(self) -> QTabWidget.TabPosition: """ The side of the widget to display labels on. Returns ------- int """ return self._label_position @labelPosition.setter def labelPosition(self, new_pos: QTabWidget.TabPosition) -> None: """ The side of the widget to display labels on. Parameters ---------- new_pos : QTabWidget.TabPosition, int """ self._label_position = new_pos self.rebuild_layout() @Property(int) def numBits(self) -> int: """ Number of bits to interpret. Returns ------- int """ return self._num_bits @numBits.setter def numBits(self, new_num_bits: int) -> None: """ Number of bits to interpret. Parameters ---------- new_num_bits : int """ if new_num_bits < 1: return self._num_bits = new_num_bits for indicator in self._indicators: indicator.deleteLater() self._indicators = [PyDMBitIndicator(parent=self, circle=self.circles) for i in range(0, self._num_bits)] old_labels = self.labels new_labels = ["Bit {}".format(i) for i in range(0, self._num_bits)] for i, old_label in enumerate(old_labels): if i >= self._num_bits: break new_labels[i] = old_label self.labels = new_labels @Property(int) def shift(self) -> int: """ Bit shift. Returns ------- int """ return self._shift @shift.setter def shift(self, new_shift: int) -> None: """ Bit shift. Parameters ---------- new_shift : int """ self._shift = new_shift self.update_indicators() @Property("QStringList") def labels(self) -> List[str]: """ Labels for each bit. Returns ------- list """ return [str(currLabel.text()) for currLabel in self._labels] @labels.setter def labels(self, new_labels: List[str]) -> None: """ Labels for each bit. Parameters ---------- new_labels : list """ for label in self._labels: label.hide() label.deleteLater() self._labels = [QLabel(text, parent=self) for text in new_labels] # Have to reset showLabels to hide or show all the new labels we just made. self.showLabels = self._show_labels self.rebuild_layout()
[docs] def value_changed(self, new_val: int) -> None: """ Callback invoked when the Channel value is changed. Parameters ---------- new_val : int The new value from the channel. """ super(PyDMByteIndicator, self).value_changed(new_val) try: int(new_val) self.update_indicators() except Exception: pass
[docs] def paintEvent(self, _) -> None: """ Paint events are sent to widgets that need to update themselves, for instance when part of a widget is exposed because a covering widget was moved. At PyDMDrawing this method handles the alarm painting with parameters from the stylesheet, configures the brush, pen and calls ```draw_item``` so the specifics can be performed for each of the drawing classes. Parameters ---------- event : QPaintEvent """ painter = QPainter(self) opt = QStyleOption() opt.initFrom(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) painter.setRenderHint(QPainter.Antialiasing)
[docs] def alarm_severity_changed(self, new_alarm_severity: int) -> None: """ Callback invoked when the Channel alarm severity is changed. Parameters ---------- new_alarm_severity : int The new severity where 0 = NO_ALARM, 1 = MINOR, 2 = MAJOR and 3 = INVALID """ if new_alarm_severity == self._alarm_state: return else: super(PyDMByteIndicator, self).alarm_severity_changed(new_alarm_severity) # Checks if _shift attribute exits because base class can call method # before the object constructor is complete if hasattr(self, "_shift"): self.update_indicators()
[docs]class PyDMMultiStateIndicator(QWidget, PyDMWidget): """ Widget with 16 available states that are set by the connected channel. Each state represents a different color that can be configured. Parameters ---------- parent : QWidget The parent widget for the Label init_channel : str, optional The channel to be used by the widget. """ def __init__(self, parent: Optional[QWidget] = None, init_channel=None): QWidget.__init__(self, parent) PyDMWidget.__init__(self, init_channel=init_channel) self._state_colors = [QColor(Qt.black)] * 16 self._curr_state = 0 self._curr_color = QColor(Qt.black) self._painter = QPainter() self._brush = QBrush(Qt.SolidPattern) self._pen = QPen(Qt.SolidLine) self._render_as_rectangle = False # whether or not we render the widget as a circle (default) or a rectangle @Property(bool) def renderAsRectangle(self) -> bool: return self._render_as_rectangle @renderAsRectangle.setter def renderAsRectangle(self, new_val: bool) -> None: if new_val != self._render_as_rectangle: self._render_as_rectangle = new_val
[docs] def value_changed(self, new_val: int) -> None: """ Callback invoked when the Channel value is changed. Parameters ---------- new_val : int The new value from the channel. """ super(PyDMMultiStateIndicator, self).value_changed(new_val) try: int(new_val) if new_val >= 0 and new_val <= 15: # use states 0-15 (16 total states) self._curr_state = int(new_val) self._curr_color = self._state_colors[self._curr_state] self.update() except Exception: pass
[docs] def paintEvent(self, event: QPaintEvent) -> None: """ Paint events are sent to widgets that need to update themselves, for instance when part of a widget is exposed because a covering widget was moved. Paint the widget according to the state colors. Parameters ---------- event : QPaintEvent """ self._painter.begin(self) opt = QStyleOption() opt.initFrom(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, self._painter, self) self._painter.setRenderHint(QPainter.Antialiasing) self._painter.setBrush(QBrush(self._curr_color)) self._painter.setPen(QPen(Qt.red)) if self._render_as_rectangle: self._painter.drawRect(self.rect()) else: # render as circle rect = self.rect() w = rect.width() h = rect.height() r = int(min(w, h) / 2.0 - 2.0 * max(self._pen.widthF(), 1.0)) self._painter.drawEllipse(QPoint(w // 2, h // 2), r, r) self._painter.end()
# color state setters/getters @Property(int) def currentValue(self) -> int: """ The color for when widget is in state 0 Returns ------- int """ return self._curr_state @currentValue.setter def currentValue(self, new_state: int) -> None: """ The color for when widget is in state 0 Parameters ---------- new_color : int """ if new_state != self._curr_state: self._curr_state = new_state self.value_changed(new_state) # color state setters/getters @Property(QColor) def state0Color(self) -> QColor: """ The color for when widget is in state 0 Returns ------- QColor """ return self._state_colors[0] @state0Color.setter def state0Color(self, state_color: QColor) -> None: """ The color for when widget is in state 0 Parameters ---------- new_color : QColor """ if state_color != self._state_colors[0]: self._state_colors[0] = state_color @Property(QColor) def state1Color(self) -> QColor: """ The color for when widget is in state 1 Returns ------- QColor """ return self._state_colors[1] @state1Color.setter def state1Color(self, state_color: QColor) -> None: """ The color for when widget is in state 1 Parameters ---------- new_color : QColor """ if state_color != self._state_colors[1]: self._state_colors[1] = state_color @Property(QColor) def state2Color(self) -> QColor: """ The color for when widget is in state 2 Returns ------- QColor """ return self._state_colors[2] @state2Color.setter def state2Color(self, new_color: QColor) -> None: """ The color for when widget is in state 2 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[2]: self._state_colors[2] = new_color @Property(QColor) def state3Color(self) -> QColor: """ The color for when widget is in state 3 Returns ------- QColor """ return self._state_colors[3] @state3Color.setter def state3Color(self, new_color: QColor) -> None: """ The color for when widget is in state 3 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[3]: self._state_colors[3] = new_color @Property(QColor) def state4Color(self) -> QColor: """ The color for when widget is in state 4 Returns ------- QColor """ return self._state_colors[4] @state4Color.setter def state4Color(self, new_color: QColor) -> None: """ The color for when widget is in state 4 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[4]: self._state_colors[4] = new_color @Property(QColor) def state5Color(self) -> QColor: """ The color for when widget is in state 5 Returns ------- QColor """ return self._state_colors[5] @state5Color.setter def state5Color(self, new_color: QColor) -> None: """ The color for when widget is in state 5 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[5]: self._state_colors[5] = new_color @Property(QColor) def state6Color(self) -> QColor: """ The color for when widget is in state 6 Returns ------- QColor """ return self._state_colors[6] @state6Color.setter def state6Color(self, new_color: QColor) -> None: """ The color for when widget is in state 6 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[6]: self._state_colors[6] = new_color @Property(QColor) def state7Color(self) -> QColor: """ The color for when widget is in state 7 Returns ------- QColor """ return self._state_colors[7] @state7Color.setter def state7Color(self, new_color: QColor) -> None: """ The color for when widget is in state 7 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[7]: self._state_colors[7] = new_color @Property(QColor) def state8Color(self) -> QColor: """ The color for when widget is in state 8 Returns ------- QColor """ return self._state_colors[8] @state8Color.setter def state8Color(self, new_color: QColor) -> None: """ The color for when widget is in state 8 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[8]: self._state_colors[8] = new_color @Property(QColor) def state9Color(self) -> QColor: """ The color for when widget is in state 9 Returns ------- QColor """ return self._state_colors[9] @state9Color.setter def state9Color(self, new_color: QColor) -> None: """ The color for when widget is in state 9 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[9]: self._state_colors[9] = new_color @Property(QColor) def state10Color(self) -> QColor: """ The color for when widget is in state 10 Returns ------- QColor """ return self._state_colors[10] @state10Color.setter def state10Color(self, new_color: QColor) -> None: """ The color for when widget is in state 10 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[10]: self._state_colors[10] = new_color @Property(QColor) def state11Color(self) -> QColor: """ The color for when widget is in state 11 Returns ------- QColor """ return self._state_colors[11] @state11Color.setter def state11Color(self, new_color: QColor) -> None: """ The color for when widget is in state 11 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[11]: self._state_colors[11] = new_color @Property(QColor) def state12Color(self) -> QColor: """ The color for when widget is in state 12 Returns ------- QColor """ return self._state_colors[12] @state12Color.setter def state12Color(self, new_color: QColor) -> None: """ The color for when widget is in state 12 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[12]: self._state_colors[12] = new_color @Property(QColor) def state13Color(self) -> QColor: """ The color for when widget is in state 13 Returns ------- QColor """ return self._state_colors[13] @state13Color.setter def state13Color(self, new_color: QColor) -> None: """ The color for when widget is in state 13 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[13]: self._state_colors[13] = new_color @Property(QColor) def state14Color(self) -> QColor: """ The color for when widget is in state 14 Returns ------- QColor """ return self._state_colors[14] @state14Color.setter def state14Color(self, new_color: QColor) -> None: """ The color for when widget is in state 14 Parameters ---------- new_color : QColor """ if new_color != self._state_colors[14]: self._state_colors[14] = new_color @Property(QColor) def state15Color(self) -> QColor: """ The color for when widget is in state 15 Returns ------- QColor """ return self._state_colors[15] @state15Color.setter def state15Color(self, state_color: QColor) -> None: """ The color for when widget is in state 15 Parameters ---------- new_color : QColor """ if state_color != self._state_colors[15]: self._state_colors[15] = state_color