|  | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Pixmap Dial, a custom Qt4 widget
# Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the GPL.txt file
# ------------------------------------------------------------------------------------------------------------
# Imports (Global)
from math import floor
from PyQt4.QtCore import Qt, QPointF, QRectF, QTimer, QSize, SLOT
from PyQt4.QtGui import QColor, QConicalGradient, QDial, QFont, QFontMetrics
from PyQt4.QtGui import QLinearGradient, QPainter, QPainterPath, QPen, QPixmap
# ------------------------------------------------------------------------------------------------------------
# Widget Class
class PixmapDial(QDial):
    # enum Orientation
    HORIZONTAL = 0
    VERTICAL   = 1
    # enum CustomPaint
    CUSTOM_PAINT_NULL      = 0
    CUSTOM_PAINT_CARLA_WET = 1
    CUSTOM_PAINT_CARLA_VOL = 2
    CUSTOM_PAINT_CARLA_L   = 3
    CUSTOM_PAINT_CARLA_R   = 4
    HOVER_MIN = 0
    HOVER_MAX = 9
    def __init__(self, parent):
        QDial.__init__(self, parent)
        self.m_pixmap = QPixmap(":/bitmaps/dial_01d.png")
        self.m_pixmapNum   = "01"
        self.m_customPaint = self.CUSTOM_PAINT_NULL
        self.m_hovered   = False
        self.m_hoverStep = self.HOVER_MIN
        if self.m_pixmap.width() > self.m_pixmap.height():
            self.m_orientation = self.HORIZONTAL
        else:
            self.m_orientation = self.VERTICAL
        self.m_label = ""
        self.m_labelPos = QPointF(0.0, 0.0)
        self.m_labelFont = QFont()
        self.m_labelFont.setPointSize(6)
        self.m_labelWidth  = 0
        self.m_labelHeight = 0
        self.m_labelGradient = QLinearGradient(0, 0, 0, 1)
        if self.palette().window().color().lightness() > 100:
            # Light background
            c = self.palette().dark().color()
            self.m_color1 = c
            self.m_color2 = QColor(c.red(), c.green(), c.blue(), 0)
            self.m_colorT = [self.palette().buttonText().color(), self.palette().mid().color()]
        else:
            # Dark background
            self.m_color1 = QColor(0, 0, 0, 255)
            self.m_color2 = QColor(0, 0, 0, 0)
            self.m_colorT = [Qt.white, Qt.darkGray]
        self.updateSizes()
    def getSize(self):
        return self.p_size
    def setCustomPaint(self, paint):
        self.m_customPaint = paint
        self.m_labelPos.setY(self.p_size + self.m_labelHeight/2)
        self.update()
    def setEnabled(self, enabled):
        if self.isEnabled() != enabled:
            self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmapNum, "" if enabled else "d"))
            self.updateSizes()
            self.update()
        QDial.setEnabled(self, enabled)
    def setLabel(self, label):
        self.m_label = label
        self.m_labelWidth  = QFontMetrics(self.m_labelFont).width(label)
        self.m_labelHeight = QFontMetrics(self.m_labelFont).height()
        self.m_labelPos.setX(float(self.p_size)/2 - float(self.m_labelWidth)/2)
        self.m_labelPos.setY(self.p_size + self.m_labelHeight)
        self.m_labelGradient.setColorAt(0.0, self.m_color1)
        self.m_labelGradient.setColorAt(0.6, self.m_color1)
        self.m_labelGradient.setColorAt(1.0, self.m_color2)
        self.m_labelGradient.setStart(0, float(self.p_size)/2)
        self.m_labelGradient.setFinalStop(0, self.p_size + self.m_labelHeight + 5)
        self.m_labelGradient_rect = QRectF(float(self.p_size)/8, float(self.p_size)/2, float(self.p_size)*6/8, self.p_size+self.m_labelHeight+5)
        self.update()
    def setPixmap(self, pixmapId):
        self.m_pixmapNum = "%02i" % pixmapId
        self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmapNum, "" if self.isEnabled() else "d"))
        if self.m_pixmap.width() > self.m_pixmap.height():
            self.m_orientation = self.HORIZONTAL
        else:
            self.m_orientation = self.VERTICAL
        self.updateSizes()
        self.update()
    def minimumSizeHint(self):
        return QSize(self.p_size, self.p_size)
    def sizeHint(self):
        return QSize(self.p_size, self.p_size)
    def updateSizes(self):
        self.p_width  = self.m_pixmap.width()
        self.p_height = self.m_pixmap.height()
        if self.p_width < 1:
            self.p_width = 1
        if self.p_height < 1:
            self.p_height = 1
        if self.m_orientation == self.HORIZONTAL:
            self.p_size  = self.p_height
            self.p_count = self.p_width / self.p_height
        else:
            self.p_size  = self.p_width
            self.p_count = self.p_height / self.p_width
        self.setMinimumSize(self.p_size, self.p_size + self.m_labelHeight + 5)
        self.setMaximumSize(self.p_size, self.p_size + self.m_labelHeight + 5)
    def enterEvent(self, event):
        self.m_hovered = True
        if self.m_hoverStep  == self.HOVER_MIN:
            self.m_hoverStep += 1
        QDial.enterEvent(self, event)
    def leaveEvent(self, event):
        self.m_hovered = False
        if self.m_hoverStep  == self.HOVER_MAX:
            self.m_hoverStep -= 1
        QDial.leaveEvent(self, event)
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)
        if self.m_label:
            if self.m_customPaint == self.CUSTOM_PAINT_NULL:
                painter.setPen(self.m_color2)
                painter.setBrush(self.m_labelGradient)
                painter.drawRect(self.m_labelGradient_rect)
            painter.setFont(self.m_labelFont)
            painter.setPen(self.m_colorT[0 if self.isEnabled() else 1])
            painter.drawText(self.m_labelPos, self.m_label)
        if self.isEnabled():
            current = float(self.value() - self.minimum())
            divider = float(self.maximum() - self.minimum())
            if divider == 0.0:
                return
            value  = current / divider
            target = QRectF(0.0, 0.0, self.p_size, self.p_size)
            per = int((self.p_count - 1) * value)
            if self.m_orientation == self.HORIZONTAL:
                xpos = self.p_size * per
                ypos = 0.0
            else:
                xpos = 0.0
                ypos = self.p_size * per
            source = QRectF(xpos, ypos, self.p_size, self.p_size)
            painter.drawPixmap(target, self.m_pixmap, source)
            # Custom knobs (Dry/Wet and Volume)
            if self.m_customPaint in (self.CUSTOM_PAINT_CARLA_WET, self.CUSTOM_PAINT_CARLA_VOL):
                # knob color
                colorGreen = QColor(0x5D, 0xE7, 0x3D, 191 + self.m_hoverStep*7)
                colorBlue  = QColor(0x3E, 0xB8, 0xBE, 191 + self.m_hoverStep*7)
                # draw small circle
                ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
                ballPath = QPainterPath()
                ballPath.addEllipse(ballRect)
                #painter.drawRect(ballRect)
                tmpValue  = (0.375 + 0.75*value)
                ballValue = tmpValue - floor(tmpValue)
                ballPoint = ballPath.pointAtPercent(ballValue)
                # draw arc
                startAngle = 216*16
                spanAngle  = -252*16*value
                if self.m_customPaint == self.CUSTOM_PAINT_CARLA_WET:
                    painter.setBrush(colorBlue)
                    painter.setPen(QPen(colorBlue, 0))
                    painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
                    gradient = QConicalGradient(15.5, 15.5, -45)
                    gradient.setColorAt(0.0,   colorBlue)
                    gradient.setColorAt(0.125, colorBlue)
                    gradient.setColorAt(0.625, colorGreen)
                    gradient.setColorAt(0.75,  colorGreen)
                    gradient.setColorAt(0.76,  colorGreen)
                    gradient.setColorAt(1.0,   colorGreen)
                    painter.setBrush(gradient)
                    painter.setPen(QPen(gradient, 3))
                else:
                    painter.setBrush(colorBlue)
                    painter.setPen(QPen(colorBlue, 0))
                    painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
                    painter.setBrush(colorBlue)
                    painter.setPen(QPen(colorBlue, 3))
                painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
            # Custom knobs (L and R)
            elif self.m_customPaint in (self.CUSTOM_PAINT_CARLA_L, self.CUSTOM_PAINT_CARLA_R):
                # knob color
                color = QColor(0xAD + self.m_hoverStep*5, 0xD5 + self.m_hoverStep*4, 0x4B + self.m_hoverStep*5)
                # draw small circle
                ballRect = QRectF(7.0, 8.0, 11.0, 12.0)
                ballPath = QPainterPath()
                ballPath.addEllipse(ballRect)
                #painter.drawRect(ballRect)
                tmpValue  = (0.375 + 0.75*value)
                ballValue = tmpValue - floor(tmpValue)
                ballPoint = ballPath.pointAtPercent(ballValue)
                painter.setBrush(color)
                painter.setPen(QPen(color, 0))
                painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))
                # draw arc
                if self.m_customPaint == self.CUSTOM_PAINT_CARLA_L:
                    startAngle = 216*16
                    spanAngle  = -252.0*16*value
                elif self.m_customPaint == self.CUSTOM_PAINT_CARLA_R:
                    startAngle = 324.0*16
                    spanAngle  = 252.0*16*(1.0-value)
                else:
                    return
                painter.setPen(QPen(color, 2))
                painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle)
            if self.HOVER_MIN < self.m_hoverStep < self.HOVER_MAX:
                self.m_hoverStep += 1 if self.m_hovered else -1
                QTimer.singleShot(20, self, SLOT("update()"))
        else:
            target = QRectF(0.0, 0.0, self.p_size, self.p_size)
            painter.drawPixmap(target, self.m_pixmap, target)
        event.accept()
    def resizeEvent(self, event):
        self.updateSizes()
        QDial.resizeEvent(self, event)
 |