Пример #1
0
    def __init__(self, data, color=Qt.green, parent=None):
        if ("size_x" in data.index) and ("size_y" in data.index):
            size_x = data["size_x"]
            size_y = data["size_y"]
        else:
            size_x = size_y = data["size"]
        super().__init__(data["x"] - size_x + 0.5, data["y"] - size_y + 0.5,
                         2 * size_x, 2 * size_y, parent)
        pen = QPen()
        pen.setWidthF(1.25)
        pen.setCosmetic(True)
        pen.setColor(color)
        self.setPen(pen)

        ttStr = "\n".join([
            "{}: {:.2f}".format(k, v) for k, v in data.items() if k != "frame"
        ])
        self.setToolTip(ttStr)
Пример #2
0
class BaseSymbolIcon(QWidget):
    """
    Base class to be used for all the Symbol Icon widgets.
    This class holds most of the properties to be exposed and takes care of 90%
    of the drawing code needed.

    Parameters
    ----------
    parent : QWidget
        The parent widget for this widget.
    """
    def __init__(self, parent=None):
        self._brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern)
        self._original_brush = None
        self._rotation = 0
        self._pen_style = Qt.SolidLine
        self._pen = QPen(self._pen_style)
        self._pen.setCosmetic(True)
        self._pen_width = 1.0
        self._pen_color = QColor(0, 0, 0)
        self._pen.setWidthF(self._pen_width)
        self._pen.setColor(self._pen_color)
        self._original_pen_style = self._pen_style
        self._original_pen_color = self._pen_color
        super(BaseSymbolIcon, self).__init__(parent)
        self.setObjectName("icon")

    def minimumSizeHint(self):
        return QSize(32, 32)

    def paintEvent(self, event):
        """
        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.

        This method handles the painting with parameters from the stylesheet,
        configures the brush, pen and calls ```draw_icon``` so the specifics
        can be performed for each of the drawing classes.

        Parameters
        ----------
        event : QPaintEvent
        """
        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)
        painter.setClipping(True)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
        painter.setRenderHint(QPainter.Antialiasing)
        x = event.rect().x()
        y = event.rect().y()
        w = event.rect().width()
        h = event.rect().height()
        painter.translate(w / 2.0, h / 2.0)
        painter.rotate(self._rotation)
        painter.translate(-w / 2.0, -h / 2.0)
        painter.translate(self._pen_width / 2.0, self._pen_width / 2.0)
        painter.scale(w - self._pen_width, h - self._pen_width)
        painter.translate(x, y)
        painter.setBrush(self._brush)
        painter.setPen(self._pen)
        self.draw_icon(painter)

        QWidget.paintEvent(self, event)

    def draw_icon(self, painter):
        """
        Method responsible for the drawing of the icon part of the paintEvent.
        This method must be implemented by the symbol icon widgets to include
        their specific drawings.

        Parameters
        ----------
        painter : QPainter
        """
        raise NotImplementedError("draw_icon must be implemented by subclass")

    @Property(QBrush)
    def brush(self):
        """
        PyQT Property for the brush object to be used when coloring the
        drawing

        Returns
        -------
        QBrush
        """
        return self._brush

    @brush.setter
    def brush(self, new_brush):
        """
        PyQT Property for the brush object to be used when coloring the
        drawing

        Parameters
        ----------
        new_brush : QBrush
        """
        if new_brush != self._brush:
            self._brush = new_brush
            self.update()

    @Property(Qt.PenStyle)
    def penStyle(self):
        """
        PyQT Property for the pen style to be used when drawing the border

        Returns
        -------
        int
            Index at Qt.PenStyle enum
        """
        return self._pen_style

    @penStyle.setter
    def penStyle(self, new_style):
        """
        PyQT Property for the pen style to be used when drawing the border

        Parameters
        ----------
        new_style : int
            Index at Qt.PenStyle enum
        """
        if new_style != self._pen_style:
            self._pen_style = new_style
            self._pen.setStyle(new_style)
            self.update()

    @Property(QColor)
    def penColor(self):
        """
        PyQT Property for the pen color to be used when drawing the border

        Returns
        -------
        QColor
        """
        return self._pen_color

    @penColor.setter
    def penColor(self, new_color):
        """
        PyQT Property for the pen color to be used when drawing the border

        Parameters
        ----------
        new_color : QColor
        """
        if new_color != self._pen_color:
            self._pen_color = new_color
            self._pen.setColor(new_color)
            self.update()

    @Property(float)
    def penWidth(self):
        """
        PyQT Property for the pen width to be used when drawing the border

        Returns
        -------
        float
        """
        return self._pen_width

    @penWidth.setter
    def penWidth(self, new_width):
        """
        PyQT Property for the pen width to be used when drawing the border

        Parameters
        ----------
        new_width : float
        """
        if new_width < 0:
            return
        if new_width != self._pen_width:
            self._pen_width = new_width
            self._pen.setWidth(self._pen_width)
            self.update()

    @Property(float)
    def rotation(self):
        """
        PyQt Property for the rotation.
        This rotates the icon coordinate system clockwise.

        Returns
        -------
        angle : float
            The angle in degrees
        """
        return self._rotation

    @rotation.setter
    def rotation(self, angle):
        """
        PyQt Property for the rotation.
        This rotates the icon coordinate system clockwise.

        Parameters
        ----------
        angle : float
            The angle in degrees
        """
        self._rotation = angle
        self.update()
Пример #3
0
class BaseSymbolIcon(QWidget):
    """
    Base class to be used for all the Symbol Icon widgets.
    This class holds most of the properties to be exposed and takes care of 90%
    of the drawing code needed.

    Parameters
    ----------
    parent : QWidget
        The parent widget for this widget.
    """
    clicked = Signal()

    def __init__(self, parent=None):
        self._brush = QBrush(QColor(0, 255, 0), Qt.SolidPattern)
        self._original_brush = None
        self._rotation = 0
        self._pen_style = Qt.SolidLine
        self._pen = QPen(self._pen_style)
        self._pen.setCosmetic(True)
        self._pen_width = 1.0
        self._pen_color = QColor(0, 0, 0)
        self._pen.setWidthF(self._pen_width)
        self._pen.setColor(self._pen_color)
        self._original_pen_style = self._pen_style
        self._original_pen_color = self._pen_color
        super(BaseSymbolIcon, self).__init__(parent=parent)
        self.setObjectName("icon")
        if not is_qt_designer():
            # We should  install the Event Filter only if we are running
            # and not at the Designer
            self.installEventFilter(self)

    def eventFilter(self, obj, event):
        """
        EventFilter to redirect "middle click" to :meth:`.show_address_tooltip`
        """
        # Override the eventFilter to capture all middle mouse button events,
        # and show a tooltip if needed.
        if event.type() == QEvent.MouseButtonPress:
            if event.button() == Qt.MiddleButton:
                self.show_state_channel(event)
                return True
        return False

    def show_state_channel(self, event):
        """
        Show the State Channel Tooltip and copy address to clipboard

        This is intended to replicate the behavior of the "middle click" from
        EDM. If the parent is not PCDSSymbolBase and does not have a valid
        State Channel nothing will be displayed.
        """
        from ..vacuum.base import PCDSSymbolBase

        p = find_ancestor_for_widget(self, PCDSSymbolBase)
        if not p:
            return

        state_suffix = getattr(p, '_state_suffix', None)
        if not state_suffix:
            return

        addr = "{}{}".format(p.channelsPrefix, state_suffix)
        QToolTip.showText(event.globalPos(), addr)
        # If the address has a protocol, strip it out before putting it on the
        # clipboard.
        copy_text = remove_protocol(addr)

        clipboard = QApplication.clipboard()
        clipboard.setText(copy_text)
        event = QEvent(QEvent.Clipboard)
        QApplication.instance().sendEvent(clipboard, event)

    def minimumSizeHint(self):
        return QSize(32, 32)

    def paintEvent(self, event):
        """
        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.

        This method handles the painting with parameters from the stylesheet,
        configures the brush, pen and calls ```draw_icon``` so the specifics
        can be performed for each of the drawing classes.

        Parameters
        ----------
        event : QPaintEvent
        """
        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)
        painter.setClipping(True)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
        painter.setRenderHint(QPainter.Antialiasing)
        w = self.width()
        h = self.height()
        painter.translate(w / 2.0, h / 2.0)
        painter.rotate(self._rotation)
        painter.translate(-w / 2.0, -h / 2.0)
        painter.translate(self._pen_width / 2.0, self._pen_width / 2.0)
        painter.scale(w - self._pen_width, h - self._pen_width)
        painter.translate(0, 0)
        painter.setBrush(self._brush)
        painter.setPen(self._pen)
        self.draw_icon(painter)

        QWidget.paintEvent(self, event)

    def draw_icon(self, painter):
        """
        Method responsible for the drawing of the icon part of the paintEvent.
        This method must be implemented by the symbol icon widgets to include
        their specific drawings.

        Parameters
        ----------
        painter : QPainter
        """
        raise NotImplementedError("draw_icon must be implemented by subclass")

    @Property(QBrush)
    def brush(self):
        """
        PyQT Property for the brush object to be used when coloring the
        drawing

        Returns
        -------
        QBrush
        """
        return self._brush

    @brush.setter
    def brush(self, new_brush):
        """
        PyQT Property for the brush object to be used when coloring the
        drawing

        Parameters
        ----------
        new_brush : QBrush
        """
        if new_brush != self._brush:
            self._brush = new_brush
            self.update()

    @Property(Qt.PenStyle)
    def penStyle(self):
        """
        PyQT Property for the pen style to be used when drawing the border

        Returns
        -------
        int
            Index at Qt.PenStyle enum
        """
        return self._pen_style

    @penStyle.setter
    def penStyle(self, new_style):
        """
        PyQT Property for the pen style to be used when drawing the border

        Parameters
        ----------
        new_style : int
            Index at Qt.PenStyle enum
        """
        if new_style != self._pen_style:
            self._pen_style = new_style
            self._pen.setStyle(new_style)
            self.update()

    @Property(QColor)
    def penColor(self):
        """
        PyQT Property for the pen color to be used when drawing the border

        Returns
        -------
        QColor
        """
        return self._pen_color

    @penColor.setter
    def penColor(self, new_color):
        """
        PyQT Property for the pen color to be used when drawing the border

        Parameters
        ----------
        new_color : QColor
        """
        if new_color != self._pen_color:
            self._pen_color = new_color
            self._pen.setColor(new_color)
            self.update()

    @Property(float)
    def penWidth(self):
        """
        PyQT Property for the pen width to be used when drawing the border

        Returns
        -------
        float
        """
        return self._pen_width

    @penWidth.setter
    def penWidth(self, new_width):
        """
        PyQT Property for the pen width to be used when drawing the border

        Parameters
        ----------
        new_width : float
        """
        if new_width < 0:
            return
        if new_width != self._pen_width:
            self._pen_width = new_width
            self._pen.setWidth(self._pen_width)
            self.update()

    @Property(float)
    def rotation(self):
        """
        PyQt Property for the rotation.
        This rotates the icon coordinate system clockwise.

        Returns
        -------
        angle : float
            The angle in degrees
        """
        return self._rotation

    @rotation.setter
    def rotation(self, angle):
        """
        PyQt Property for the rotation.
        This rotates the icon coordinate system clockwise.

        Parameters
        ----------
        angle : float
            The angle in degrees
        """
        self._rotation = angle
        self.update()

    def mousePressEvent(self, evt):
        """Clicking the icon fires the ``clicked`` signal"""
        # Default Qt reaction to the icon press
        super().mousePressEvent(evt)
        if evt.button() == Qt.LeftButton:
            self.clicked.emit()
Пример #4
0
class MicroViewScene(QGraphicsScene):
    def __init__(self, parent):
        super().__init__(parent)

        self._imageItem = ImageGraphicsItem()
        self.addItem(self._imageItem)
        self._roiMode = False

        self._drawingRoi = False

        self.roiPen = QPen()
        self.roiPen.setWidthF(1.25)
        self.roiPen.setCosmetic(True)
        self.roiPen.setColor(Qt.yellow)

        self._roiPolygon = QPolygonF()
        self._roiItem = QGraphicsPolygonItem(self._roiPolygon)
        self._roiItem.setPen(self.roiPen)
        self.addItem(self._roiItem)

    def setImage(self, img):
        if isinstance(img, QImage):
            img = QPixmap.fromImage(img)
        self._imageItem.setPixmap(img)

    @Property(ImageGraphicsItem)
    def imageItem(self):
        return self._imageItem

    def enableRoiMode(self, enable):
        if enable == self._roiMode:
            return
        if enable:
            self._roiPolygon = QPolygonF()
            self._roiItem.setPolygon(self._roiPolygon)
            self._tempRoiPolygon = QPolygonF(self._roiPolygon)
            self._tempRoiPolygon.append(QPointF())
        if not enable:
            self._roiItem.setPolygon(self._roiPolygon)
            self.roiChanged.emit(self._roiPolygon)
        self._roiMode = enable
        self.roiModeChanged.emit(enable)

    roiModeChanged = Signal(bool)

    @Property(bool, fset=enableRoiMode)
    def roiMode(self):
        return self._roiMode

    def setRoi(self, roi):
        if self._roiPolygon == roi:
            return
        self._roiPolygon = roi
        self._roiItem.setPolygon(self._roiPolygon)
        self.roiChanged.emit(roi)

    roiChanged = Signal(QPolygonF)

    @Property(QPolygonF,
              fset=setRoi,
              doc="Polygon describing the region of interest (ROI)")
    def roi(self):
        return self._roiPolygon

    def _appendPointToRoi(self, pos, polygon, replace_last=False):
        br = self._imageItem.boundingRect()
        topLeft = br.topLeft()
        bottomRight = br.bottomRight()
        # Make sure we stay inside the image boundaries
        xInBr = max(topLeft.x(), pos.x())
        xInBr = min(bottomRight.x(), xInBr)
        yInBr = max(topLeft.y(), pos.y())
        yInBr = min(bottomRight.y(), yInBr)
        pointInBr = QPointF(xInBr, yInBr)

        if replace_last:
            polygon[-1] = pointInBr
        else:
            polygon.append(pointInBr)

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        if not self._roiMode:
            return
        self._appendPointToRoi(event.scenePos(), self._roiPolygon, False)
        self._roiItem.setPolygon(self._roiPolygon)
        self._tempRoiPolygon = QPolygonF(self._roiPolygon)
        self._tempRoiPolygon.append(QPointF())

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        if not self._roiMode:
            return
        self._appendPointToRoi(event.scenePos(), self._tempRoiPolygon, True)
        self._roiItem.setPolygon(self._tempRoiPolygon)

    def mouseDoubleClickEvent(self, event):
        super().mouseDoubleClickEvent(event)
        # the first click of the double click is a normal mousePressEvent,
        # thus the current point has already been added. Simply exit ROI mode
        self.roiMode = False
Пример #5
0
class TunableMarker(QGraphicsItem):
    def __init__(self,
                 shape: Shape,
                 size: int,
                 key: Optional[int] = None,
                 **kwargs):
        super().__init__()
        self._shape = shape
        self._size = size
        self.key = key
        self.info = kwargs

        self._pen = QPen()
        self._pen.setWidthF(0.25)
        self._brush = QBrush()

    def _make_rect(self, length):
        # make a rectangle of width and height equal to 'length' and centered
        # about (0, 0)
        return QRectF(-length / 2, -length / 2, length, length)

    def boundingRect(self):
        return self._make_rect(self._size + 4)

    def _paint_ellipse(self, painter):
        rect = self._make_rect(self._size)
        painter.drawEllipse(rect)

    def _paint_diamond(self, painter):
        size = self._size
        X = [0, size / 2, 0, -size / 2, 0]
        Y = [size / 2, 0, -size / 2, 0, size / 2]
        points = [QPointF(x, y) for x, y in zip(X, Y)]
        polygon = QPolygonF(points)
        painter.drawPolygon(polygon, len(points))

    def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem,
              widget: QWidget):

        painter.setPen(self._pen)
        painter.setBrush(self._brush)

        shape_to_paint_method = {
            Shape.CIRCLE: self._paint_ellipse,
            Shape.DIAMOND: self._paint_diamond
        }
        drawing_method = shape_to_paint_method[self._shape]
        drawing_method(painter)

    def get_marker_color(self):
        """
        Marker color.
        """
        return self._pen.color()

    def set_marker_color(self, color: Union[QColor, int]):
        color = QColor(color)
        self._pen.setColor(color)
        self._brush.setColor(color)
        self.update()

    def get_marker_fill(self) -> bool:
        """
        Whether this marker is filled in or merely an outline.
        """
        return bool(self._brush.style())

    def set_marker_fill(self, f: bool):
        if f:
            f = Qt.SolidPattern
        else:
            f = Qt.NoBrush
        self._brush.setStyle(f)
        self.update()

    def get_marker_shape(self):
        return self._shape

    def set_marker_shape(self, shape: Shape):
        self._shape = shape
        self.update()

    def get_marker_size(self):
        return self._size

    def set_marker_size(self, sz: int):
        self._size = sz
        self.update()
Пример #6
0
class PyDMDrawing(QWidget, PyDMWidget):
    """
    Base class to be used for all PyDM Drawing Widgets.
    This class inherits from QWidget and PyDMWidget.

    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=None, init_channel=None):
        self._rotation = 0.0
        self._brush = QBrush(Qt.SolidPattern)
        self._original_brush = None
        self._painter = QPainter()
        self._pen = QPen(Qt.NoPen)
        self._pen_style = Qt.NoPen
        self._pen_cap_style = Qt.SquareCap
        self._pen_join_style = Qt.MiterJoin
        self._pen_width = 0
        self._pen_color = QColor(0, 0, 0)
        self._pen.setCapStyle(self._pen_cap_style)
        self._pen.setJoinStyle(self._pen_join_style)
        self._original_pen_style = self._pen_style
        self._original_pen_color = self._pen_color
        QWidget.__init__(self, parent)
        PyDMWidget.__init__(self, init_channel=init_channel)
        self.alarmSensitiveBorder = False

    def sizeHint(self):
        return QSize(100, 100)

    def paintEvent(self, _):
        """
        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)

        painter.setBrush(self._brush)
        painter.setPen(self._pen)

        self.draw_item(painter)

    def draw_item(self, painter):
        """
        The classes inheriting from PyDMDrawing must overwrite this method.
        This method translate the painter to the center point given by
        ```get_center``` and rotate the canvas by the given amount of
        degrees.
        """
        xc, yc = self.get_center()
        painter.translate(xc, yc)
        painter.rotate(-self._rotation)

    def get_center(self):
        """
        Simple calculation of the canvas' center point.

        Returns
        -------
        x, y : float
            Tuple with X and Y coordinates of the center.
        """
        return self.width() * 0.5, self.height() * 0.5

    def get_bounds(self, maxsize=False, force_no_pen=False):
        """
        Returns a tuple containing the useful area for the drawing.

        Parameters
        ----------
        maxsize : bool, default is False
            If True, width and height information are based on the
            maximum inner rectangle dimensions given by ```get_inner_max```,
            otherwise width and height will receive the widget size.

        force_no_pen : bool, default is False
            If True the pen width will not be considered when calculating
            the bounds.

        Returns
        -------
        x, y, w, h : tuple
            Tuple with X and Y coordinates followed by the maximum width
            and height.
        """
        w, h = self.width(), self.height()

        if maxsize:
            w, h = self.get_inner_max()

        xc, yc = w * 0.5, h * 0.5

        if self.has_border() and not force_no_pen:
            w = max(0, w - 2 * self._pen_width)
            h = max(0, h - 2 * self._pen_width)
            x = max(0, self._pen_width)
            y = max(0, self._pen_width)
        else:
            x = 0
            y = 0
        return x - xc, y - yc, w, h

    def has_border(self):
        """
        Check whether or not the drawing have a border based on the
        Pen Style and Pen width.

        Returns
        -------
        bool
            True if the drawing has a border, False otherwise.
        """
        if self._pen.style() != Qt.NoPen and self._pen_width > 0:
            return True
        else:
            return False

    def is_square(self):
        """
        Check if the widget has the same width and height values.

        Returns
        -------
        bool
            True in case the widget has a square shape, False otherwise.
        """
        return self.height() == self.width()

    def get_inner_max(self):
        """
        Calculates the largest inner rectangle in a rotated rectangle.
        This implementation was based on https://stackoverflow.com/a/18402507

        Returns
        -------
        w, h : tuple
            The width and height of the largest rectangle.
        """
        # Based on https://stackoverflow.com/a/18402507
        w0 = 0
        h0 = 0
        angle = math.radians(self._rotation)
        origWidth = self.width()
        origHeight = self.height()

        if origWidth == 0:
            logger.error("Invalid width. The value must be greater than {0}".format(origWidth))
            return

        if origHeight == 0:
            logger.error("Invalid height. The value must be greater than {0}".format(origHeight))
            return

        if (origWidth <= origHeight):
            w0 = origWidth
            h0 = origHeight
        else:
            w0 = origHeight
            h0 = origWidth
        # Angle normalization in range [-PI..PI)
        ang = angle - math.floor((angle + math.pi) / (2 * math.pi)) * 2 * math.pi
        ang = math.fabs(ang)
        if ang > math.pi / 2:
            ang = math.pi - ang
        c = w0 / (h0 * math.sin(ang) + w0 * math.cos(ang))
        w = 0
        h = 0
        if (origWidth <= origHeight):
            w = w0 * c
            h = h0 * c
        else:
            w = h0 * c
            h = w0 * c
        return w, h

    @Property(QBrush)
    def brush(self):
        """
        PyQT Property for the brush object to be used when coloring the
        drawing

        Returns
        -------
        QBrush
        """
        return self._brush

    @brush.setter
    def brush(self, new_brush):
        """
        PyQT Property for the brush object to be used when coloring the
        drawing

        Parameters
        ----------
        new_brush : QBrush
        """
        if new_brush != self._brush:
            if self._alarm_state == PyDMWidget.ALARM_NONE:
                self._original_brush = new_brush
            self._brush = new_brush
            self.update()

    @Property(Qt.PenStyle)
    def penStyle(self):
        """
        PyQT Property for the pen style to be used when drawing the border

        Returns
        -------
        int
            Index at Qt.PenStyle enum
        """
        return self._pen_style

    @penStyle.setter
    def penStyle(self, new_style):
        """
        PyQT Property for the pen style to be used when drawing the border

        Parameters
        ----------
        new_style : int
            Index at Qt.PenStyle enum
        """
        if self._alarm_state == PyDMWidget.ALARM_NONE:
            self._original_pen_style = new_style
        if new_style != self._pen_style:
            self._pen_style = new_style
            self._pen.setStyle(new_style)
            self.update()

    @Property(Qt.PenCapStyle)
    def penCapStyle(self):
        """
        PyQT Property for the pen cap to be used when drawing the border

        Returns
        -------
        int
            Index at Qt.PenCapStyle enum
        """
        return self._pen_cap_style

    @penCapStyle.setter
    def penCapStyle(self, new_style):
        """
        PyQT Property for the pen cap style to be used when drawing the border

        Parameters
        ----------
        new_style : int
            Index at Qt.PenStyle enum
        """
        if new_style != self._pen_cap_style:
            self._pen_cap_style = new_style
            self._pen.setCapStyle(new_style)
            self.update()

    @Property(Qt.PenJoinStyle)
    def penJoinStyle(self):
        """
        PyQT Property for the pen join style to be used when drawing the border

        Returns
        -------
        int
            Index at Qt.PenJoinStyle enum
        """
        return self._pen_join_style

    @penJoinStyle.setter
    def penJoinStyle(self, new_style):
        """
        PyQT Property for the pen join style to be used when drawing the border

        Parameters
        ----------
        new_style : int
            Index at Qt.PenStyle enum
        """
        if new_style != self._pen_join_style:
            self._pen_join_style = new_style
            self._pen.setJoinStyle(new_style)
            self.update()

    @Property(QColor)
    def penColor(self):
        """
        PyQT Property for the pen color to be used when drawing the border

        Returns
        -------
        QColor
        """
        return self._pen_color

    @penColor.setter
    def penColor(self, new_color):
        """
        PyQT Property for the pen color to be used when drawing the border

        Parameters
        ----------
        new_color : QColor
        """
        if self._alarm_state == PyDMWidget.ALARM_NONE:
            self._original_pen_color = new_color

        if new_color != self._pen_color:
            self._pen_color = new_color
            self._pen.setColor(new_color)
            self.update()

    @Property(float)
    def penWidth(self):
        """
        PyQT Property for the pen width to be used when drawing the border

        Returns
        -------
        float
        """
        return self._pen_width

    @penWidth.setter
    def penWidth(self, new_width):
        """
        PyQT Property for the pen width to be used when drawing the border

        Parameters
        ----------
        new_width : float
        """
        if new_width < 0:
            return
        if new_width != self._pen_width:
            self._pen_width = new_width
            self._pen.setWidthF(float(self._pen_width))
            self.update()

    @Property(float)
    def rotation(self):
        """
        PyQT Property for the counter-clockwise rotation in degrees
        to be applied to the drawing.

        Returns
        -------
        float
        """
        return self._rotation

    @rotation.setter
    def rotation(self, new_angle):
        """
        PyQT Property for the counter-clockwise rotation in degrees
        to be applied to the drawing.

        Parameters
        ----------
        new_angle : float
        """
        if new_angle != self._rotation:
            self._rotation = new_angle
            self.update()

    def alarm_severity_changed(self, new_alarm_severity):
        PyDMWidget.alarm_severity_changed(self, new_alarm_severity)
        if new_alarm_severity == PyDMWidget.ALARM_NONE:
            if self._original_brush is not None:
                self.brush = self._original_brush
            if self._original_pen_color is not None:
                self.penColor = self._original_pen_color
            if self._original_pen_style is not None:
                self.penStyle = self._original_pen_style