#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Common Dial, a custom Qt widget # Copyright (C) 2011-2022 Filipe Coelho # # 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 doc/GPL.txt file. # --------------------------------------------------------------------------------------------------------------------- # Imports (Global) from math import isnan from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter from PyQt5.QtWidgets import QDial # --------------------------------------------------------------------------------------------------------------------- # Widget Class # to be implemented by subclasses #def updateSizes(self): #def paintDial(self, painter): class CommonDial(QDial): # enum CustomPaintMode CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient) CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3) CUSTOM_PAINT_MODE_CARLA_VOL = 2 # color blue (reserved #3) CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4) CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4) CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3) CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3) CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6) CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient # enum Orientation HORIZONTAL = 0 VERTICAL = 1 HOVER_MIN = 0 HOVER_MAX = 9 MODE_DEFAULT = 0 MODE_LINEAR = 1 # signals dragStateChanged = pyqtSignal(bool) realValueChanged = pyqtSignal(float) def __init__(self, parent, index): QDial.__init__(self, parent) self.fDialMode = self.MODE_LINEAR self.fMinimum = 0.0 self.fMaximum = 1.0 self.fRealValue = 0.0 self.fPrecision = 10000 self.fIsInteger = False self.fIsHovered = False self.fIsPressed = False self.fHoverStep = self.HOVER_MIN self.fLastDragPos = None self.fLastDragValue = 0.0 self.fIndex = index self.fLabel = "" self.fLabelPos = QPointF(0.0, 0.0) self.fLabelFont = QFont(self.font()) self.fLabelFont.setPixelSize(8) self.fLabelWidth = 0 self.fLabelHeight = 0 if self.palette().window().color().lightness() > 100: # Light background c = self.palette().dark().color() self.fLabelGradientColor1 = c self.fLabelGradientColor2 = QColor(c.red(), c.green(), c.blue(), 0) self.fLabelGradientColorT = [self.palette().buttonText().color(), self.palette().mid().color()] else: # Dark background self.fLabelGradientColor1 = QColor(0, 0, 0, 255) self.fLabelGradientColor2 = QColor(0, 0, 0, 0) self.fLabelGradientColorT = [Qt.white, Qt.darkGray] self.fLabelGradient = QLinearGradient(0, 0, 0, 1) self.fLabelGradient.setColorAt(0.0, self.fLabelGradientColor1) self.fLabelGradient.setColorAt(0.6, self.fLabelGradientColor1) self.fLabelGradient.setColorAt(1.0, self.fLabelGradientColor2) self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL self.fCustomPaintColor = QColor(0xff, 0xff, 0xff) # Fake internal value, custom precision QDial.setMinimum(self, 0) QDial.setMaximum(self, self.fPrecision) QDial.setValue(self, 0) self.valueChanged.connect(self.slot_valueChanged) def forceWhiteLabelGradientText(self): self.fLabelGradientColor1 = QColor(0, 0, 0, 255) self.fLabelGradientColor2 = QColor(0, 0, 0, 0) self.fLabelGradientColorT = [Qt.white, Qt.darkGray] def setLabelColor(self, enabled, disabled): self.fLabelGradientColor1 = QColor(0, 0, 0, 255) self.fLabelGradientColor2 = QColor(0, 0, 0, 0) self.fLabelGradientColorT = [enabled, disabled] def getIndex(self): return self.fIndex def setIndex(self, index): self.fIndex = index def setPrecision(self, value, isInteger): self.fPrecision = value self.fIsInteger = isInteger QDial.setMaximum(self, int(value)) def setMinimum(self, value): self.fMinimum = value def setMaximum(self, value): self.fMaximum = value def rvalue(self): return self.fRealValue def setValue(self, value, emitSignal=False): if self.fRealValue == value or isnan(value): return if value <= self.fMinimum: qtValue = 0 self.fRealValue = self.fMinimum elif value >= self.fMaximum: qtValue = int(self.fPrecision) self.fRealValue = self.fMaximum else: qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision) self.fRealValue = value # Block change signal, we'll handle it ourselves self.blockSignals(True) QDial.setValue(self, qtValue) self.blockSignals(False) if emitSignal: self.realValueChanged.emit(self.fRealValue) def setCustomPaintMode(self, paintMode): if self.fCustomPaintMode == paintMode: return self.fCustomPaintMode = paintMode self.update() def setCustomPaintColor(self, color): if self.fCustomPaintColor == color: return self.fCustomPaintColor = color self.update() def setLabel(self, label): if self.fLabel == label: return self.fLabel = label self.updateSizes() self.update() @pyqtSlot(int) def slot_valueChanged(self, value): self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum self.realValueChanged.emit(self.fRealValue) def enterEvent(self, event): self.fIsHovered = True if self.fHoverStep == self.HOVER_MIN: self.fHoverStep = self.HOVER_MIN + 1 QDial.enterEvent(self, event) def leaveEvent(self, event): self.fIsHovered = False if self.fHoverStep == self.HOVER_MAX: self.fHoverStep = self.HOVER_MAX - 1 QDial.leaveEvent(self, event) def mousePressEvent(self, event): if self.fDialMode == self.MODE_DEFAULT: QDial.mousePressEvent(self, event) return if event.button() == Qt.LeftButton: self.fIsPressed = True self.fLastDragPos = event.pos() self.fLastDragValue = self.fRealValue self.dragStateChanged.emit(True) def mouseMoveEvent(self, event): if self.fDialMode == self.MODE_DEFAULT: QDial.mouseMoveEvent(self, event) return if not self.fIsPressed: return diff = (self.fMaximum - self.fMinimum) / 4.0 pos = event.pos() dx = diff * float(pos.x() - self.fLastDragPos.x()) / self.width() dy = diff * float(pos.y() - self.fLastDragPos.y()) / self.height() value = self.fLastDragValue + dx - dy if value < self.fMinimum: value = self.fMinimum elif value > self.fMaximum: value = self.fMaximum elif self.fIsInteger: value = float(round(value)) self.setValue(value, True) def mouseReleaseEvent(self, event): if self.fDialMode == self.MODE_DEFAULT: QDial.mouseReleaseEvent(self, event) return if self.fIsPressed: self.fIsPressed = False self.dragStateChanged.emit(False) def paintEvent(self, event): painter = QPainter(self) event.accept() painter.save() painter.setRenderHint(QPainter.Antialiasing, True) if self.fLabel: if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: painter.setPen(self.fLabelGradientColor2) painter.setBrush(self.fLabelGradient) painter.drawRect(self.fLabelGradientRect) painter.setFont(self.fLabelFont) painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1]) painter.drawText(self.fLabelPos, self.fLabel) self.paintDial(painter) painter.restore() def resizeEvent(self, event): QDial.resizeEvent(self, event) self.updateSizes() # ---------------------------------------------------------------------------------------------------------------------