예제 #1
0
    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)
예제 #2
0
    def paintEvent(self, event):

        painter = QPainter(self)
        painter.setRenderHints(QPainter.Antialiasing)

        side = min(self.width(), self.height())
        painter.scale(side / 32.0, side / 32.0)

        painter.setPen(Qt.NoPen)

        if not self.is_pressed:
            painter.setBrush(self.back_color)
        else:
            painter.setBrush(
                QColor(self.back_color.red() + 30,
                       self.back_color.green() + 30,
                       self.back_color.blue() + 30))

        painter.drawRoundedRect(QRect(0, 0, 32, 32), 8, 8)

        if self.is_checked:

            check_path = QPainterPath()
            check_path.moveTo(self.x1)
            check_path.lineTo(self.x2)
            check_path.lineTo(self.x3)

            pen = QPen(self.check_color, self.check_thick, Qt.SolidLine)
            painter.setPen(pen)

            painter.drawPath(check_path)
예제 #3
0
    def paintEvent(self, event):
        side = min(self.width(), self.height())

        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.translate(self.width() / 2,
                          self.height() / 2)  # painter坐标系原点移至widget中央
        painter.scale(side / 200,
                      side / 200)  # 缩放painterwidget坐标系,使绘制的时钟位于widge中央,即钟表支持缩放

        self.draw_panel(painter)  # 画外框表盘
        self.draw_scale_num(painter)  # 画刻度数字
        self.draw_scale_line(painter)  # 画刻度线
        self.drawTitle(painter)  # 画标题备注
        self.drawValue(painter)  # 画数显
        self.drawIndicator(painter)  # 画指针
예제 #4
0
    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.

        At PyDMSymbolEditor this method handles the image preview.

        Parameters
        ----------
        event : QPaintEvent
        """
        if not self.preview:
            return
        size = QSize(140, 140)
        _painter = QPainter()
        _painter.begin(self)
        opt = QStyleOption()
        opt.initFrom(self)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, _painter, self)
        image_to_draw = self.preview_file
        if isinstance(image_to_draw, QPixmap):
            w = float(image_to_draw.width())
            h = float(image_to_draw.height())
            sf = min(size.width() / w, size.height() / h)
            scale = (sf, sf)
            _painter.scale(scale[0], scale[1])
            _painter.drawPixmap(335 / sf, 120 / sf, image_to_draw)
        elif isinstance(image_to_draw, QSvgRenderer):
            draw_size = QSizeF(image_to_draw.defaultSize())
            draw_size.scale(QSizeF(size), Qt.KeepAspectRatio)
            image_to_draw.render(
                _painter,
                QRectF(335, 120, draw_size.width(), draw_size.height()))
        _painter.end()
        self.preview = False
예제 #5
0
파일: scale.py 프로젝트: vbaggiol/pydm
class QScale(QFrame):
    """
    A bar-shaped indicator for scalar value.
    Configurable features include indicator type (bar/pointer), scale tick
    marks and orientation (horizontal/vertical).

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Scale
    """
    def __init__(self, parent=None):
        super(QScale, self).__init__(parent)
        self._value = 1
        self._lower_limit = -5
        self._upper_limit = 5
        self.position = None  # unit: pixel

        self._bg_color = QColor('darkgray')
        self._bg_size_rate = 0.8  # from 0 to 1

        self._indicator_color = QColor('black')
        self._pointer_width_rate = 0.05
        self._barIndicator = False

        self._num_divisions = 10
        self._show_ticks = True
        self._tick_pen = QPen()
        self._tick_color = QColor('black')
        self._tick_width = 0
        self._tick_size_rate = 0.1  # from 0 to 1
        self._painter = QPainter()

        self._painter_rotation = None
        self._painter_translation_y = None
        self._painter_translation_x = None
        self._painter_scale_x = None
        self._flip_traslation_y = None
        self._flip_scale_y = None

        self._widget_width = self.width()
        self._widget_height = self.height()

        self._orientation = Qt.Horizontal
        self._inverted_appearance = False
        self._flip_scale = False
        self._scale_height = 35
        self._origin_at_zero = False
        self._origin_position = 0

        self.set_position()

    def adjust_transformation(self):
        """
        This method sets parameters for the widget transformations (needed to for
        orientation, flipping and appearance inversion).
        """
        self.setMaximumSize(QWIDGETSIZE_MAX,
                            QWIDGETSIZE_MAX)  # Unset fixed size
        if self._orientation == Qt.Horizontal:
            self._widget_width = self.width()
            self._widget_height = self.height()
            self._painter_translation_y = 0
            self._painter_rotation = 0
            self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
            self.setFixedHeight(self._scale_height)
        elif self._orientation == Qt.Vertical:
            # Invert dimensions for paintEvent()
            self._widget_width = self.height()
            self._widget_height = self.width()
            self._painter_translation_y = self._widget_width
            self._painter_rotation = -90
            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
            self.setFixedWidth(self._scale_height)

        if self._inverted_appearance:
            self._painter_translation_x = self._widget_width
            self._painter_scale_x = -1
        else:
            self._painter_translation_x = 0
            self._painter_scale_x = 1

        if self._flip_scale:
            self._flip_traslation_y = self._widget_height
            self._flip_scale_y = -1
        else:
            self._flip_traslation_y = 0
            self._flip_scale_y = 1

    def set_tick_pen(self):
        """
        Define pen style for drawing scale tick marks.
        """
        self._tick_pen.setColor(self._tick_color)
        self._tick_pen.setWidth(self._tick_width)

    def draw_ticks(self):
        """
        Draw tick marks on the scale.
        """
        if not self._show_ticks:
            return
        self.set_tick_pen()
        self._painter.setPen(self._tick_pen)
        division_size = self._widget_width / self._num_divisions
        tick_y0 = self._widget_height
        tick_yf = (1 - self._tick_size_rate) * self._widget_height
        for i in range(self._num_divisions + 1):
            x = i * division_size
            self._painter.drawLine(x, tick_y0, x, tick_yf)  # x1, y1, x2, y2

    def draw_bar(self):
        """
        Draw a bar as indicator of current value.
        """
        self.set_origin()
        self.set_position()

        if self.position < 0 or self.position > self._widget_width:
            return
        self._painter.setPen(Qt.transparent)
        self._painter.setBrush(self._indicator_color)
        bar_width = self.position - self._origin_position
        bar_height = self._bg_size_rate * self._widget_height
        self._painter.drawRect(self._origin_position, 0, bar_width, bar_height)

    def draw_pointer(self):
        """
        Draw a pointer as indicator of current value.
        """
        self.set_position()
        if self.position < 0 or self.position > self._widget_width:
            return
        self._painter.setPen(Qt.transparent)
        self._painter.setBrush(self._indicator_color)
        pointer_width = self._pointer_width_rate * self._widget_width
        pointer_height = self._bg_size_rate * self._widget_height
        points = [
            QPoint(self.position, 0),
            QPoint(self.position + 0.5 * pointer_width, 0.5 * pointer_height),
            QPoint(self.position, pointer_height),
            QPoint(self.position - 0.5 * pointer_width, 0.5 * pointer_height)
        ]
        self._painter.drawPolygon(QPolygon(points))

    def draw_indicator(self):
        """
        Draw the selected indicator for current value.
        """
        if self._barIndicator:
            self.draw_bar()
        else:
            self.draw_pointer()

    def draw_background(self):
        """
        Draw the background of the scale.
        """
        self._painter.setPen(Qt.transparent)
        self._painter.setBrush(self._bg_color)
        bg_width = self._widget_width
        bg_height = self._bg_size_rate * self._widget_height
        self._painter.drawRect(0, 0, bg_width, bg_height)

    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.

        Parameters
        ----------
        event : QPaintEvent
        """
        self.adjust_transformation()
        self._painter.begin(self)
        self._painter.translate(
            0, self._painter_translation_y)  # Draw vertically if needed
        self._painter.rotate(self._painter_rotation)
        self._painter.translate(self._painter_translation_x,
                                0)  # Invert appearance if needed
        self._painter.scale(self._painter_scale_x, 1)

        self._painter.translate(
            0, self._flip_traslation_y)  # Invert scale if needed
        self._painter.scale(1, self._flip_scale_y)

        self._painter.setRenderHint(QPainter.Antialiasing)

        self.draw_background()
        self.draw_ticks()
        self.draw_indicator()

        self._painter.end()

    def calculate_position_for_value(self, value):
        """
        Calculate the position (pixel) in which the pointer should be drawn for a given value.
        """
        if value is None or value < self._lower_limit or value > self._upper_limit or \
           self._upper_limit - self._lower_limit == 0:
            proportion = -1  # Invalid
        else:
            proportion = (value - self._lower_limit) / (self._upper_limit -
                                                        self._lower_limit)

        position = int(proportion * self._widget_width)
        return position

    def set_origin(self):
        """
        Set the position (pixel) in which the origin should be drawn.
        """
        if self._origin_at_zero:
            self._origin_position = self.calculate_position_for_value(0)
        else:
            self._origin_position = 0

    def set_position(self):
        """
        Set the position (pixel) in which the pointer should be drawn.
        """
        self.position = self.calculate_position_for_value(self._value)

    def update_indicator(self):
        """
        Update the position and the drawing of indicator.
        """
        self.set_position()
        self.repaint()

    def set_value(self, value):
        """
        Set a new current value for the indicator.
        """
        self._value = value
        self.update_indicator()

    def set_upper_limit(self, new_limit):
        """
        Set the scale upper limit.

        Parameters
        ----------
        new_limit : float
            The upper limit of the scale.
        """
        self._upper_limit = new_limit

    def set_lower_limit(self, new_limit):
        """
        Set the scale lower limit.

        Parameters
        ----------
        new_limit : float
            The lower limit of the scale.
        """
        self._lower_limit = new_limit

    def get_show_ticks(self):
        return self._show_ticks

    def set_show_ticks(self, checked):
        if self._show_ticks != bool(checked):
            self._show_ticks = checked
            self.repaint()

    def get_orientation(self):
        return self._orientation

    def set_orientation(self, orientation):
        self._orientation = orientation
        self.adjust_transformation()
        self.repaint()

    def get_flip_scale(self):
        return self._flip_scale

    def set_flip_scale(self, checked):
        self._flip_scale = bool(checked)
        self.adjust_transformation()
        self.repaint()

    def get_inverted_appearance(self):
        return self._inverted_appearance

    def set_inverted_appearance(self, inverted):
        self._inverted_appearance = inverted
        self.adjust_transformation()
        self.repaint()

    def get_bar_indicator(self):
        return self._barIndicator

    def set_bar_indicator(self, checked):
        if self._barIndicator != bool(checked):
            self._barIndicator = checked
            self.repaint()

    def get_background_color(self):
        return self._bg_color

    def set_background_color(self, color):
        self._bg_color = color
        self.repaint()

    def get_indicator_color(self):
        return self._indicator_color

    def set_indicator_color(self, color):
        self._indicator_color = color
        self.repaint()

    def get_tick_color(self):
        return self._tick_color

    def set_tick_color(self, color):
        self._tick_color = color
        self.repaint()

    def get_background_size_rate(self):
        return self._bg_size_rate

    def set_background_size_rate(self, rate):
        if rate >= 0 and rate <= 1 and self._bg_size_rate != rate:
            self._bg_size_rate = rate
            self.repaint()

    def get_tick_size_rate(self):
        return self._tick_size_rate

    def set_tick_size_rate(self, rate):
        if rate >= 0 and rate <= 1 and self._tick_size_rate != rate:
            self._tick_size_rate = rate
            self.repaint()

    def get_num_divisions(self):
        return self._num_divisions

    def set_num_divisions(self, divisions):
        if isinstance(
                divisions,
                int) and divisions > 0 and self._num_divisions != divisions:
            self._num_divisions = divisions
            self.repaint()

    def get_scale_height(self):
        return self._scale_height

    def set_scale_height(self, value):
        self._scale_height = int(value)
        self.adjust_transformation()
        self.repaint()

    def get_origin_at_zero(self):
        return self._origin_at_zero

    def set_origin_at_zero(self, checked):
        if self._origin_at_zero != bool(checked):
            self._origin_at_zero = checked
            self.repaint()
예제 #6
0
파일: symbol.py 프로젝트: vbaggiol/pydm
class PyDMSymbol(QWidget, PyDMWidget):
    """
    PyDMSymbol will render an image (symbol) for each value of a channel.

    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):
        QWidget.__init__(self, parent)
        PyDMWidget.__init__(self, init_channel=init_channel)
        if 'Index' not in PyDMSymbol.RULE_PROPERTIES:
            PyDMSymbol.RULE_PROPERTIES = PyDMWidget.RULE_PROPERTIES.copy()
            PyDMSymbol.RULE_PROPERTIES.update(
                {'Index': ['set_current_key', object]})
        self.app = QApplication.instance()
        self._current_key = 0
        self._state_images_string = ""
        self._state_images = {
        }  # Keyed on state values (ints), values are (filename, qpixmap or qsvgrenderer) tuples.
        self._aspect_ratio_mode = Qt.KeepAspectRatio
        self._sizeHint = self.minimumSizeHint()
        self._painter = QPainter()

    def init_for_designer(self):
        """
        Method called after the constructor to tweak configurations for
        when using the widget with the Qt Designer
        """
        self.value = 0
        self._current_key = 0

    def set_current_key(self, current_key):
        """
        Change the image being displayed for the one given by `current_key`.

        Parameters
        ----------
        current_key : object
            The current_key parameter can be of any type as long as it matches
            the type used as key for the imageFiles dictionary.

        """
        if self._current_key != current_key:
            self._current_key = current_key
            self.update()

    @Property(str)
    def imageFiles(self):
        """
        JSON-formatted dictionary keyed on states (integers), with filenames
        of the image file to display for the state.

        Returns
        -------
        str
        """
        if not self._state_images:
            return self._state_images_string
        return json.dumps({
            str(state): val[0]
            for (state, val) in self._state_images.items()
        })

    @imageFiles.setter
    def imageFiles(self, new_files):
        """
        JSON-formatted dictionary keyed on states (integers), with filenames
        of the image file to display for the state.

        Parameters
        ----------
        new_files : str
        """
        self._state_images_string = str(new_files)
        try:
            new_file_dict = json.loads(self._state_images_string)
        except Exception:
            self._state_images = {}
            return
        self._sizeHint = QSize(0, 0)
        parent_display = self.find_parent_display()
        base_path = os.path.dirname(parent_display.loaded_file())
        for (state, filename) in new_file_dict.items():
            file_path = find_file(filename, base_path=base_path)
            # First, lets try SVG.  We have to try SVG first, otherwise
            # QPixmap will happily load the SVG and turn it into a raster image.
            # Really annoying: We have to try to load the file as SVG,
            # and we expect it will fail often (because many images aren't SVG).
            # Qt prints a warning message to stdout any time SVG loading fails.
            # So we have to temporarily silence Qt warning messages here.
            qInstallMessageHandler(self.qt_message_handler)
            svg = QSvgRenderer()
            svg.repaintNeeded.connect(self.update)
            if svg.load(file_path):
                self._state_images[int(state)] = (filename, svg)
                self._sizeHint = self._sizeHint.expandedTo(svg.defaultSize())
                qInstallMessageHandler(None)
                continue
            qInstallMessageHandler(None)
            # SVG didn't work, lets try QPixmap
            image = QPixmap(file_path)
            if not image.isNull():
                self._state_images[int(state)] = (filename, image)
                self._sizeHint = self._sizeHint.expandedTo(image.size())
                continue
            # If we get this far, the file specified could not be loaded at all.
            logger.error("Could not load image: {}".format(filename))
            self._state_images[int(state)] = (filename, None)

    @Property(Qt.AspectRatioMode)
    def aspectRatioMode(self):
        """
        Which aspect ratio mode to use.

        Returns
        -------
        Qt.AspectRatioMode
        """
        return self._aspect_ratio_mode

    @aspectRatioMode.setter
    def aspectRatioMode(self, new_mode):
        """
        Which aspect ratio mode to use.

        Parameters
        -----------
        new_mode : Qt.AspectRatioMode
        """
        if new_mode != self._aspect_ratio_mode:
            self._aspect_ratio_mode = new_mode
            self.update()

    def connection_changed(self, connected):
        """
        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 : int
            When this value is 0 the channel is disconnected, 1 otherwise.
        """
        super(PyDMSymbol, self).connection_changed(connected)
        self.update()

    def value_changed(self, new_val):
        """
        Callback invoked when the Channel value is changed.

        Parameters
        ----------
        new_val : int
            The new value from the channel.
        """
        super(PyDMSymbol, self).value_changed(new_val)
        self._current_key = new_val
        self.update()

    def sizeHint(self):
        """
        This property holds the recommended size for the widget.

        Returns
        -------
        QSize
        """
        return self._sizeHint

    def minimumSizeHint(self):
        """
        This property holds the recommended minimum size for the widget.

        Returns
        -------
        QSize
        """
        return QSize(
            10, 10
        )  # This is totally arbitrary, I just want *some* visible nonzero size

    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.

        At PyDMSymbol this method handles the alarm painting with parameters
        from the stylesheet and draws the proper image.

        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)
        if self._current_key is None:
            self._painter.end()
            return
        image_to_draw = self._state_images.get(self._current_key,
                                               (None, None))[1]
        if image_to_draw is None:
            self._painter.end()
            return
        if isinstance(image_to_draw, QPixmap):
            w = float(image_to_draw.width())
            h = float(image_to_draw.height())
            if self._aspect_ratio_mode == Qt.IgnoreAspectRatio:
                scale = (event.rect().width() / w, event.rect().height() / h)
            elif self._aspect_ratio_mode == Qt.KeepAspectRatio:
                sf = min(event.rect().width() / w, event.rect().height() / h)
                scale = (sf, sf)
            elif self._aspect_ratio_mode == Qt.KeepAspectRatioByExpanding:
                sf = max(event.rect().width() / w, event.rect().height() / h)
                scale = (sf, sf)
            self._painter.scale(scale[0], scale[1])
            self._painter.drawPixmap(event.rect().x(),
                                     event.rect().y(), image_to_draw)
        elif isinstance(image_to_draw, QSvgRenderer):
            draw_size = QSizeF(image_to_draw.defaultSize())
            draw_size.scale(QSizeF(event.rect().size()),
                            self._aspect_ratio_mode)
            image_to_draw.render(
                self._painter,
                QRectF(0.0, 0.0, draw_size.width(), draw_size.height()))
        self._painter.end()

    def qt_message_handler(self, msg_type, *args):
        # Intentionally suppress all qt messages.  Make sure not to leave this handler installed.
        pass
예제 #7
0
파일: scale.py 프로젝트: slaclab/pydm
class QScale(QFrame):
    """
    A bar-shaped indicator for scalar value.
    Configurable features include indicator type (bar/pointer), scale tick
    marks and orientation (horizontal/vertical).

    Parameters
    ----------
    parent : QWidget
        The parent widget for the Scale
    """
    def __init__(self, parent=None):
        super(QScale, self).__init__(parent)
        self._value = 1
        self._lower_limit = -5
        self._upper_limit = 5
        self.position = None  # unit: pixel

        self._bg_color = QColor('darkgray')
        self._bg_size_rate = 0.8    # from 0 to 1

        self._indicator_color = QColor('black')
        self._pointer_width_rate = 0.05
        self._barIndicator = False

        self._num_divisions = 10
        self._show_ticks = True
        self._tick_pen = QPen()
        self._tick_color = QColor('black')
        self._tick_width = 0
        self._tick_size_rate = 0.1  # from 0 to 1
        self._painter = QPainter()

        self._painter_rotation = None
        self._painter_translation_y = None
        self._painter_translation_x = None
        self._painter_scale_x = None
        self._flip_traslation_y = None
        self._flip_scale_y = None

        self._widget_width = self.width()
        self._widget_height = self.height()

        self._orientation = Qt.Horizontal
        self._inverted_appearance = False
        self._flip_scale = False
        self._scale_height = 35
        self._origin_at_zero = False
        self._origin_position = 0

        self.set_position()

    def adjust_transformation(self):
        """
        This method sets parameters for the widget transformations (needed to for
        orientation, flipping and appearance inversion).
        """
        self.setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)  # Unset fixed size
        if self._orientation == Qt.Horizontal:
            self._widget_width = self.width()
            self._widget_height = self.height()
            self._painter_translation_y = 0
            self._painter_rotation = 0
            self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
            self.setFixedHeight(self._scale_height)
        elif self._orientation == Qt.Vertical:
            # Invert dimensions for paintEvent()
            self._widget_width = self.height()
            self._widget_height = self.width()
            self._painter_translation_y = self._widget_width
            self._painter_rotation = -90
            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
            self.setFixedWidth(self._scale_height)

        if self._inverted_appearance:
            self._painter_translation_x = self._widget_width
            self._painter_scale_x = -1
        else:
            self._painter_translation_x = 0
            self._painter_scale_x = 1

        if self._flip_scale:
            self._flip_traslation_y = self._widget_height
            self._flip_scale_y = -1
        else:
            self._flip_traslation_y = 0
            self._flip_scale_y = 1

    def set_tick_pen(self):
        """
        Define pen style for drawing scale tick marks.
        """
        self._tick_pen.setColor(self._tick_color)
        self._tick_pen.setWidth(self._tick_width)

    def draw_ticks(self):
        """
        Draw tick marks on the scale.
        """
        if not self._show_ticks:
            return
        self.set_tick_pen()
        self._painter.setPen(self._tick_pen)
        division_size = self._widget_width / self._num_divisions
        tick_y0 = self._widget_height
        tick_yf = (1 - self._tick_size_rate)*self._widget_height
        for i in range(self._num_divisions+1):
            x = i*division_size
            self._painter.drawLine(x, tick_y0, x, tick_yf) # x1, y1, x2, y2

    def draw_bar(self):
        """
        Draw a bar as indicator of current value.
        """
        self.set_origin()
        self.set_position()

        if self.position < 0 or self.position > self._widget_width:
            return
        self._painter.setPen(Qt.transparent)
        self._painter.setBrush(self._indicator_color)
        bar_width = self.position - self._origin_position
        bar_height = self._bg_size_rate * self._widget_height
        self._painter.drawRect(self._origin_position, 0, bar_width, bar_height)

    def draw_pointer(self):
        """
        Draw a pointer as indicator of current value.
        """
        self.set_position()
        if self.position < 0 or self.position > self._widget_width:
            return
        self._painter.setPen(Qt.transparent)
        self._painter.setBrush(self._indicator_color)
        pointer_width = self._pointer_width_rate * self._widget_width
        pointer_height = self._bg_size_rate * self._widget_height
        points = [
            QPoint(self.position, 0),
            QPoint(self.position + 0.5*pointer_width, 0.5*pointer_height),
            QPoint(self.position, pointer_height),
            QPoint(self.position - 0.5*pointer_width, 0.5*pointer_height)
        ]
        self._painter.drawPolygon(QPolygon(points))

    def draw_indicator(self):
        """
        Draw the selected indicator for current value.
        """
        if self._barIndicator:
            self.draw_bar()
        else:
            self.draw_pointer()

    def draw_background(self):
        """
        Draw the background of the scale.
        """
        self._painter.setPen(Qt.transparent)
        self._painter.setBrush(self._bg_color)
        bg_width = self._widget_width
        bg_height = self._bg_size_rate * self._widget_height
        self._painter.drawRect(0, 0, bg_width, bg_height)

    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.

        Parameters
        ----------
        event : QPaintEvent
        """
        self.adjust_transformation()
        self._painter.begin(self)
        self._painter.translate(0, self._painter_translation_y) # Draw vertically if needed
        self._painter.rotate(self._painter_rotation)
        self._painter.translate(self._painter_translation_x, 0) # Invert appearance if needed
        self._painter.scale(self._painter_scale_x, 1)

        self._painter.translate(0, self._flip_traslation_y)     # Invert scale if needed
        self._painter.scale(1, self._flip_scale_y)

        self._painter.setRenderHint(QPainter.Antialiasing)

        self.draw_background()
        self.draw_ticks()
        self.draw_indicator()

        self._painter.end()

    def calculate_position_for_value(self, value):
        """
        Calculate the position (pixel) in which the pointer should be drawn for a given value.
        """
        if value < self._lower_limit or value > self._upper_limit or \
           self._upper_limit - self._lower_limit == 0:
            proportion = -1 # Invalid
        else:
            proportion = (value - self._lower_limit) / (self._upper_limit - self._lower_limit)

        position = int(proportion * self._widget_width)
        return position

    def set_origin(self):
        """
        Set the position (pixel) in which the origin should be drawn.
        """
        if self._origin_at_zero:
            self._origin_position = self.calculate_position_for_value(0)
        else:
            self._origin_position = 0

    def set_position(self):
        """
        Set the position (pixel) in which the pointer should be drawn.
        """
        self.position = self.calculate_position_for_value(self._value)

    def update_indicator(self):
        """
        Update the position and the drawing of indicator.
        """
        self.set_position()
        self.repaint()

    def set_value(self, value):
        """
        Set a new current value for the indicator.
        """
        self._value = value
        self.update_indicator()

    def set_upper_limit(self, new_limit):
        """
        Set the scale upper limit.

        Parameters
        ----------
        new_limit : float
            The upper limit of the scale.
        """
        self._upper_limit = new_limit

    def set_lower_limit(self, new_limit):
        """
        Set the scale lower limit.

        Parameters
        ----------
        new_limit : float
            The lower limit of the scale.
        """
        self._lower_limit = new_limit

    def get_show_ticks(self):
        return self._show_ticks

    def set_show_ticks(self, checked):
        if self._show_ticks != bool(checked):
            self._show_ticks = checked
            self.repaint()

    def get_orientation(self):
        return self._orientation

    def set_orientation(self, orientation):
        self._orientation = orientation
        self.adjust_transformation()
        self.repaint()

    def get_flip_scale(self):
        return self._flip_scale

    def set_flip_scale(self, checked):
        self._flip_scale = bool(checked)
        self.adjust_transformation()
        self.repaint()

    def get_inverted_appearance(self):
        return self._inverted_appearance

    def set_inverted_appearance(self, inverted):
        self._inverted_appearance = inverted
        self.adjust_transformation()
        self.repaint()

    def get_bar_indicator(self):
        return self._barIndicator

    def set_bar_indicator(self, checked):
        if self._barIndicator != bool(checked):
            self._barIndicator = checked
            self.repaint()

    def get_background_color(self):
        return self._bg_color

    def set_background_color(self, color):
        self._bg_color = color
        self.repaint()

    def get_indicator_color(self):
        return self._indicator_color

    def set_indicator_color(self, color):
        self._indicator_color = color
        self.repaint()

    def get_tick_color(self):
        return self._tick_color

    def set_tick_color(self, color):
        self._tick_color = color
        self.repaint()

    def get_background_size_rate(self):
        return self._bg_size_rate

    def set_background_size_rate(self, rate):
        if rate >= 0 and rate <=1 and self._bg_size_rate != rate:
            self._bg_size_rate = rate
            self.repaint()

    def get_tick_size_rate(self):
        return self._tick_size_rate

    def set_tick_size_rate(self, rate):
        if rate >= 0 and rate <=1 and self._tick_size_rate != rate:
            self._tick_size_rate = rate
            self.repaint()

    def get_num_divisions(self):
        return self._num_divisions

    def set_num_divisions(self, divisions):
        if isinstance(divisions, int) and divisions > 0 and self._num_divisions != divisions:
            self._num_divisions = divisions
            self.repaint()

    def get_scale_height(self):
        return self._scale_height

    def set_scale_height(self, value):
        self._scale_height = int(value)
        self.adjust_transformation()
        self.repaint()

    def get_origin_at_zero(self):
        return self._origin_at_zero

    def set_origin_at_zero(self, checked):
        if self._origin_at_zero != bool(checked):
            self._origin_at_zero = checked
            self.repaint()
예제 #8
0
파일: symbol.py 프로젝트: slaclab/pydm
class PyDMSymbol(QWidget, PyDMWidget):
    """
    PyDMSymbol will render an image (symbol) for each value of a channel.

    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):
        QWidget.__init__(self, parent)
        PyDMWidget.__init__(self, init_channel=init_channel)
        if 'Index' not in PyDMSymbol.RULE_PROPERTIES:
            PyDMSymbol.RULE_PROPERTIES = PyDMWidget.RULE_PROPERTIES.copy()
            PyDMSymbol.RULE_PROPERTIES.update(
                {'Index': ['set_current_key', object]})
        self.app = QApplication.instance()
        self._current_key = 0
        self._state_images_string = ""
        self._state_images = {}  # Keyed on state values (ints), values are (filename, qpixmap or qsvgrenderer) tuples.
        self._aspect_ratio_mode = Qt.KeepAspectRatio
        self._sizeHint = self.minimumSizeHint()
        self._painter = QPainter()

    def init_for_designer(self):
        """
        Method called after the constructor to tweak configurations for
        when using the widget with the Qt Designer
        """
        self.value = 0
        self._current_key = 0

    def set_current_key(self, current_key):
        """
        Change the image being displayed for the one given by `current_key`.

        Parameters
        ----------
        current_key : object
            The current_key parameter can be of any type as long as it matches
            the type used as key for the imageFiles dictionary.

        """
        if self._current_key != current_key:
            self._current_key = current_key
            self.update()

    @Property(str)
    def imageFiles(self):
        """
        JSON-formatted dictionary keyed on states (integers), with filenames
        of the image file to display for the state.

        Returns
        -------
        str
        """
        if not self._state_images:
            return self._state_images_string
        return json.dumps({str(state): val[0] for (state, val) in self._state_images.items()})

    @imageFiles.setter
    def imageFiles(self, new_files):
        """
        JSON-formatted dictionary keyed on states (integers), with filenames
        of the image file to display for the state.

        Parameters
        ----------
        new_files : str
        """
        self._state_images_string = str(new_files)
        try:
            new_file_dict = json.loads(self._state_images_string)
        except Exception:
            self._state_images = {}
            return
        self._sizeHint = QSize(0, 0)
        for (state, filename) in new_file_dict.items():
            if is_pydm_app():
                try:
                    file_path = self.app.get_path(filename)
                except Exception as e:
                    logger.exception("Couldn't get file with path %s", filename)
                    file_path = filename
            else:
                file_path = filename
            # First, lets try SVG.  We have to try SVG first, otherwise
            # QPixmap will happily load the SVG and turn it into a raster image.
            # Really annoying: We have to try to load the file as SVG,
            # and we expect it will fail often (because many images aren't SVG).
            # Qt prints a warning message to stdout any time SVG loading fails.
            # So we have to temporarily silence Qt warning messages here.
            qInstallMessageHandler(self.qt_message_handler)
            svg = QSvgRenderer()
            svg.repaintNeeded.connect(self.update)
            if svg.load(file_path):
                self._state_images[int(state)] = (filename, svg)
                self._sizeHint = self._sizeHint.expandedTo(svg.defaultSize())
                qInstallMessageHandler(None)
                continue
            qInstallMessageHandler(None)
            # SVG didn't work, lets try QPixmap
            image = QPixmap(file_path)
            if not image.isNull():
                self._state_images[int(state)] = (filename, image)
                self._sizeHint = self._sizeHint.expandedTo(image.size())
                continue
            # If we get this far, the file specified could not be loaded at all.
            logger.error("Could not load image: {}".format(filename))
            self._state_images[int(state)] = (filename, None)

    @Property(Qt.AspectRatioMode)
    def aspectRatioMode(self):
        """
        Which aspect ratio mode to use.

        Returns
        -------
        Qt.AspectRatioMode
        """
        return self._aspect_ratio_mode

    @aspectRatioMode.setter
    def aspectRatioMode(self, new_mode):
        """
        Which aspect ratio mode to use.

        Parameters
        -----------
        new_mode : Qt.AspectRatioMode
        """
        if new_mode != self._aspect_ratio_mode:
            self._aspect_ratio_mode = new_mode
            self.update()

    def connection_changed(self, connected):
        """
        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 : int
            When this value is 0 the channel is disconnected, 1 otherwise.
        """
        super(PyDMSymbol, self).connection_changed(connected)
        self.update()

    def value_changed(self, new_val):
        """
        Callback invoked when the Channel value is changed.

        Parameters
        ----------
        new_val : int
            The new value from the channel.
        """
        super(PyDMSymbol, self).value_changed(new_val)
        self._current_key = new_val
        self.update()

    def sizeHint(self):
        """
        This property holds the recommended size for the widget.

        Returns
        -------
        QSize
        """
        return self._sizeHint

    def minimumSizeHint(self):
        """
        This property holds the recommended minimum size for the widget.

        Returns
        -------
        QSize
        """
        return QSize(10, 10)  # This is totally arbitrary, I just want *some* visible nonzero size

    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.

        At PyDMSymbol this method handles the alarm painting with parameters
        from the stylesheet and draws the proper image.

        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)
        if self._current_key is None:
            self._painter.end()
            return
        image_to_draw = self._state_images.get(self._current_key, (None, None))[1]
        if image_to_draw is None:
            self._painter.end()
            return
        if isinstance(image_to_draw, QPixmap):
            w = float(image_to_draw.width())
            h = float(image_to_draw.height())
            if self._aspect_ratio_mode == Qt.IgnoreAspectRatio:
                scale = (event.rect().width() / w, event.rect().height() / h)
            elif self._aspect_ratio_mode == Qt.KeepAspectRatio:
                sf = min(event.rect().width() / w, event.rect().height() / h)
                scale = (sf, sf)
            elif self._aspect_ratio_mode == Qt.KeepAspectRatioByExpanding:
                sf = max(event.rect().width() / w, event.rect().height() / h)
                scale = (sf, sf)
            self._painter.scale(scale[0], scale[1])
            self._painter.drawPixmap(event.rect().x(), event.rect().y(), image_to_draw)
        elif isinstance(image_to_draw, QSvgRenderer):
            draw_size = QSizeF(image_to_draw.defaultSize())
            draw_size.scale(QSizeF(event.rect().size()), self._aspect_ratio_mode)
            image_to_draw.render(self._painter, QRectF(0.0, 0.0, draw_size.width(), draw_size.height()))
        self._painter.end()

    def qt_message_handler(self, msg_type, *args):
        # Intentionally suppress all qt messages.  Make sure not to leave this handler installed.
        pass
예제 #9
0
class BarIndicatorBase(QWidget):
    """docstring for BarIndicator"""
    def __init__(self, parent=None):
        super(BarIndicatorBase, self).__init__(parent)

        self._value = 100
        self._minimum = 0.0
        self._maximum = 100.0
        self._value_at_100_percent = 100.0
        self._format = '$value %'

        self._text_color = QColor(0, 0, 0)
        self._border_color = Qt.gray
        self._border_radius = 2
        self._border_width = 1

        self._painter = QPainter()

        self._orientation = Qt.Horizontal
        self._bar_width = self.height()
        self._bar_length = self.width()

        self._painter_rotation = None
        self._painter_translation_y = None
        self._painter_translation_x = None
        self._painter_scale_x = None
        self._flip_translation_y = None
        self._flip_scale_y = None

        self._inverted_appearance = False
        self._flip_scale = False
        self._origin_at_zero = False
        self._origin_position = 0

        self.barGradient = [
            '0.0, 0, 255, 0',
            '0.8, 255, 255, 0',
            '1.0, 255, 0, 0',
        ]

    def adjustTransformation(self):
        """This method sets parameters for the widget transformations (needed
        for orientation, flipping and appearance inversion).
        """
        if self._orientation == Qt.Horizontal:
            self._bar_width = self.height()
            self._bar_length = self.width()
            self._painter_translation_y = 0
            self._painter_rotation = 0

        elif self._orientation == Qt.Vertical:
            # Invert dimensions for paintEvent()
            self._bar_width = self.width()
            self._bar_length = self.height()
            self._painter_translation_y = self._bar_length
            self._painter_rotation = -90

        if self._inverted_appearance:
            self._painter_translation_x = self._bar_width
            self._painter_scale_x = -1
        else:
            self._painter_translation_x = 0
            self._painter_scale_x = 1

        if self._flip_scale:
            self._flip_translation_y = self._bar_length
            self._flip_scale_y = -1
        else:
            self._flip_translation_y = 0
            self._flip_scale_y = 1

    def paintEvent(self, event):

        self.adjustTransformation()
        self._painter.begin(self)
        self._painter.translate(
            0, self._painter_translation_y)  # Draw vertically if needed
        self._painter.rotate(self._painter_rotation)
        self._painter.translate(self._painter_translation_x,
                                0)  # Invert appearance if needed
        self._painter.scale(self._painter_scale_x, 1)

        self._painter.translate(
            0, self._flip_translation_y)  # Invert scale if needed
        self._painter.scale(1, self._flip_scale_y)

        self._painter.setRenderHint(QPainter.Antialiasing)

        self.drawBackground()

        if self._border_width > 0:
            self.drawBorder()

        if self._format != '':
            self.drawText()

        self._painter.end()

    def drawBackground(self):

        bw = float(self._border_width)
        br = self._border_radius

        w = self.sliderPositionFromValue(self.minimum, self.maximum,
                                         self._value, self._bar_length)
        h = self._bar_width
        rect = QRectF(bw / 2, bw / 2, w - bw, h - bw)

        p = self._painter

        # draw the load meter value bar
        p.setPen(Qt.transparent)
        p.setBrush(self.gradient)

        p.drawRoundedRect(rect, br, br)

    def drawBorder(self):
        p = self._painter

        bw = float(self._border_width)
        br = self._border_radius

        p.setBrush(Qt.transparent)
        border_pen = QPen()
        border_pen.setWidth(int(bw))
        border_pen.setColor(self._border_color)
        p.setPen(border_pen)

        # deal with orientation
        if self._orientation == Qt.Horizontal:
            rect = QRectF(bw / 2, bw / 2,
                          self.width() - bw,
                          self.height() - bw)
        else:
            # must be Qt.Vertical
            rect = QRectF(bw / 2, bw / 2,
                          self.height() - bw,
                          self.width() - bw)

        p.drawRoundedRect(rect, br, br)

    def drawText(self):
        p = self._painter

        # draw the load percentage text
        p.setPen(self._text_color)
        if self.orientation == Qt.Vertical:
            p.drawText(0, 0, self.height(), self.width(), Qt.AlignCenter,
                       self.text())
        else:
            p.drawText(0, 0, self.width(), self.height(), Qt.AlignCenter,
                       self.text())

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

    def resizeEvent(self, event):
        if self._orientation == Qt.Horizontal:
            self.gradient.setStart(0, 0)
            self.gradient.setFinalStop(self.width(), 0)
        else:
            self.gradient.setStart(0, 0)
            self.gradient.setFinalStop(self.height(), 0)

    def sliderPositionFromValue(self, min, max, val, span, upsideDown=False):
        return span * (val / max - min)

    @Slot(int)
    @Slot(float)
    @Slot(object)
    def setValue(self, val):
        self.value = val

    @Slot(int)
    @Slot(float)
    def setMinimum(self, min):
        self.minimum = min

    @Slot(int)
    @Slot(float)
    def setMaximum(self, max):
        self.maximum = max

    @Property(float)
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        if value >= self.minimum and value <= self.maximum:
            self._value = value
            self.update()

    @Property(float)
    def minimum(self):
        return self._minimum

    @minimum.setter
    def minimum(self, min_val):
        self._minimum = min_val
        self.update()

    @Property(float)
    def maximum(self):
        return self._maximum

    @maximum.setter
    def maximum(self, max_val):
        self._maximum = max_val
        self.update()

    @Property(str)
    def format(self):
        return self._format

    @format.setter
    def format(self, fmt):
        self._format = fmt
        self.update()

    @Property(Qt.Orientation)
    def orientation(self):
        return self._orientation

    @orientation.setter
    def orientation(self, orient):
        if orient == self._orientation:
            return

        self._orientation = orient
        self.adjustTransformation()
        self.update()

    @Property(float)
    def valueAt100Percent(self):
        return self._value_at_100_percent

    @valueAt100Percent.setter
    def valueAt100Percent(self, value_at):
        self._value_at_100_percent = value_at
        self.update()

    def text(self):
        values = {
            'v': self._value,
            'p': int((self._value * 100 / self._value_at_100_percent) + .5)
        }
        try:
            return Template(self._format).substitute(value=values['p'])
        except:
            return self.format

    # ToDo: Make this a QLinearGradient
    @Property('QStringList')
    def barGradient(self):
        return self._gradient_def

    @barGradient.setter
    def barGradient(self, gradient):
        grad = QLinearGradient(0, 0, 0, 0)
        try:
            for stop in gradient:
                pos, r, g, b = stop.split(',')[:4]
                color = QColor(int(r), int(g), int(b))
                grad.setColorAt(float(pos), color)
        except:
            LOG.exception('Invalid gradient.')
            return

        self._gradient_def = gradient
        self.gradient = grad
        self.resizeEvent(None)
        self.update()

    # text color
    @Property(QColor)
    def textColor(self):
        return self._text_color

    @textColor.setter
    def textColor(self, text_color):
        self._text_color = text_color
        self.update()

    # border color
    @Property(QColor)
    def borderColor(self):
        return self._border_color

    @borderColor.setter
    def borderColor(self, border_color):
        self._border_color = border_color
        self.update()

    # border radius
    @Property(int)
    def borderRadius(self):
        return self._border_radius

    @borderRadius.setter
    def borderRadius(self, border_radius):
        self._border_radius = border_radius
        self.update()

    # border width
    @Property(int)
    def borderWidth(self):
        return self._border_width

    @borderWidth.setter
    def borderWidth(self, border_width):
        self._border_width = border_width
        self.update()