def fillPixmap(self, widget, pixmap, offset=None): """ Fill a pixmap with the content of a widget In Qt >= 5.0 `QPixmap.fill()` is a nop, in Qt 4.x it is buggy for backgrounds with gradients. Thus `fillPixmap()` offers an alternative implementation. :param QWidget widget: Widget :param QPixmap pixmap: Pixmap to be filled :param QPoint offset: Offset .. seealso:: :py:meth:`QPixmap.fill()` """ if offset is None: offset = QPoint() rect = QRect(offset, pixmap.size()) painter = QPainter(pixmap) painter.translate(-offset) autoFillBrush = widget.palette().brush(widget.backgroundRole()) if not (widget.autoFillBackground() and autoFillBrush.isOpaque()): bg = widget.palette().brush(QPalette.Window) qwtFillRect(widget, painter, rect, bg) if widget.autoFillBackground(): qwtFillRect(widget, painter, rect, autoFillBrush) if widget.testAttribute(Qt.WA_StyledBackground): painter.setClipRegion(QRegion(rect)) opt = QStyleOption() opt.initFrom(widget) widget.style().drawPrimitive(QStyle.PE_Widget, opt, painter, widget)
def paintEvent(self, QPaintEvent): self.updatePosition() painter = QPainter(self) painter.fillRect(self.rect(), Qt.transparent) painter.setRenderHint(QPainter.Antialiasing, True) if self._currentCounter >= self._numberOfLines: self._currentCounter = 0 painter.setPen(Qt.NoPen) for i in range(self._numberOfLines): painter.save() painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength) rotateAngle = float(360 * i) / float(self._numberOfLines) painter.rotate(rotateAngle) painter.translate(self._innerRadius, 0) distance = self.lineCountDistanceFromPrimary( i, self._currentCounter, self._numberOfLines) color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage, self._minimumTrailOpacity, self._color) painter.setBrush(color) painter.drawRoundedRect( QRectF(0, -self._lineWidth / 2, self._lineLength, self._lineWidth), self._roundness, self._roundness, Qt.RelativeSize) painter.restore()
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 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) # 画指针
def drawColorBar(self, painter, colorMap, interval, scaleMap, orientation, rect): """ Draw a color bar into a rectangle :param QPainter painter: Painter :param qwt.color_map.QwtColorMap colorMap: Color map :param qwt.interval.QwtInterval interval: Value range :param qwt.scalemap.QwtScaleMap scaleMap: Scale map :param Qt.Orientation orientation: Orientation :param QRectF rect: Target rectangle """ colorTable = [] if colorMap.format() == QwtColorMap.Indexed: colorTable = colorMap.colorTable(interval) c = QColor() devRect = rect.toAlignedRect() pixmap = QPixmap(devRect.size()) pixmap.fill(Qt.transparent) pmPainter = QPainter(pixmap) pmPainter.translate(-devRect.x(), -devRect.y()) if orientation == Qt.Horizontal: sMap = QwtScaleMap(scaleMap) sMap.setPaintInterval(rect.left(), rect.right()) for x in range(devRect.left(), devRect.right() + 1): value = sMap.invTransform(x) if colorMap.format() == QwtColorMap.RGB: c.setRgba(colorMap.rgb(interval, value)) else: c = colorTable[colorMap.colorIndex(interval, value)] pmPainter.setPen(c) pmPainter.drawLine( QLineF(x, devRect.top(), x, devRect.bottom())) else: sMap = QwtScaleMap(scaleMap) sMap.setPaintInterval(rect.bottom(), rect.top()) for y in range(devRect.top(), devRect.bottom() + 1): value = sMap.invTransform(y) if colorMap.format() == QwtColorMap.RGB: c.setRgba(colorMap.rgb(interval, value)) else: c = colorTable[colorMap.colorIndex(interval, value)] pmPainter.setPen(c) pmPainter.drawLine( QLineF(devRect.left(), y, devRect.right(), y)) pmPainter.end() self.drawPixmap(painter, rect, pixmap)
def qwtBackgroundWidget(w): if w.parentWidget() is None: return w if w.autoFillBackground(): brush = w.palette().brush(w.backgroundRole()) if brush.color().alpha() > 0: return w if w.testAttribute(Qt.WA_StyledBackground): image = QImage(1, 1, QImage.Format_ARGB32) image.fill(Qt.transparent) painter = QPainter(image) painter.translate(-w.rect().center()) qwtDrawStyledBackground(w, painter) painter.end() if qAlpha(image.pixel(0, 0)) != 0: return w return qwtBackgroundWidget(w.parentWidget())
def drawline(self, p1, p2, angle, tip=False): painter = QPainter() painter.begin(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.blue) pen = painter.pen() pen.setWidth(2) painter.setPen(pen) painter.setBrush(Qt.blue) painter.translate(p1) painter.rotate(angle) pt = (p2 - p1) if tip: pt /= (pt.x()**2 + pt.y()**2)**0.5 / 10 # painter.scale(scale, scale) # painter.drawLine(QPoint(0, 0), (p2-p1)*scale) painter.drawLine(QPoint(0, 0), pt) return painter
def paintEvent(self, paint_event): """Paints the box""" painter = QPainter(self) rect = self.contentsRect() tile_count = 3 tile_size = int(rect.width() / tile_count) painter.save() painter.translate(rect.x(), rect.y()) for y in range(tile_count): for x in range(tile_count): color_index = (y * tile_count + x) % 2 tile_rect = QRect(x * tile_size, y * tile_size, tile_size, tile_size) painter.fillRect(tile_rect, self._tile_colors[color_index]) painter.restore() painter.fillRect(rect, self._color) QFrame.paintEvent(self, paint_event)
def paintEvent(self, QPaintEvent): if not self._isSpinning: return self.updatePosition() painter = QPainter(self) painter.fillRect(self.rect(), Qt.transparent) painter.setRenderHint(QPainter.Antialiasing, True) if self._currentCounter >= self._numberOfLines: self._currentCounter = 0 painter.setPen(Qt.NoPen) for i in range(0, self._numberOfLines): painter.save() painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength) rotateAngle = float(360 * i) / float(self._numberOfLines) painter.rotate(rotateAngle) painter.translate(self._innerRadius, 0) distance = self.lineCountDistanceFromPrimary( i, self._currentCounter, self._numberOfLines) color = self.currentLineColor(distance, self._numberOfLines, self._trailFadePercentage, self._minimumTrailOpacity, self._color) # Compute the scaling factor to apply to the size and thickness # of the lines in the trail. if self._trailSizeDecreasing: sf = (self._numberOfLines - distance) / self._numberOfLines else: sf = 1 painter.setBrush(color) rect = QRect(0, round(-self._lineWidth / 2), round(sf * self._lineLength), round(sf * self._lineWidth)) painter.drawRoundedRect(rect, self._roundness, self._roundness, Qt.RelativeSize) painter.restore()
def paintEvent(self, e): cr = self.contentsRect() painter = QPainter(self) painter.setClipRegion(e.region()) # if self.__data.isDown: # qDrawWinButton( # painter, 0, 0, self.width(), self.height(), self.palette(), True # ) painter.save() if self.__data.isDown: shiftSize = buttonShift(self) painter.translate(shiftSize.width(), shiftSize.height()) painter.setClipRect(cr) self.drawContents(painter) if not self.__data.icon.isNull(): iconRect = QRect(cr) iconRect.setX(iconRect.x() + self.margin()) if self.__data.itemMode != QwtLegendData.ReadOnly: iconRect.setX(iconRect.x() + BUTTONFRAME) iconRect.setSize(self.__data.icon.size()) iconRect.moveCenter(QPoint(iconRect.center().x(), cr.center().y())) painter.drawPixmap(iconRect, self.__data.icon) painter.restore()
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()
def graph2icon(g: Graph, width: int, node_mode: bool, show_label: bool, monochrome: bool, *, except_node: Optional[int] = None, engine: str = "", pos: Optional[_Pos] = None) -> QIcon: """Draw a generalized chain graph.""" if engine: pos = engine_picker(g, engine, node_mode) if pos is None: raise ValueError("no engine selected") if not pos: pixmap = QPixmap(width, width) pixmap.fill(Qt.transparent) return QIcon(pixmap) width_bound = -float('inf') for x, y in pos.values(): if abs(x) > width_bound: width_bound = x if abs(y) > width_bound: width_bound = y width_bound *= 2.5 image = QImage(QSize(int(width_bound), int(width_bound)), QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) painter = QPainter(image) painter.translate(image.width() / 2, image.height() / 2) pen = QPen() r = int(width_bound / 50) pen.setWidth(r) painter.setPen(pen) _font.setPixelSize(r * 6) painter.setFont(_font) # Draw edges if node_mode: for l1, l2 in g.edges: if except_node in {l1, l2}: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawLine(QPointF(pos[l1][0], -pos[l1][1]), QPointF(pos[l2][0], -pos[l2][1])) else: color = color_qt('dark-gray') if monochrome else LINK_COLOR color.setAlpha(150) painter.setBrush(QBrush(color)) for link in g.vertices: if link == except_node: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawPolygon(*convex_hull([(pos[n][0], -pos[n][1]) for n, edge in edges_view(g) if link in edge], as_qpoint=True)) # Draw vertices for k, (x, y) in pos.items(): if node_mode: color = color_num(len(list(g.neighbors(k))) - 1) if k == except_node: color.setAlpha(150) else: if monochrome: color = Qt.black elif except_node in dict(edges_view(g))[k]: color = color_qt('green') else: color = color_qt('blue') pen.setColor(color) painter.setPen(pen) painter.setBrush(QBrush(color)) point = QPointF(x, -y) painter.drawEllipse(point, r, r) if show_label: pen.setColor(Qt.darkMagenta) painter.setPen(pen) painter.drawText(point, str(k)) painter.end() return QIcon(QPixmap.fromImage(image).scaledToWidth(width))
class BaseCanvas(QWidget, metaclass=QABCMeta): """The subclass can draw a blank canvas more easier.""" @abstractmethod def __init__(self, parent: QWidget): """Set the parameters for drawing.""" super(BaseCanvas, self).__init__(parent) self.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.setFocusPolicy(Qt.StrongFocus) self.setMouseTracking(True) self.painter = QPainter() # Origin coordinate self.ox = self.width() / 2 self.oy = self.height() / 2 # Canvas zoom rate self.zoom = 1. # Joint size self.joint_size = 5 # Canvas line width self.link_width = 3 self.path_width = 3 # Font size self.font_size = 15 # Show point mark or dimension self.show_ticks = _TickMark.SHOW self.show_point_mark = True self.show_dimension = True # Path track self.path = _PathOption() # Path solving self.ranges: Dict[str, QRectF] = {} self.target_path: Dict[int, Sequence[_Coord]] = {} self.show_target_path = False # Background self.background = QImage() self.background_opacity = 1. self.background_scale = 1. self.background_offset = QPointF(0, 0) # Monochrome mode self.monochrome = False # Grab mode self.__grab_mode = False def switch_grab(self) -> None: """Start grab mode.""" self.__grab_mode = not self.__grab_mode @staticmethod def zoom_factor(width: int, height: int, x_right: float, x_left: float, y_top: float, y_bottom: float) -> float: """Calculate the zoom factor.""" x_diff = x_left - x_right y_diff = y_top - y_bottom x_diff = x_diff if x_diff else 1. y_diff = y_diff if y_diff else 1. if width / x_diff < height / y_diff: return width / x_diff else: return height / y_diff @abstractmethod def paintEvent(self, event: QPaintEvent) -> None: """Using a QPainter under 'self', so just change QPen or QBrush before painting. """ if not self.__grab_mode: self.painter.begin(self) self.painter.fillRect(event.rect(), QBrush(Qt.white)) # Translation self.painter.translate(self.ox, self.oy) # Background if not self.background.isNull(): rect = self.background.rect() self.painter.setOpacity(self.background_opacity) self.painter.drawImage( QRectF( self.background_offset * self.zoom, QSizeF(rect.width(), rect.height()) * self.background_scale * self.zoom), self.background, QRectF(rect)) self.painter.setOpacity(1) # Show frame pen = QPen(Qt.blue) pen.setWidth(1) self.painter.setPen(pen) self.painter.setFont(QFont("Arial", self.font_size)) # Draw origin lines if self.show_ticks not in {_TickMark.SHOW, _TickMark.SHOW_NUM}: return pen.setColor(Qt.gray) self.painter.setPen(pen) x_l = -self.ox x_r = self.width() - self.ox self.painter.drawLine(QPointF(x_l, 0), QPointF(x_r, 0)) y_t = self.height() - self.oy y_b = -self.oy self.painter.drawLine(QPointF(0, y_b), QPointF(0, y_t)) def indexing(v: float) -> int: """Draw tick.""" return int(v / self.zoom - v / self.zoom % 5) # Draw tick for x in range(indexing(x_l), indexing(x_r) + 1, 5): if x == 0: continue is_ten = x % 10 == 0 end = QPointF(x * self.zoom, -10 if is_ten else -5) self.painter.drawLine(QPointF(x, 0) * self.zoom, end) if self.show_ticks == _TickMark.SHOW_NUM and is_ten: self.painter.drawText(end + QPointF(0, 3), f"{x}") for y in range(indexing(y_b), indexing(y_t) + 1, 5): if y == 0: continue is_ten = y % 10 == 0 end = QPointF(10 if is_ten else 5, y * self.zoom) self.painter.drawLine(QPointF(0, y) * self.zoom, end) if self.show_ticks == _TickMark.SHOW_NUM and is_ten: self.painter.drawText(end + QPointF(3, 0), f"{-y}") # Please to call the "end" method when ending paint event. def draw_circle(self, p: QPointF, r: float) -> None: """Draw circle.""" self.painter.drawEllipse(p, r, r) def draw_point(self, i: int, cx: float, cy: float, fixed: bool, color: Optional[Tuple[int, int, int]], mul: int = 1) -> None: """Draw a joint.""" if self.monochrome or color is None: color = Qt.black else: color = QColor(*color) pen = QPen(color) pen.setWidth(2) self.painter.setPen(pen) x = cx * self.zoom y = cy * -self.zoom if fixed: # Draw a triangle below self.painter.drawPolygon( QPointF(x, y), QPointF(x - self.joint_size, y + 2 * self.joint_size), QPointF(x + self.joint_size, y + 2 * self.joint_size)) r = self.joint_size for _ in range(1 if mul < 1 else mul): self.draw_circle(QPointF(x, y), r) r += 5 if not self.show_point_mark: return pen.setColor(Qt.darkGray) pen.setWidth(2) self.painter.setPen(pen) text = f"[Point{i}]" if self.show_dimension: text += f":({cx:.02f}, {cy:.02f})" self.painter.drawText(QPointF(x, y) + QPointF(6, -6), text) def draw_ranges(self) -> None: """Draw rectangle ranges.""" pen = QPen() pen.setWidth(5) for i, (tag, rect) in enumerate(self.ranges.items()): range_color = QColor(color_num(i + 1)) range_color.setAlpha(30) self.painter.setBrush(range_color) range_color.setAlpha(255) pen.setColor(range_color) self.painter.setPen(pen) cx = rect.x() * self.zoom cy = rect.y() * -self.zoom if rect.width(): self.painter.drawRect( QRectF(QPointF(cx, cy), QSizeF(rect.width(), rect.height()) * self.zoom)) else: self.draw_circle(QPointF(cx, cy), 3) range_color.setAlpha(255) pen.setColor(range_color) self.painter.setPen(pen) self.painter.drawText(QPointF(cx, cy) + QPointF(6, -6), tag) self.painter.setBrush(Qt.NoBrush) def draw_target_path(self) -> None: """Draw solving path.""" pen = QPen() pen.setWidth(self.path_width) for i, n in enumerate(sorted(self.target_path)): path = self.target_path[n] if self.monochrome: line, dot = target_path_style(0) else: line, dot = target_path_style(i + 1) pen.setColor(line) self.painter.setPen(pen) if len(path) == 1: x, y = path[0] p = QPointF(x, -y) * self.zoom self.painter.drawText(p + QPointF(6, -6), f"P{n}") pen.setColor(dot) self.painter.setPen(pen) self.draw_circle(p, self.joint_size) else: painter_path = QPainterPath() for j, (x, y) in enumerate(path): p = QPointF(x, -y) * self.zoom self.draw_circle(p, self.joint_size) if j == 0: self.painter.drawText(p + QPointF(6, -6), f"P{n}") painter_path.moveTo(p) else: x2, y2 = path[j - 1] self.__draw_arrow(x, -y, x2, -y2, zoom=True) painter_path.lineTo(p) pen.setColor(line) self.painter.setPen(pen) self.painter.drawPath(painter_path) for x, y in path: pen.setColor(dot) self.painter.setPen(pen) self.draw_circle( QPointF(x, -y) * self.zoom, self.joint_size) self.painter.setBrush(Qt.NoBrush) def __draw_arrow(self, x1: float, y1: float, x2: float, y2: float, *, zoom: bool = False, text: str = '') -> None: """Front point -> Back point""" if zoom: x1 *= self.zoom y1 *= self.zoom x2 *= self.zoom y2 *= self.zoom a = atan2(y2 - y1, x2 - x1) x1 = (x1 + x2) / 2 - 7.5 * cos(a) y1 = (y1 + y2) / 2 - 7.5 * sin(a) first_point = QPointF(x1, y1) self.painter.drawLine( first_point, QPointF(x1 + 15 * cos(a + radians(20)), y1 + 15 * sin(a + radians(20)))) self.painter.drawLine( first_point, QPointF(x1 + 15 * cos(a - radians(20)), y1 + 15 * sin(a - radians(20)))) if not text: return # Font font = self.painter.font() font_copy = QFont(font) font.setBold(True) font.setPointSize(font.pointSize() + 8) self.painter.setFont(font) # Color pen = self.painter.pen() color = pen.color() pen.setColor(color.darker()) self.painter.setPen(pen) self.painter.drawText(first_point, text) pen.setColor(color) self.painter.setPen(pen) self.painter.setFont(font_copy) def draw_curve(self, path: Sequence[_Coord]) -> None: """Draw path as curve.""" if len(set(path)) < 2: return painter_path = QPainterPath() error = False for i, (x, y) in enumerate(path): if isnan(x): error = True self.painter.drawPath(painter_path) painter_path = QPainterPath() else: p = QPointF(x, -y) * self.zoom if i == 0: painter_path.moveTo(p) self.draw_circle(p, 2) continue if error: painter_path.moveTo(p) error = False else: painter_path.lineTo(p) self.painter.drawPath(painter_path) def draw_dot(self, path: Sequence[_Coord]) -> None: """Draw path as dots.""" if len(set(path)) < 2: return for i, (x, y) in enumerate(path): if isnan(x): continue p = QPointF(x, -y) * self.zoom if i == 0: self.draw_circle(p, 2) else: self.painter.drawPoint(p) def solution_polygon( self, func: str, args: Sequence[str], target: str, pos: Sequence[VPoint]) -> Tuple[List[QPointF], QColor]: """Get solution polygon.""" if func == 'PLLP': color = QColor(121, 171, 252) params = [args[0], args[-1]] elif func == 'PLAP': color = QColor(249, 84, 216) params = [args[0]] else: if func == 'PLPP': color = QColor(94, 255, 185) else: # PXY color = QColor(249, 175, 27) params = [args[0]] params.append(target) tmp_list = [] for name in params: try: index = int(name.replace('P', '')) except ValueError: continue else: vpoint = pos[index] tmp_list.append(QPointF(vpoint.cx, -vpoint.cy) * self.zoom) return tmp_list, color def draw_solution(self, func: str, args: Sequence[str], target: str, pos: Sequence[VPoint]) -> None: """Draw the solution triangle.""" points, color = self.solution_polygon(func, args, target, pos) color.setAlpha(150) pen = QPen(color) pen.setWidth(self.joint_size) self.painter.setPen(pen) def draw_arrow(index: int, text: str) -> None: """Draw arrow.""" self.__draw_arrow(points[-1].x(), points[-1].y(), points[index].x(), points[index].y(), text=text) draw_arrow(0, args[1]) if func == 'PLLP': draw_arrow(1, args[2]) color.setAlpha(30) self.painter.setBrush(QBrush(color)) self.painter.drawPolygon(QPolygonF(points)) self.painter.setBrush(Qt.NoBrush) @Slot(int) def set_show_ticks(self, show: int): """Set the appearance of tick mark.""" self.show_ticks = _TickMark(show + 1) self.update() @Slot(bool) def set_monochrome_mode(self, monochrome: bool) -> None: """Set monochrome mode.""" self.monochrome = monochrome self.update()
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()
def create_high_dpi_drop_indicator_pixmap( self, size: QSizeF, area: DockWidgetArea, mode: OverlayMode) -> QPixmap: ''' Create high dpi drop indicator pixmap Parameters ---------- size : QSizeF area : DockWidgetArea mode : OverlayMode Returns ------- value : QPixmap ''' border_color = self.icon_color(IconColor.frame_color) background_color = self.icon_color(IconColor.window_background_color) window = self.public.window() # QT version compatibility (TODO necessary for qtpy?) device_pixel_ratio = (window.devicePixelRatioF() if hasattr(window, 'devicePixelRatioF') else window.devicePixelRatio()) pixmap_size = QSizeF(size * device_pixel_ratio) pm = QPixmap(pixmap_size.toSize()) pm.fill(QColor(0, 0, 0, 0)) p = QPainter(pm) pen = p.pen() shadow_rect = QRectF(pm.rect()) base_rect = QRectF() base_rect.setSize(shadow_rect.size() * 0.7) base_rect.moveCenter(shadow_rect.center()) # Fill shadow_color = self.icon_color(IconColor.shadow_color) if shadow_color.alpha() == 255: shadow_color.setAlpha(64) p.fillRect(shadow_rect, shadow_color) # Drop area rect. p.save() area_rect = QRectF() area_line = QLineF() non_area_rect = QRectF() if area == DockWidgetArea.top: area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width(), base_rect.height()*.5) non_area_rect = QRectF(base_rect.x(), shadow_rect.height()*.5, base_rect.width(), base_rect.height()*.5) area_line = QLineF(area_rect.bottomLeft(), area_rect.bottomRight()) elif area == DockWidgetArea.right: area_rect = QRectF(shadow_rect.width()*.5, base_rect.y(), base_rect.width()*.5, base_rect.height()) non_area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width()*.5, base_rect.height()) area_line = QLineF(area_rect.topLeft(), area_rect.bottomLeft()) elif area == DockWidgetArea.bottom: area_rect = QRectF(base_rect.x(), shadow_rect.height()*.5, base_rect.width(), base_rect.height()*.5) non_area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width(), base_rect.height()*.5) area_line = QLineF(area_rect.topLeft(), area_rect.topRight()) elif area == DockWidgetArea.left: area_rect = QRectF(base_rect.x(), base_rect.y(), base_rect.width()*.5, base_rect.height()) non_area_rect = QRectF(shadow_rect.width()*.5, base_rect.y(), base_rect.width()*.5, base_rect.height()) area_line = QLineF(area_rect.topRight(), area_rect.bottomRight()) baseSize = base_rect.size() if (OverlayMode.container == mode and area != DockWidgetArea.center): base_rect = area_rect p.fillRect(base_rect, background_color) if area_rect.isValid(): pen = p.pen() pen.setColor(border_color) Color = self.icon_color(IconColor.overlay_color) if Color.alpha() == 255: Color.setAlpha(64) p.setBrush(Color) p.setPen(Qt.NoPen) p.drawRect(area_rect) pen = p.pen() pen.setWidth(1) pen.setColor(border_color) pen.setStyle(Qt.DashLine) p.setPen(pen) p.drawLine(area_line) p.restore() p.save() # Draw outer border pen = p.pen() pen.setColor(border_color) pen.setWidth(1) p.setBrush(Qt.NoBrush) p.setPen(pen) p.drawRect(base_rect) # draw window title bar p.setBrush(border_color) frame_rect = QRectF(base_rect.topLeft(), QSizeF(base_rect.width(), baseSize.height()/10)) p.drawRect(frame_rect) p.restore() # Draw arrow for outer container drop indicators if (OverlayMode.container == mode and area != DockWidgetArea.center): arrow_rect = QRectF() arrow_rect.setSize(baseSize) arrow_rect.setWidth(arrow_rect.width()/4.6) arrow_rect.setHeight(arrow_rect.height()/2) arrow_rect.moveCenter(QPointF(0, 0)) arrow = QPolygonF() arrow.append(arrow_rect.topLeft()) arrow.append(QPointF(arrow_rect.right(), arrow_rect.center().y())) arrow.append(arrow_rect.bottomLeft()) p.setPen(Qt.NoPen) p.setBrush(self.icon_color(IconColor.arrow_color)) p.setRenderHint(QPainter.Antialiasing, True) p.translate(non_area_rect.center().x(), non_area_rect.center().y()) if area == DockWidgetArea.top: p.rotate(-90) elif area == DockWidgetArea.right: ... elif area == DockWidgetArea.bottom: p.rotate(90) elif area == DockWidgetArea.left: p.rotate(180) p.drawPolygon(arrow) pm.setDevicePixelRatio(device_pixel_ratio) return pm
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()