Exemplo n.º 1
0
def innerGlowBackgroundPixmap(color, size, radius=5):
    """ Draws radial gradient pixmap, then uses that to draw
    a rounded-corner gradient rectangle pixmap.

    Args:
        color (QColor): used as outer color (lightness 245 used for inner)
        size (QSize): size of output pixmap
        radius (int): radius of inner glow rounded corners
    """
    key = "InnerGlowBackground " + \
          color.name() + " " + \
          str(radius)

    bg = QPixmapCache.find(key)
    if bg:
        return bg

    # set background colors for gradient
    color = color.toHsl()
    light_color = color.fromHsl(color.hslHue(), color.hslSaturation(), 245)
    dark_color = color

    # initialize radial gradient
    center = QPoint(radius, radius)
    pixRect = QRect(0, 0, radius * 2, radius * 2)
    gradientPixmap = QPixmap(radius * 2, radius * 2)
    gradientPixmap.fill(dark_color)

    # draw radial gradient pixmap
    pixPainter = QPainter(gradientPixmap)
    pixPainter.setPen(Qt.NoPen)
    gradient = QRadialGradient(center, radius - 1)
    gradient.setColorAt(0, light_color)
    gradient.setColorAt(1, dark_color)
    pixPainter.setBrush(gradient)
    pixPainter.drawRect(pixRect)
    pixPainter.end()

    # set tl and br to the gradient's square-shaped rect
    tl = QPoint(0, 0)
    br = QPoint(size.width(), size.height())

    # fragments of radial gradient pixmap to create rounded gradient outline rectangle
    frags = [
        # top-left corner
        QPainter.PixmapFragment.create(
            QPointF(tl.x() + radius / 2, tl.y() + radius / 2),
            QRectF(0, 0, radius, radius)
        ),
        # top-mid 'linear gradient'
        QPainter.PixmapFragment.create(
            QPointF(tl.x() + (br.x() - tl.x()) / 2, tl.y() + radius / 2),
            QRectF(radius, 0, 1, radius),
            scaleX=(br.x() - tl.x() - 2 * radius)
        ),
        # top-right corner
        QPainter.PixmapFragment.create(
            QPointF(br.x() - radius / 2, tl.y() + radius / 2),
            QRectF(radius, 0, radius, radius)
        ),
        # left-mid 'linear gradient'
        QPainter.PixmapFragment.create(
            QPointF(tl.x() + radius / 2, tl.y() + (br.y() - tl.y()) / 2),
            QRectF(0, radius, radius, 1),
            scaleY=(br.y() - tl.y() - 2 * radius)
        ),
        # mid solid
        QPainter.PixmapFragment.create(
            QPointF(tl.x() + (br.x() - tl.x()) / 2, tl.y() + (br.y() - tl.y()) / 2),
            QRectF(radius, radius, 1, 1),
            scaleX=(br.x() - tl.x() - 2 * radius),
            scaleY=(br.y() - tl.y() - 2 * radius)
        ),
        # right-mid 'linear gradient'
        QPainter.PixmapFragment.create(
            QPointF(br.x() - radius / 2, tl.y() + (br.y() - tl.y()) / 2),
            QRectF(radius, radius, radius, 1),
            scaleY=(br.y() - tl.y() - 2 * radius)
        ),
        # bottom-left corner
        QPainter.PixmapFragment.create(
            QPointF(tl.x() + radius / 2, br.y() - radius / 2),
            QRectF(0, radius, radius, radius)
        ),
        # bottom-mid 'linear gradient'
        QPainter.PixmapFragment.create(
            QPointF(tl.x() + (br.x() - tl.x()) / 2, br.y() - radius / 2),
            QRectF(radius, radius, 1, radius),
            scaleX=(br.x() - tl.x() - 2 * radius)
        ),
        # bottom-right corner
        QPainter.PixmapFragment.create(
            QPointF(br.x() - radius / 2, br.y() - radius / 2),
            QRectF(radius, radius, radius, radius)
        ),
    ]

    # draw icon background to pixmap
    outPix = QPixmap(size.width(), size.height())
    outPainter = QPainter(outPix)
    outPainter.setPen(Qt.NoPen)
    outPainter.drawPixmapFragments(frags,
                                   gradientPixmap,
                                   QPainter.PixmapFragmentHints(QPainter.OpaqueHint))
    outPainter.end()

    QPixmapCache.insert(key, outPix)

    return outPix
Exemplo n.º 2
0
class HRangeSlider(QAbstractSlider):
    # emitted when the maximum/minimum of the slider is changed.
    # arguments are the new maximum/minimum values
    rangeChanged = pyqtSignal(float, float)
    # emitted when the slider position moves
    # arguments are the new position values
    sliderMoved = pyqtSignal(float, float)
    # emitted when the handle is pressed with its ID
    sliderPressed = pyqtSignal(int)
    # emitted when a handle is released with its ID
    sliderReleased = pyqtSignal(int)
    # emitted when the slider value changes. Changes with position
    # if tracking is enabled: setTracking/hasTracking()
    valueChanged = pyqtSignal(float, float)
    # emitted when the interval changes (gap between max and min)
    # arguments is the new interval value. Always emitted AFTER
    # valueChanged
    intervalChanged = pyqtSignal(float)

    # not supported
    setInvertedAppearance = setInvertedControls = setOrientation = pageStep = setPageStep\
    = singleStep = setSingleStep = setSliderDown = setSliderPosition = actionTriggered\
    = triggerAction = _INVALID

    # creates a slider with
    # minimum   - minimum value of the slider
    # maximum   - maximum value of the slider
    # steps     - affects the granularity (precision) of the slider
    # tracking  - sets tracking on/off
    def __init__(self, *args, **kwargs):
        self.__scaledMinimum = kwargs.pop('minimum', 0)
        self.__scaledMaximum = kwargs.pop('maximum', 99)
        tracking = kwargs.pop('tracking', True)

        self.__steps = max(kwargs.pop('steps', 1024), 1)
        self.__scale = (self.__scaledMaximum -
                        self.__scaledMinimum) / self.__steps
        self._tickList = kwargs.pop('ticks', None)

        if self._tickList is not None:
            self._tickList = np.unique(self._tickList)

        self._tickImage = QPixmap()

        minimum = kwargs['minimum'] = 0
        maximum = kwargs['maximum'] = self.__steps

        self.__position0 = self.__value0 = kwargs.pop('value0', minimum)
        self.__position1 = self.__value1 = kwargs.pop('value1', maximum)

        self._handleWidth = kwargs.pop('handleWidth', 10)
        self._tickHeight = 4

        kwargs['orientation'] = Qt.Horizontal
        super().__init__(*args, **kwargs)

        self.setTracking(tracking)
        self.setMouseTracking(True)
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)

        # holds bounding rectangles for handle0, handle1, inner handle and groove respectively; last rect is invalid
        self._rects = [QRect(), QRect(), QRect(), QRect()]

        # the index of the handle used as index to _rects; -1 is none
        self._hover = -1
        self._press = -1

        # used to calculate mouse events
        self._handleOffset = QPoint()
        self._lastPos = (maximum, minimum)
        self._lastClick = QPoint()

        #
        self._makeTickMarks()

    # OVERRIDE QABSTRACTSLIDER PROPERTIES
    def invertedAppearance(self):
        return false

    def invertedControls(self):
        return false

    def steps(self):
        return self.__steps

    def valueSteps(self):
        return (int(self.__value0), int(self.__value1))

    def setValueSteps(self, value0, value1):
        if value0 > value1:
            return
        steps0 = value0
        steps1 = value1
        self.setSliderValue(steps0, steps1)
        if self.__position0 != self.__value0 or self.__position1 != self.__value1:
            self.__position0 = self.__value0
            self.__position1 = self.__value1
            self.sliderMoved.emit(self.stepsToValue(self.__position0),
                                  self.stepsToValue(self.__position1))
            self.update()

    # sets a new step value and tries to approximate the old values
    def setSteps(self, steps):
        if steps <= 0:
            return
        oldValue = self.value()
        self.__steps = steps
        self.setValue(oldValue[0], oldValue[1])

    def maximum(self):
        return self.__scaledMaximum

    # sets the maximum value. The minimum value will be set to the new maximum if it is
    # greater (or equal) to the new maximum. Slider positions and values will maintain
    # their proportion of value between maximum and minimum, so the caller should re-set
    # slider values to their values before the range change if so desired. Note that depending
    # on the step size of the slider, it may not be possible to exactly restore the previous value
    def setMaximum(self, newMaximum):
        if self.__scaledMaximum == newMaximum:
            return False
        self.__scaledMaximum = newMaximum
        self.__scaledMinimum = min(self.__scaledMinimum, self.__scaledMaximum)
        self.rangeChanged.emit(self.__scaledMinimum, self.__scaledMaximum)
        val0 = self.stepsToValue(self.__value0)
        val1 = self.stepsToValue(self.__value1)
        self.valueChanged.emit(val0, val1)
        self.intervalChanged.emit(val1 - val0)
        self._makeTickMarks()
        self.update()
        return True

    def minimum(self):
        return self.__scaledMinimum

    # sets the maximum value. The minimum value will be set to the new maximum if it is
    # greater (or equal) to the new maximum. Slider positions and values will maintain
    # their proportion of value between maximum and minimum, so the caller should re-set
    # slider values to their values before the range change if so desired. Note that depending
    # on the step size of the slider, it may not be possible to exactly restore the previous value
    def setMinimum(self, newMinimum):
        if self.__scaledMinimum == newMinimum:
            return False
        self.__scaledMinimum = newMinimum
        self.__scaledMaximum = max(self.__scaledMinimum, self.__scaledMaximum)
        self.rangeChanged.emit(self.__scaledMinimum, self.__scaledMaximum)
        val0 = self.stepsToValue(self.__value0)
        val1 = self.stepsToValue(self.__value1)
        self.valueChanged.emit(val0, val1)
        self.intervalChanged.emit(val1 - val0)
        self._makeTickMarks()
        self.update()
        return True

    def orientation(self):
        return Qt.Horizontal

    def isSliderDown(self):
        return self._press in {0, 1, 2}

    def value(self):
        return (self.stepsToValue(self.__value0),
                self.stepsToValue(self.__value1))

    # Used to (programatically) set the values AND positions on the slider
    # Slider values will be forced within the range [maximum, minimum], so
    # change slider range before values if necessary
    def setValue(self, value0, value1):
        if value0 > value1:
            return
        steps0 = self.valueToSteps(value0)
        steps1 = self.valueToSteps(value1)

        self.setSliderValue(steps0, steps1)
        if self.__position0 != self.__value0 or self.__position1 != self.__value1:
            self.__position0 = self.__value0
            self.__position1 = self.__value1
            self.sliderMoved.emit(self.stepsToValue(self.__position0),
                                  self.stepsToValue(self.__position1))
            self.update()

    def setTickList(self, tickList):
        self._tickList = np.nan_to_num(np.unique(tickList))
        self._makeTickMarks()
        self.update()

    # OVERRIDE QABSTRACTSLIDER FUNCTIONS
    # ...
    def stepsToValue(self, steps):
        return steps / self.__steps * (
            self.__scaledMaximum - self.__scaledMinimum) + self.__scaledMinimum

    def valueToSteps(self, value):
        if self.__scaledMaximum == self.__scaledMinimum:
            return 0
        return round(
            (value - self.__scaledMinimum) /
            (self.__scaledMaximum - self.__scaledMinimum) * self.__steps)

    def _setSliderPosition(self, pos0, pos1):
        oldPos0 = self.__position0
        oldPos1 = self.__position1
        if pos0 is not None:
            self.__position0 = pos0
        if pos1 is not None:
            self.__position1 = pos1
        if self.__position0 > self.__position1:
            if pos1 is None:
                self.__position0 = self.__position1
            if pos0 is None:
                self.__position1 = self.__position0

        if self.hasTracking():
            self.setSliderValue(self.__position0, self.__position1)
        if oldPos0 != self.__position0 or oldPos1 != self.__position1:
            self.sliderMoved.emit(self.stepsToValue(self.__position0),
                                  self.stepsToValue(self.__position1))
        self.update()

    def setSliderValue(self, val0, val1):
        oldVal0 = self.__value0
        oldVal1 = self.__value1
        if val0 is not None:
            self.__value0 = max(val0, 0)
        if val1 is not None:
            self.__value1 = min(val1, self.__steps)
        val0 = self.stepsToValue(self.__value0)
        val1 = self.stepsToValue(self.__value1)
        if oldVal0 != val0 or oldVal1 != val1:
            self.valueChanged.emit(val0, val1)
        if (oldVal1 - oldVal0) != (self.__value1 - self.__value0):
            #print(oldVal1, ',', oldVal0, ',', val1, ',', val0)
            self.intervalChanged.emit(val1 - val0)

    def updateValues(self):
        self.setSliderValue(self.__position0, self.__position1)

    # override
    def sizeHint(self):
        return QSize(100, 21)

    def resizeEvent(self, e):
        self._makeTickMarks()

    # override
    def leaveEvent(self, e):
        oldHoverRect = self._rects[self._hover]
        self._hover = -1
        self.update(oldHoverRect)

    def mouseMoveEvent(self, e):
        #print("pressed: ",self._press,",pos0: ",self.__position0,",pos1: ",self.__position1)
        measureWidth = self.width() - self._handleWidth - self._handleWidth
        if self._press == 0:
            newValue = round((e.pos().x() - self._handleOffset.x()) /
                             measureWidth * self.__steps)
            self._setSliderPosition(max(0, newValue), None)
        if self._press == 1:
            newValue = round(
                (e.pos().x() - self._handleOffset.x() - self._handleWidth) /
                measureWidth * self.__steps)
            self._setSliderPosition(None, min(self.__steps, newValue))
        elif self._press == 2:
            delta = round((e.pos().x() - self._lastClick.x()) / measureWidth *
                          self.__steps)
            if self._lastPos[0] + delta < 0:
                self._setSliderPosition(0, self.__position1 - self.__position0)
            elif self._lastPos[1] + delta > self.__steps:
                self._setSliderPosition(
                    self.__steps - self.__position1 + self.__position0,
                    self.__steps)
            else:
                self._setSliderPosition(self._lastPos[0] + delta,
                                        self._lastPos[1] + delta)
        else:
            oldHover = self._hover
            self._hover = self._testHandles(e.pos())

            if oldHover != self._hover:
                self.update(self._rects[oldHover])
                self.update(self._rects[self._hover])

    def mouseReleaseEvent(self, e):
        e.accept()
        oldPressRect = self._rects[self._press]
        if self._press in {0, 1, 2}:
            self.sliderReleased.emit(self._press)
            if not self.hasTracking():
                self.setSliderValue(self.__position0, self.__position1)
        self._press = -1
        self.update(oldPressRect)

    def mousePressEvent(self, e):
        e.accept()
        self._press = self._testHandles(e.pos())
        if self._press in {0, 1, 2}:
            if e.button() == Qt.RightButton:
                self._press = 2
            self._handleOffset = e.pos() - self._rects[self._press].topLeft()
            self._lastPos = (self.__position0, self.__position1)
            self._lastClick = e.pos()
            self.sliderPressed.emit(self._press)
        self.update(self._rects[self._press])

    # test all rects for intersection with pos and returns the index
    def _testHandles(self, pos):
        for i, rect in enumerate(self._rects):
            if rect.contains(pos):
                return i
        return -1

    def _makeTickMarks(self):
        if self._tickList is None:
            return
        self._tickImage = pixmap = QPixmap(
            self.width() - self._handleWidth - self._handleWidth,
            self._tickHeight)
        pixmap.fill(self.palette().color(QPalette.Active, QPalette.Background))
        painter = QPainter(self._tickImage)
        painter.setPen(QColor(165, 162, 148))

        w = pixmap.width() - 1
        v = self.__steps
        for val in self._tickList:
            if val < self.__scaledMinimum or val > self.__scaledMaximum:
                continue
            step = self.valueToSteps(val)
            x = step * w / v
            painter.drawLine(x, 0, x, self._tickHeight - 1)

    # override
    def paintEvent(self, e):
        #print("hover: ",self._hover,",press: ",self._press)
        painter = QStylePainter(self)

        # prepare the drawing options
        # for drawing slider groove
        opt = QStyleOptionSlider()
        opt.initFrom(self)

        opt.subControls = QStyle.SC_SliderGroove

        opt.maximum = self.__steps
        opt.minimum = 0
        opt.orientation = self.orientation()
        opt.sliderPosition = self.__position0
        opt.sliderValue = self.__value0

        if self._tickList is not None:
            opt.tickPosition = QSlider.TicksBelow
        else:
            opt.tickPosition = QSlider.NoTicks

        handleWidth = self._handleWidth
        measureWidth = self.width() - handleWidth - handleWidth

        painter.drawComplexControl(QStyle.CC_Slider, opt)

        # draw tickmarks
        if self._tickList is not None:
            painter.drawPixmap(self._handleWidth,
                               self.height() - self._tickHeight,
                               self._tickImage)

        # handle colors
        colorPRESS = QColor(204, 204, 204)
        colorHOVER = QColor(23, 23, 23)
        colorNORMAL = QColor(0, 122, 217) if self.isEnabled() else colorPRESS

        handleHeight = self.height(
        ) if self._tickList is None else self.height() - self._tickHeight

        # draw handle 0
        if self._press in {0, 2}:
            color = colorPRESS
        elif self._hover == 0:
            color = colorHOVER
        else:
            color = colorNORMAL
        self._rects[0] = r0 = QRect(
            self.__position0 / self.__steps * measureWidth, 0, handleWidth,
            handleHeight)
        painter.fillRect(self._rects[0], color)

        # draw handle 1
        if self._press in {1, 2}:
            color = colorPRESS
        elif self._hover == 1:
            color = colorHOVER
        else:
            color = colorNORMAL

        self._rects[1] = r1 = QRect(
            self.__position1 / self.__steps * measureWidth + handleWidth, 0,
            handleWidth, handleHeight)
        painter.fillRect(self._rects[1], color)

        # draw inner handle
        if self._press == 2 or not self.isEnabled():
            color = QColor(0, 0, 0, 15)
        elif self._hover == 2:
            color = QColor(0, 61, 108, 63)
        else:
            color = QColor(0, 122, 217, 63)

        self._rects[2] = QRect(r0.left(), r0.top(),
                               r1.right() - r0.left() + 1, handleHeight)

        painter.fillRect(self._rects[2], color)