Signed-off-by: falkTX <falktx@falktx.com>tags/v2.5.0
@@ -132,6 +132,17 @@ debug: | |||||
# --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
lint: | |||||
pylint \ | |||||
--extension-pkg-whitelist=PyQt5 \ | |||||
--max-line-length=120 \ | |||||
--max-locals=25 \ | |||||
--max-statements=100 \ | |||||
--disable=bare-except,duplicate-code,invalid-name,missing-docstring,too-many-branches,too-many-instance-attributes,too-many-statements \ | |||||
$(wildcard widgets/*.py) | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
i18n_update: $(TSs) | i18n_update: $(TSs) | ||||
i18n_release: $(QMs) | i18n_release: $(QMs) | ||||
@@ -162,9 +162,9 @@ class CanvasPreviewFrame(QFrame): | |||||
if self.fZoomCursors[self._kCursorName] != cursorName: | if self.fZoomCursors[self._kCursorName] != cursorName: | ||||
prefix = ":/cursors/zoom" | prefix = ":/cursors/zoom" | ||||
self.fZoomCursors[self._kCursorName] = cursorName | self.fZoomCursors[self._kCursorName] = cursorName | ||||
self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap("{}_{}.png".format(prefix, cursorName)), 8, 7) | |||||
self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap("{}-in_{}.png".format(prefix, cursorName)), 8, 7) | |||||
self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap("{}-out_{}.png".format(prefix, cursorName)), 8, 7) | |||||
self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap(f"{prefix}_{cursorName}.png"), 8, 7) | |||||
self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap(f"{prefix}-in_{cursorName}.png"), 8, 7) | |||||
self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap(f"{prefix}-out_{cursorName}.png"), 8, 7) | |||||
# ----------------------------------------------------------------------------------------------------------------- | # ----------------------------------------------------------------------------------------------------------------- | ||||
@@ -406,19 +406,3 @@ class CanvasPreviewFrame(QFrame): | |||||
self.fFrameWidth = 1 if self.fUseCustomPaint else self.frameWidth() | self.fFrameWidth = 1 if self.fUseCustomPaint else self.frameWidth() | ||||
# --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
if __name__ == '__main__': | |||||
# pylint: disable=unused-import | |||||
# pylint: disable=ungrouped-imports | |||||
import sys | |||||
import resources_rc | |||||
from PyQt5.QtWidgets import QApplication | |||||
# pylint: enable=unused-import | |||||
# pylint: enable=ungrouped-imports | |||||
app = QApplication(sys.argv) | |||||
gui = CanvasPreviewFrame(None) | |||||
gui.show() | |||||
sys.exit(app.exec_()) |
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Collapsible Box, a custom Qt widget | # Collapsible Box, a custom Qt widget | ||||
# Copyright (C) 2019 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2019-2022 Filipe Coelho <falktx@falktx.com> | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License as | # modify it under the terms of the GNU General Public License as | ||||
@@ -20,7 +20,6 @@ | |||||
# Imports (Global) | # Imports (Global) | ||||
from PyQt5.QtCore import pyqtSlot, Qt | from PyQt5.QtCore import pyqtSlot, Qt | ||||
from PyQt5.QtGui import QCursor, QFont | |||||
from PyQt5.QtWidgets import QFrame, QSizePolicy, QToolButton, QVBoxLayout, QWidget | from PyQt5.QtWidgets import QFrame, QSizePolicy, QToolButton, QVBoxLayout, QWidget | ||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
@@ -75,3 +74,5 @@ class CollapsibleBox(QFrame): | |||||
def getContentLayout(self): | def getContentLayout(self): | ||||
return self.content_layout | return self.content_layout | ||||
# ------------------------------------------------------------------------------------------------------------ |
@@ -0,0 +1,278 @@ | |||||
#!/usr/bin/env python3 | |||||
# -*- coding: utf-8 -*- | |||||
# Common Dial, a custom Qt widget | |||||
# Copyright (C) 2011-2022 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 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() | |||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Digital Peak Meter, a custom Qt widget | # Digital Peak Meter, a custom Qt widget | ||||
# Copyright (C) 2011-2019 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com> | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License as | # modify it under the terms of the GNU General Public License as | ||||
@@ -16,16 +16,16 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from math import sqrt | from math import sqrt | ||||
from PyQt5.QtCore import qCritical, Qt, QTimer, QSize, QLineF, QRectF | |||||
from PyQt5.QtCore import qCritical, Qt, QSize, QLineF, QRectF | |||||
from PyQt5.QtGui import QColor, QLinearGradient, QPainter, QPen, QPixmap | from PyQt5.QtGui import QColor, QLinearGradient, QPainter, QPen, QPixmap | ||||
from PyQt5.QtWidgets import QWidget | from PyQt5.QtWidgets import QWidget | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Widget Class | # Widget Class | ||||
class DigitalPeakMeter(QWidget): | class DigitalPeakMeter(QWidget): | ||||
@@ -43,7 +43,7 @@ class DigitalPeakMeter(QWidget): | |||||
STYLE_RNCBC = 3 | STYLE_RNCBC = 3 | ||||
STYLE_CALF = 4 | STYLE_CALF = 4 | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def __init__(self, parent): | def __init__(self, parent): | ||||
QWidget.__init__(self, parent) | QWidget.__init__(self, parent) | ||||
@@ -71,7 +71,7 @@ class DigitalPeakMeter(QWidget): | |||||
self.updateGrandient() | self.updateGrandient() | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def channelCount(self): | def channelCount(self): | ||||
return self.fChannelCount | return self.fChannelCount | ||||
@@ -81,13 +81,14 @@ class DigitalPeakMeter(QWidget): | |||||
return | return | ||||
if count < 0: | if count < 0: | ||||
return qCritical("DigitalPeakMeter::setChannelCount(%i) - channel count must be a positive integer or zero" % count) | |||||
qCritical(f"DigitalPeakMeter::setChannelCount({count}) - channel count must be a positive integer or zero") | |||||
return | |||||
self.fChannelCount = count | self.fChannelCount = count | ||||
self.fChannelData = [] | self.fChannelData = [] | ||||
self.fLastChannelData = [] | self.fLastChannelData = [] | ||||
for x in range(count): | |||||
for _ in range(count): | |||||
self.fChannelData.append(0.0) | self.fChannelData.append(0.0) | ||||
self.fLastChannelData.append(0.0) | self.fLastChannelData.append(0.0) | ||||
@@ -98,7 +99,7 @@ class DigitalPeakMeter(QWidget): | |||||
self.setMinimumSize(0, 0) | self.setMinimumSize(0, 0) | ||||
self.setMaximumSize(9999, 9999) | self.setMaximumSize(9999, 9999) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def meterColor(self): | def meterColor(self): | ||||
return self.fMeterColor | return self.fMeterColor | ||||
@@ -108,7 +109,8 @@ class DigitalPeakMeter(QWidget): | |||||
return | return | ||||
if color not in (self.COLOR_GREEN, self.COLOR_BLUE): | if color not in (self.COLOR_GREEN, self.COLOR_BLUE): | ||||
return qCritical("DigitalPeakMeter::setMeterColor(%i) - invalid color" % color) | |||||
qCritical(f"DigitalPeakMeter::setMeterColor({color}) - invalid color") | |||||
return | |||||
if color == self.COLOR_GREEN: | if color == self.COLOR_GREEN: | ||||
self.fMeterColorBase = QColor(93, 231, 61) | self.fMeterColorBase = QColor(93, 231, 61) | ||||
@@ -121,7 +123,7 @@ class DigitalPeakMeter(QWidget): | |||||
self.updateGrandient() | self.updateGrandient() | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def meterLinesEnabled(self): | def meterLinesEnabled(self): | ||||
return self.fMeterLinesEnabled | return self.fMeterLinesEnabled | ||||
@@ -132,7 +134,7 @@ class DigitalPeakMeter(QWidget): | |||||
self.fMeterLinesEnabled = yesNo | self.fMeterLinesEnabled = yesNo | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def meterOrientation(self): | def meterOrientation(self): | ||||
return self.fMeterOrientation | return self.fMeterOrientation | ||||
@@ -142,13 +144,14 @@ class DigitalPeakMeter(QWidget): | |||||
return | return | ||||
if orientation not in (self.HORIZONTAL, self.VERTICAL): | if orientation not in (self.HORIZONTAL, self.VERTICAL): | ||||
return qCritical("DigitalPeakMeter::setMeterOrientation(%i) - invalid orientation" % orientation) | |||||
qCritical(f"DigitalPeakMeter::setMeterOrientation({orientation}) - invalid orientation") | |||||
return | |||||
self.fMeterOrientation = orientation | self.fMeterOrientation = orientation | ||||
self.updateGrandient() | self.updateGrandient() | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def meterStyle(self): | def meterStyle(self): | ||||
return self.fMeterStyle | return self.fMeterStyle | ||||
@@ -158,7 +161,8 @@ class DigitalPeakMeter(QWidget): | |||||
return | return | ||||
if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF): | if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF): | ||||
return qCritical("DigitalPeakMeter::setMeterStyle(%i) - invalid style" % style) | |||||
qCritical(f"DigitalPeakMeter::setMeterStyle({style}) - invalid style") | |||||
return | |||||
if style == self.STYLE_DEFAULT: | if style == self.STYLE_DEFAULT: | ||||
self.fMeterBackground = QColor("#070707") | self.fMeterBackground = QColor("#070707") | ||||
@@ -182,7 +186,7 @@ class DigitalPeakMeter(QWidget): | |||||
self.updateGrandient() | self.updateGrandient() | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def smoothMultiplier(self): | def smoothMultiplier(self): | ||||
return self.fSmoothMultiplier | return self.fSmoothMultiplier | ||||
@@ -192,28 +196,37 @@ class DigitalPeakMeter(QWidget): | |||||
return | return | ||||
if not isinstance(value, int): | if not isinstance(value, int): | ||||
return qCritical("DigitalPeakMeter::setSmoothMultiplier() - value must be an integer") | |||||
qCritical("DigitalPeakMeter::setSmoothMultiplier() - value must be an integer") | |||||
return | |||||
if value < 0: | if value < 0: | ||||
return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be >= 0" % value) | |||||
qCritical(f"DigitalPeakMeter::setSmoothMultiplier({value}) - value must be >= 0") | |||||
return | |||||
if value > 5: | if value > 5: | ||||
return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be < 5" % value) | |||||
qCritical(f"DigitalPeakMeter::setSmoothMultiplier({value}) - value must be < 5") | |||||
return | |||||
self.fSmoothMultiplier = value | self.fSmoothMultiplier = value | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def displayMeter(self, meter, level, forced = False): | def displayMeter(self, meter, level, forced = False): | ||||
if not isinstance(meter, int): | if not isinstance(meter, int): | ||||
return qCritical("DigitalPeakMeter::displayMeter(,) - meter value must be an integer") | |||||
qCritical("DigitalPeakMeter::displayMeter(,) - meter value must be an integer") | |||||
return | |||||
if not isinstance(level, float): | if not isinstance(level, float): | ||||
return qCritical("DigitalPeakMeter::displayMeter(%i,) - level value must be a float" % (meter,)) | |||||
qCritical(f"DigitalPeakMeter::displayMeter({meter},) - level value must be a float") | |||||
return | |||||
if meter <= 0 or meter > self.fChannelCount: | if meter <= 0 or meter > self.fChannelCount: | ||||
return qCritical("DigitalPeakMeter::displayMeter(%i, %f) - invalid meter number" % (meter, level)) | |||||
qCritical(f"DigitalPeakMeter::displayMeter({meter}, {level}) - invalid meter number") | |||||
return | |||||
i = meter - 1 | i = meter - 1 | ||||
if self.fSmoothMultiplier > 0 and not forced: | if self.fSmoothMultiplier > 0 and not forced: | ||||
level = (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) / float(self.fSmoothMultiplier + 1) | |||||
level = ( | |||||
(self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) | |||||
/ float(self.fSmoothMultiplier + 1) | |||||
) | |||||
if level < 0.001: | if level < 0.001: | ||||
level = 0.0 | level = 0.0 | ||||
@@ -226,7 +239,7 @@ class DigitalPeakMeter(QWidget): | |||||
self.fLastChannelData[i] = level | self.fLastChannelData[i] = level | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def updateGrandient(self): | def updateGrandient(self): | ||||
self.fMeterGradient = QLinearGradient(0, 0, 1, 1) | self.fMeterGradient = QLinearGradient(0, 0, 1, 1) | ||||
@@ -287,15 +300,15 @@ class DigitalPeakMeter(QWidget): | |||||
elif self.fMeterOrientation == self.VERTICAL: | elif self.fMeterOrientation == self.VERTICAL: | ||||
self.fMeterGradient.setFinalStop(0, self.height()) | self.fMeterGradient.setFinalStop(0, self.height()) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def minimumSizeHint(self): | def minimumSizeHint(self): | ||||
return QSize(10, 10) | |||||
return QSize(20, 10) if self.fMeterOrientation == self.HORIZONTAL else QSize(10, 20) | |||||
def sizeHint(self): | def sizeHint(self): | ||||
return QSize(self.width(), self.height()) | return QSize(self.width(), self.height()) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def drawCalf(self, event): | def drawCalf(self, event): | ||||
painter = QPainter(self) | painter = QPainter(self) | ||||
@@ -323,7 +336,8 @@ class DigitalPeakMeter(QWidget): | |||||
def paintEvent(self, event): | def paintEvent(self, event): | ||||
if self.fMeterStyle == self.STYLE_CALF: | if self.fMeterStyle == self.STYLE_CALF: | ||||
return self.drawCalf(event) | |||||
self.drawCalf(event) | |||||
return | |||||
painter = QPainter(self) | painter = QPainter(self) | ||||
event.accept() | event.accept() | ||||
@@ -440,30 +454,10 @@ class DigitalPeakMeter(QWidget): | |||||
painter.setPen(QColor(110, 15, 15, 100)) | painter.setPen(QColor(110, 15, 15, 100)) | ||||
painter.drawLine(QLineF(2, lsmall - (lsmall * 0.96), lfull-2.0, lsmall - (lsmall * 0.96))) | painter.drawLine(QLineF(2, lsmall - (lsmall * 0.96), lfull-2.0, lsmall - (lsmall * 0.96))) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def resizeEvent(self, event): | def resizeEvent(self, event): | ||||
QWidget.resizeEvent(self, event) | QWidget.resizeEvent(self, event) | ||||
self.updateGrandientFinalStop() | self.updateGrandientFinalStop() | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Main Testing | |||||
if __name__ == '__main__': | |||||
import sys | |||||
import resources_rc | |||||
from PyQt5.QtWidgets import QApplication | |||||
app = QApplication(sys.argv) | |||||
gui = DigitalPeakMeter(None) | |||||
gui.setChannelCount(2) | |||||
#gui.setMeterOrientation(DigitalPeakMeter.HORIZONTAL) | |||||
gui.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) | |||||
gui.displayMeter(1, 0.5) | |||||
gui.displayMeter(2, 0.8) | |||||
gui.show() | |||||
sys.exit(app.exec_()) | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Middle-click draggable QGraphicsView | # Middle-click draggable QGraphicsView | ||||
# Copyright (C) 2016-2020 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2016-2022 Filipe Coelho <falktx@falktx.com> | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License as | # modify it under the terms of the GNU General Public License as | ||||
@@ -16,19 +16,20 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from PyQt5.QtCore import Qt, QEvent, QTimer | |||||
import os | |||||
from PyQt5.QtCore import Qt, QEvent | |||||
from PyQt5.QtGui import QCursor, QMouseEvent | from PyQt5.QtGui import QCursor, QMouseEvent | ||||
from PyQt5.QtWidgets import QGraphicsView | |||||
from PyQt5.QtWidgets import QGraphicsView, QMessageBox | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Custom Stuff) | # Imports (Custom Stuff) | ||||
from carla_shared import * | |||||
from carla_shared import MACOS, CustomMessageBox, gCarla | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Widget Class | # Widget Class | ||||
class DraggableGraphicsView(QGraphicsView): | class DraggableGraphicsView(QGraphicsView): | ||||
@@ -49,7 +50,7 @@ class DraggableGraphicsView(QGraphicsView): | |||||
self.setAcceptDrops(True) | self.setAcceptDrops(True) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def isDragUrlValid(self, filename): | def isDragUrlValid(self, filename): | ||||
lfilename = filename.lower() | lfilename = filename.lower() | ||||
@@ -59,7 +60,7 @@ class DraggableGraphicsView(QGraphicsView): | |||||
#return True | #return True | ||||
if MACOS and lfilename.endswith(".vst"): | if MACOS and lfilename.endswith(".vst"): | ||||
return True | return True | ||||
elif lfilename.endswith(".vst3") and ".vst3" in self.fSupportedExtensions: | |||||
if lfilename.endswith(".vst3") and ".vst3" in self.fSupportedExtensions: | |||||
return True | return True | ||||
elif os.path.isfile(filename): | elif os.path.isfile(filename): | ||||
@@ -68,7 +69,7 @@ class DraggableGraphicsView(QGraphicsView): | |||||
return False | return False | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def dragEnterEvent(self, event): | def dragEnterEvent(self, event): | ||||
urls = event.mimeData().urls() | urls = event.mimeData().urls() | ||||
@@ -93,7 +94,7 @@ class DraggableGraphicsView(QGraphicsView): | |||||
self.fWasLastDragValid = False | self.fWasLastDragValid = False | ||||
QGraphicsView.dragLeaveEvent(self, event) | QGraphicsView.dragLeaveEvent(self, event) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def dropEvent(self, event): | def dropEvent(self, event): | ||||
event.acceptProposedAction() | event.acceptProposedAction() | ||||
@@ -111,7 +112,7 @@ class DraggableGraphicsView(QGraphicsView): | |||||
self.tr("Failed to load file"), | self.tr("Failed to load file"), | ||||
gCarla.gui.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) | gCarla.gui.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# ----------------------------------------------------------------------------------------------------------------- | |||||
def mousePressEvent(self, event): | def mousePressEvent(self, event): | ||||
if event.button() == self.fMiddleButton and not (event.modifiers() & Qt.ControlModifier): | if event.button() == self.fMiddleButton and not (event.modifiers() & Qt.ControlModifier): | ||||
@@ -151,4 +152,4 @@ class DraggableGraphicsView(QGraphicsView): | |||||
return | return | ||||
QGraphicsView.wheelEvent(self, event) | QGraphicsView.wheelEvent(self, event) | ||||
# -------------------------------------------------------------------------------------------------------- | |||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -16,7 +16,7 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from PyQt5.QtCore import QRectF | from PyQt5.QtCore import QRectF | ||||
@@ -24,7 +24,7 @@ from PyQt5.QtGui import QPainter, QPixmap | |||||
from PyQt5.QtSvg import QSvgWidget | from PyQt5.QtSvg import QSvgWidget | ||||
from PyQt5.QtWidgets import QPushButton | from PyQt5.QtWidgets import QPushButton | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Widget Class | # Widget Class | ||||
class LEDButton(QPushButton): | class LEDButton(QPushButton): | ||||
@@ -61,10 +61,10 @@ class LEDButton(QPushButton): | |||||
self.fLastColor = self.UNSET | self.fLastColor = self.UNSET | ||||
if self._loadImageNowIfNeeded(): | if self._loadImageNowIfNeeded(): | ||||
if isinstance(self.fImage, QPixmap): | |||||
size = self.fImage.width() | |||||
else: | |||||
size = self.fImage.sizeHint().width() | |||||
#if isinstance(self.fImage, QPixmap): | |||||
#size = self.fImage.width() | |||||
#else: | |||||
#size = self.fImage.sizeHint().width() | |||||
self.fRect = QRectF(self.fImage.rect()) | self.fRect = QRectF(self.fImage.rect()) | ||||
self.setFixedSize(self.fImage.size()) | self.setFixedSize(self.fImage.size()) | ||||
@@ -87,7 +87,7 @@ class LEDButton(QPushButton): | |||||
def _loadImageNowIfNeeded(self): | def _loadImageNowIfNeeded(self): | ||||
if self.isChecked(): | if self.isChecked(): | ||||
if self.fLastColor == self.fColor: | if self.fLastColor == self.fColor: | ||||
return | |||||
return False | |||||
if self.fColor == self.OFF: | if self.fColor == self.OFF: | ||||
img = ":/scalable/led_off.svg" | img = ":/scalable/led_off.svg" | ||||
elif self.fColor == self.BLUE: | elif self.fColor == self.BLUE: | ||||
@@ -122,3 +122,5 @@ class LEDButton(QPushButton): | |||||
self.update() | self.update() | ||||
return True | return True | ||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -76,7 +76,9 @@ class CustomInputDialog(QDialog): | |||||
text = "<table>" | text = "<table>" | ||||
for scalePoint in scalePoints: | for scalePoint in scalePoints: | ||||
valuestr = ("%i" if decimals == 0 else "%f") % scalePoint['value'] | valuestr = ("%i" if decimals == 0 else "%f") % scalePoint['value'] | ||||
text += "<tr><td align='right'>%s</td><td align='left'> - %s</td></tr>" % (valuestr, scalePoint['label']) | |||||
text += "<tr>" | |||||
text += f"<td align='right'>{valuestr}</td><td align='left'> - {scalePoint['label']}</td>" | |||||
text += "</tr>" | |||||
text += "</table>" | text += "</table>" | ||||
self.ui.textBrowser.setText(text) | self.ui.textBrowser.setText(text) | ||||
self.resize(200, 300) | self.resize(200, 300) | ||||
@@ -341,10 +343,8 @@ class ParamSpinBox(QAbstractSpinBox): | |||||
else: | else: | ||||
self.fStep = value | self.fStep = value | ||||
if self.fStepSmall > value: | |||||
self.fStepSmall = value | |||||
if self.fStepLarge < value: | |||||
self.fStepLarge = value | |||||
self.fStepSmall = min(self.fStepSmall, value) | |||||
self.fStepLarge = max(self.fStepLarge, value) | |||||
self.fBar.fIsInteger = bool(self.fStepSmall == 1.0) | self.fBar.fIsInteger = bool(self.fStepSmall == 1.0) | ||||
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# A piano roll viewer/editor | # A piano roll viewer/editor | ||||
# Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2014-2015 Perry Nguyen | # Copyright (C) 2014-2015 Perry Nguyen | ||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
@@ -21,15 +21,15 @@ | |||||
# Imports (Global) | # Imports (Global) | ||||
from PyQt5.QtCore import Qt, QRectF, QPointF, pyqtSignal | from PyQt5.QtCore import Qt, QRectF, QPointF, pyqtSignal | ||||
from PyQt5.QtGui import QColor, QCursor, QFont, QPen, QPainter, QTransform | |||||
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsOpacityEffect, QGraphicsRectItem, QGraphicsSimpleTextItem | |||||
from PyQt5.QtGui import QColor, QCursor, QFont, QPen, QPainter | |||||
from PyQt5.QtWidgets import QGraphicsItem, QGraphicsLineItem, QGraphicsRectItem, QGraphicsSimpleTextItem | |||||
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView | from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView | ||||
from PyQt5.QtWidgets import QApplication, QComboBox, QHBoxLayout, QLabel, QStyle, QVBoxLayout, QWidget | |||||
from PyQt5.QtWidgets import QApplication, QStyle, QWidget | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
# Imports (Custom) | # Imports (Custom) | ||||
from carla_shared import * | |||||
#from carla_shared import * | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
# MIDI definitions, copied from CarlaMIDI.h | # MIDI definitions, copied from CarlaMIDI.h | ||||
@@ -57,17 +57,26 @@ def MIDI_IS_SYSTEM_MESSAGE(status): return status >= MIDI_STATUS_BIT and s | |||||
def MIDI_IS_OSC_MESSAGE(status): return status == '/' or status == '#' | def MIDI_IS_OSC_MESSAGE(status): return status == '/' or status == '#' | ||||
# MIDI Channel message type | # MIDI Channel message type | ||||
def MIDI_IS_STATUS_NOTE_OFF(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_OFF | |||||
def MIDI_IS_STATUS_NOTE_ON(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_ON | |||||
def MIDI_IS_STATUS_POLYPHONIC_AFTERTOUCH(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_POLYPHONIC_AFTERTOUCH | |||||
def MIDI_IS_STATUS_CONTROL_CHANGE(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CONTROL_CHANGE | |||||
def MIDI_IS_STATUS_PROGRAM_CHANGE(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PROGRAM_CHANGE | |||||
def MIDI_IS_STATUS_CHANNEL_PRESSURE(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CHANNEL_PRESSURE | |||||
def MIDI_IS_STATUS_PITCH_WHEEL_CONTROL(status): return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PITCH_WHEEL_CONTROL | |||||
def MIDI_IS_STATUS_NOTE_OFF(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_OFF | |||||
def MIDI_IS_STATUS_NOTE_ON(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_NOTE_ON | |||||
def MIDI_IS_STATUS_POLYPHONIC_AFTERTOUCH(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_POLYPHONIC_AFTERTOUCH | |||||
def MIDI_IS_STATUS_CONTROL_CHANGE(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CONTROL_CHANGE | |||||
def MIDI_IS_STATUS_PROGRAM_CHANGE(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PROGRAM_CHANGE | |||||
def MIDI_IS_STATUS_CHANNEL_PRESSURE(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_CHANNEL_PRESSURE | |||||
def MIDI_IS_STATUS_PITCH_WHEEL_CONTROL(status): | |||||
return MIDI_IS_CHANNEL_MESSAGE(status) and (status & MIDI_STATUS_BIT) == MIDI_STATUS_PITCH_WHEEL_CONTROL | |||||
# MIDI Utils | # MIDI Utils | ||||
def MIDI_GET_STATUS_FROM_DATA(data): return data[0] & MIDI_STATUS_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else data[0] | |||||
def MIDI_GET_CHANNEL_FROM_DATA(data): return data[0] & MIDI_CHANNEL_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else 0 | |||||
def MIDI_GET_STATUS_FROM_DATA(data): | |||||
return data[0] & MIDI_STATUS_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else data[0] | |||||
def MIDI_GET_CHANNEL_FROM_DATA(data): | |||||
return data[0] & MIDI_CHANNEL_BIT if MIDI_IS_CHANNEL_MESSAGE(data[0]) else 0 | |||||
# --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
# Graphics Items | # Graphics Items | ||||
@@ -1,99 +0,0 @@ | |||||
#!/usr/bin/env python3 | |||||
# -*- coding: utf-8 -*- | |||||
# Pixmap Button, a custom Qt widget | |||||
# Copyright (C) 2013-2019 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 doc/GPL.txt file. | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Imports (Global) | |||||
from PyQt5.QtCore import QPointF, QRectF | |||||
from PyQt5.QtGui import QColor, QFont, QPainter, QPixmap | |||||
from PyQt5.QtWidgets import QPushButton | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Widget Class | |||||
class PixmapButton(QPushButton): | |||||
def __init__(self, parent): | |||||
QPushButton.__init__(self, parent) | |||||
self.fPixmapNormal = QPixmap() | |||||
self.fPixmapDown = QPixmap() | |||||
self.fPixmapHover = QPixmap() | |||||
self.fPixmapRect = QRectF(0, 0, 0, 0) | |||||
self.fIsHovered = False | |||||
self.fTopText = "" | |||||
self.fTopTextColor = QColor() | |||||
self.fTopTextFont = QFont() | |||||
self.setText("") | |||||
def setPixmaps(self, normal, down, hover): | |||||
self.fPixmapNormal.load(normal) | |||||
self.fPixmapDown.load(down) | |||||
self.fPixmapHover.load(hover) | |||||
width = self.fPixmapNormal.width() | |||||
height = self.fPixmapNormal.height() | |||||
self.fPixmapRect = QRectF(0, 0, width, height) | |||||
self.setMinimumSize(width, height) | |||||
self.setMaximumSize(width, height) | |||||
def setTopText(self, text, color, font): | |||||
self.fTopText = text | |||||
self.fTopTextColor = color | |||||
self.fTopTextFont = font | |||||
def enterEvent(self, event): | |||||
self.fIsHovered = True | |||||
QPushButton.enterEvent(self, event) | |||||
def leaveEvent(self, event): | |||||
self.fIsHovered = False | |||||
QPushButton.leaveEvent(self, event) | |||||
def paintEvent(self, event): | |||||
painter = QPainter(self) | |||||
event.accept() | |||||
if not self.isEnabled(): | |||||
painter.save() | |||||
painter.setOpacity(0.2) | |||||
painter.drawPixmap(self.fPixmapRect, self.fPixmapNormal, self.fPixmapRect) | |||||
painter.restore() | |||||
elif self.isChecked() or self.isDown(): | |||||
painter.drawPixmap(self.fPixmapRect, self.fPixmapDown, self.fPixmapRect) | |||||
elif self.fIsHovered: | |||||
painter.drawPixmap(self.fPixmapRect, self.fPixmapHover, self.fPixmapRect) | |||||
else: | |||||
painter.drawPixmap(self.fPixmapRect, self.fPixmapNormal, self.fPixmapRect) | |||||
if not self.fTopText: | |||||
return | |||||
painter.save() | |||||
painter.setPen(self.fTopTextColor) | |||||
painter.setBrush(self.fTopTextColor) | |||||
painter.setFont(self.fTopTextFont) | |||||
painter.drawText(QPointF(10, 16), self.fTopText) | |||||
painter.restore() |
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Pixmap Dial, a custom Qt widget | # Pixmap Dial, a custom Qt widget | ||||
# Copyright (C) 2011-2019 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com> | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License as | # modify it under the terms of the GNU General Public License as | ||||
@@ -16,64 +16,24 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from math import cos, floor, pi, sin, isnan | |||||
from math import cos, floor, pi, sin | |||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize | |||||
from PyQt5.QtGui import QColor, QConicalGradient, QFont, QFontMetrics | |||||
from PyQt5.QtGui import QLinearGradient, QPainter, QPainterPath, QPen, QPixmap | |||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize | |||||
from PyQt5.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap | |||||
from PyQt5.QtWidgets import QDial | from PyQt5.QtWidgets import QDial | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Widget Class | |||||
class PixmapDial(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 | |||||
from .commondial import CommonDial | |||||
MODE_DEFAULT = 0 | |||||
MODE_LINEAR = 1 | |||||
# signals | |||||
dragStateChanged = pyqtSignal(bool) | |||||
realValueChanged = pyqtSignal(float) | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Widget Class | |||||
class PixmapDial(CommonDial): | |||||
def __init__(self, parent, index=0): | def __init__(self, parent, index=0): | ||||
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 | |||||
CommonDial.__init__(self, parent, index) | |||||
self.fIsHovered = False | |||||
self.fIsPressed = False | |||||
self.fHoverStep = self.HOVER_MIN | |||||
self.fLastDragPos = None | |||||
self.fLastDragValue = 0.0 | |||||
self.fIndex = index | |||||
self.fPixmap = QPixmap(":/bitmaps/dial_01d.png") | self.fPixmap = QPixmap(":/bitmaps/dial_01d.png") | ||||
self.fPixmapNum = "01" | self.fPixmapNum = "01" | ||||
@@ -82,60 +42,11 @@ class PixmapDial(QDial): | |||||
else: | else: | ||||
self.fPixmapOrientation = self.VERTICAL | self.fPixmapOrientation = self.VERTICAL | ||||
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) | |||||
self.updateSizes() | self.updateSizes() | ||||
# 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 getIndex(self): | |||||
return self.fIndex | |||||
def getBaseSize(self): | def getBaseSize(self): | ||||
return self.fPixmapBaseSize | return self.fPixmapBaseSize | ||||
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 updateSizes(self): | def updateSizes(self): | ||||
self.fPixmapWidth = self.fPixmap.width() | self.fPixmapWidth = self.fPixmap.width() | ||||
self.fPixmapHeight = self.fPixmap.height() | self.fPixmapHeight = self.fPixmap.height() | ||||
@@ -178,31 +89,6 @@ class PixmapDial(QDial): | |||||
self.fLabelGradientRect = QRectF(float(self.fPixmapBaseSize)/8.0, float(self.fPixmapBaseSize)/2.0, float(self.fPixmapBaseSize*3)/4.0, self.fPixmapBaseSize+self.fLabelHeight+5) | self.fLabelGradientRect = QRectF(float(self.fPixmapBaseSize)/8.0, float(self.fPixmapBaseSize)/2.0, float(self.fPixmapBaseSize*3)/4.0, self.fPixmapBaseSize+self.fLabelHeight+5) | ||||
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() | |||||
def setIndex(self, index): | |||||
self.fIndex = index | |||||
def setPixmap(self, pixmapId): | def setPixmap(self, pixmapId): | ||||
self.fPixmapNum = "%02i" % pixmapId | self.fPixmapNum = "%02i" % pixmapId | ||||
self.fPixmap.load(":/bitmaps/dial_%s%s.png" % (self.fPixmapNum, "" if self.isEnabled() else "d")) | self.fPixmap.load(":/bitmaps/dial_%s%s.png" % (self.fPixmapNum, "" if self.isEnabled() else "d")) | ||||
@@ -229,46 +115,6 @@ class PixmapDial(QDial): | |||||
self.updateSizes() | self.updateSizes() | ||||
self.update() | self.update() | ||||
def setPrecision(self, value, isInteger): | |||||
self.fPrecision = value | |||||
self.fIsInteger = isInteger | |||||
QDial.setMaximum(self, value) | |||||
def setMinimum(self, value): | |||||
self.fMinimum = value | |||||
def setMaximum(self, value): | |||||
self.fMaximum = value | |||||
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 = 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) | |||||
@pyqtSlot(int) | |||||
def slot_valueChanged(self, value): | |||||
self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum | |||||
self.realValueChanged.emit(self.fRealValue) | |||||
@pyqtSlot() | @pyqtSlot() | ||||
def slot_updatePixmap(self): | def slot_updatePixmap(self): | ||||
self.setPixmap(int(self.fPixmapNum)) | self.setPixmap(int(self.fPixmapNum)) | ||||
@@ -280,81 +126,13 @@ class PixmapDial(QDial): | |||||
return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize) | return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize) | ||||
def changeEvent(self, event): | def changeEvent(self, event): | ||||
QDial.changeEvent(self, event) | |||||
CommonDial.changeEvent(self, event) | |||||
# Force pixmap update if enabled state changes | # Force pixmap update if enabled state changes | ||||
if event.type() == QEvent.EnabledChange: | if event.type() == QEvent.EnabledChange: | ||||
self.setPixmap(int(self.fPixmapNum)) | self.setPixmap(int(self.fPixmapNum)) | ||||
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: | |||||
return QDial.mousePressEvent(self, event) | |||||
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: | |||||
return QDial.mouseMoveEvent(self, event) | |||||
if not self.fIsPressed: | |||||
return | |||||
range = (self.fMaximum - self.fMinimum) / 4.0 | |||||
pos = event.pos() | |||||
dx = range * float(pos.x() - self.fLastDragPos.x()) / self.width() | |||||
dy = range * 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: | |||||
return QDial.mouseReleaseEvent(self, event) | |||||
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) | |||||
def paintDial(self, painter): | |||||
if self.isEnabled(): | if self.isEnabled(): | ||||
normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | ||||
target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize) | target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize) | ||||
@@ -494,26 +272,4 @@ class PixmapDial(QDial): | |||||
target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize) | target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize) | ||||
painter.drawPixmap(target, self.fPixmap, target) | painter.drawPixmap(target, self.fPixmap, target) | ||||
painter.restore() | |||||
def resizeEvent(self, event): | |||||
QDial.resizeEvent(self, event) | |||||
self.updateSizes() | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Main Testing | |||||
if __name__ == '__main__': | |||||
import sys | |||||
from PyQt5.QtWidgets import QApplication | |||||
import resources_rc | |||||
app = QApplication(sys.argv) | |||||
gui = PixmapDial(None) | |||||
#gui.setEnabled(True) | |||||
#gui.setEnabled(False) | |||||
gui.setPixmap(3) | |||||
gui.setLabel("hahaha") | |||||
gui.show() | |||||
sys.exit(app.exec_()) | |||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Pixmap Keyboard, a custom Qt widget | # Pixmap Keyboard, a custom Qt widget | ||||
# Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com> | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License as | # modify it under the terms of the GNU General Public License as | ||||
@@ -16,19 +16,19 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer, QSize | |||||
from PyQt5.QtGui import QColor, QFont, QPainter, QPixmap | |||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF, QTimer, QSize | |||||
from PyQt5.QtGui import QColor, QPainter, QPixmap | |||||
from PyQt5.QtWidgets import QActionGroup, QMenu, QScrollArea, QWidget | from PyQt5.QtWidgets import QActionGroup, QMenu, QScrollArea, QWidget | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Custom) | # Imports (Custom) | ||||
from carla_shared import QSafeSettings | from carla_shared import QSafeSettings | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
kMidiKey2RectMapHorizontal = [ | kMidiKey2RectMapHorizontal = [ | ||||
QRectF(0, 0, 24, 57), # C | QRectF(0, 0, 24, 57), # C | ||||
@@ -175,6 +175,12 @@ kValidColors = ("Blue", "Green", "Orange", "Red") | |||||
kBlackNotes = (1, 3, 6, 8, 10) | kBlackNotes = (1, 3, 6, 8, 10) | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
def _isNoteBlack(note): | |||||
baseNote = note % 12 | |||||
return bool(baseNote in kBlackNotes) | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
# MIDI Keyboard, using a pixmap for painting | # MIDI Keyboard, using a pixmap for painting | ||||
@@ -502,7 +508,7 @@ class PixmapKeyboard(QWidget): | |||||
for note in self.fEnabledKeys: | for note in self.fEnabledKeys: | ||||
pos = self._getRectFromMidiNote(note) | pos = self._getRectFromMidiNote(note) | ||||
if self._isNoteBlack(note): | |||||
if _isNoteBlack(note): | |||||
continue | continue | ||||
if note < 12: | if note < 12: | ||||
@@ -558,7 +564,7 @@ class PixmapKeyboard(QWidget): | |||||
for note in self.fEnabledKeys: | for note in self.fEnabledKeys: | ||||
pos = self._getRectFromMidiNote(note) | pos = self._getRectFromMidiNote(note) | ||||
if not self._isNoteBlack(note): | |||||
if not _isNoteBlack(note): | |||||
continue | continue | ||||
if note < 12: | if note < 12: | ||||
@@ -605,15 +611,11 @@ class PixmapKeyboard(QWidget): | |||||
Qt.AlignCenter, | Qt.AlignCenter, | ||||
"C{}".format(octave)) | "C{}".format(octave)) | ||||
def _isNoteBlack(self, note): | |||||
baseNote = note % 12 | |||||
return bool(baseNote in kBlackNotes) | |||||
def _getRectFromMidiNote(self, note): | def _getRectFromMidiNote(self, note): | ||||
baseNote = note % 12 | baseNote = note % 12 | ||||
return self.fKey2RectMap[baseNote] | return self.fKey2RectMap[baseNote] | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Horizontal scroll area for keyboard | # Horizontal scroll area for keyboard | ||||
class PixmapKeyboardHArea(QScrollArea): | class PixmapKeyboardHArea(QScrollArea): | ||||
@@ -641,18 +643,4 @@ class PixmapKeyboardHArea(QScrollArea): | |||||
def slot_initScrollbarValue(self): | def slot_initScrollbarValue(self): | ||||
self.horizontalScrollBar().setValue(int(self.horizontalScrollBar().maximum()/2)) | self.horizontalScrollBar().setValue(int(self.horizontalScrollBar().maximum()/2)) | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Main Testing | |||||
if __name__ == '__main__': | |||||
import sys | |||||
from PyQt5.QtWidgets import QApplication | |||||
import resources_rc | |||||
app = QApplication(sys.argv) | |||||
gui = PixmapKeyboard(None) | |||||
gui.setEnabled(True) | |||||
gui.show() | |||||
sys.exit(app.exec_()) | |||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -16,7 +16,7 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from PyQt5.QtCore import QPointF, QRectF | from PyQt5.QtCore import QPointF, QRectF | ||||
@@ -24,7 +24,7 @@ from PyQt5.QtGui import QColor, QFont, QPainter, QPixmap | |||||
from PyQt5.QtSvg import QSvgWidget | from PyQt5.QtSvg import QSvgWidget | ||||
from PyQt5.QtWidgets import QPushButton | from PyQt5.QtWidgets import QPushButton | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Widget Class | # Widget Class | ||||
class ScalableButton(QPushButton): | class ScalableButton(QPushButton): | ||||
@@ -136,3 +136,5 @@ class ScalableButton(QPushButton): | |||||
else: | else: | ||||
self.fImageNormal.renderer().render(painter, self.fImageRect) | self.fImageNormal.renderer().render(painter, self.fImageRect) | ||||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -2,7 +2,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# Scalable Dial, a custom Qt widget | # Scalable Dial, a custom Qt widget | ||||
# Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com> | |||||
# Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com> | |||||
# | # | ||||
# This program is free software; you can redistribute it and/or | # This program is free software; you can redistribute it and/or | ||||
# modify it under the terms of the GNU General Public License as | # modify it under the terms of the GNU General Public License as | ||||
@@ -16,65 +16,24 @@ | |||||
# | # | ||||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | # For a full copy of the GNU General Public License see the doc/GPL.txt file. | ||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Imports (Global) | # Imports (Global) | ||||
from math import cos, floor, pi, sin, isnan | |||||
from math import cos, floor, pi, sin | |||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize | |||||
from PyQt5.QtGui import QColor, QConicalGradient, QFont, QFontMetrics | |||||
from PyQt5.QtGui import QLinearGradient, QPainter, QPainterPath, QPen, QPixmap | |||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize | |||||
from PyQt5.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap | |||||
from PyQt5.QtSvg import QSvgWidget | from PyQt5.QtSvg import QSvgWidget | ||||
from PyQt5.QtWidgets import QDial | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Widget Class | |||||
class ScalableDial(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 | |||||
from .commondial import CommonDial | |||||
MODE_DEFAULT = 0 | |||||
MODE_LINEAR = 1 | |||||
# signals | |||||
dragStateChanged = pyqtSignal(bool) | |||||
realValueChanged = pyqtSignal(float) | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Widget Class | |||||
class ScalableDial(CommonDial): | |||||
def __init__(self, parent, index=0): | def __init__(self, parent, index=0): | ||||
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 | |||||
CommonDial.__init__(self, parent, index) | |||||
self.fIsHovered = False | |||||
self.fIsPressed = False | |||||
self.fHoverStep = self.HOVER_MIN | |||||
self.fLastDragPos = None | |||||
self.fLastDragValue = 0.0 | |||||
self.fIndex = index | |||||
self.fImage = QSvgWidget(":/scalable/dial_03.svg") | self.fImage = QSvgWidget(":/scalable/dial_03.svg") | ||||
self.fImageNum = "01" | self.fImageNum = "01" | ||||
@@ -83,60 +42,11 @@ class ScalableDial(QDial): | |||||
else: | else: | ||||
self.fImageOrientation = self.VERTICAL | self.fImageOrientation = self.VERTICAL | ||||
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) | |||||
self.updateSizes() | self.updateSizes() | ||||
# 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 getIndex(self): | |||||
return self.fIndex | |||||
def getBaseSize(self): | def getBaseSize(self): | ||||
return self.fImageBaseSize | return self.fImageBaseSize | ||||
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 updateSizes(self): | def updateSizes(self): | ||||
if isinstance(self.fImage, QPixmap): | if isinstance(self.fImage, QPixmap): | ||||
self.fImageWidth = self.fImage.width() | self.fImageWidth = self.fImage.width() | ||||
@@ -184,31 +94,6 @@ class ScalableDial(QDial): | |||||
self.fLabelGradientRect = QRectF(float(self.fImageBaseSize)/8.0, float(self.fImageBaseSize)/2.0, | self.fLabelGradientRect = QRectF(float(self.fImageBaseSize)/8.0, float(self.fImageBaseSize)/2.0, | ||||
float(self.fImageBaseSize*3)/4.0, self.fImageBaseSize+self.fLabelHeight+5) | float(self.fImageBaseSize*3)/4.0, self.fImageBaseSize+self.fLabelHeight+5) | ||||
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() | |||||
def setIndex(self, index): | |||||
self.fIndex = index | |||||
def setImage(self, imageId): | def setImage(self, imageId): | ||||
self.fImageNum = "%02i" % imageId | self.fImageNum = "%02i" % imageId | ||||
if imageId in (2,6,7,8,9,10,11,12,13): | if imageId in (2,6,7,8,9,10,11,12,13): | ||||
@@ -247,49 +132,6 @@ class ScalableDial(QDial): | |||||
self.updateSizes() | self.updateSizes() | ||||
self.update() | self.update() | ||||
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) | |||||
@pyqtSlot(int) | |||||
def slot_valueChanged(self, value): | |||||
self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum | |||||
self.realValueChanged.emit(self.fRealValue) | |||||
@pyqtSlot() | @pyqtSlot() | ||||
def slot_updateImage(self): | def slot_updateImage(self): | ||||
self.setImage(int(self.fImageNum)) | self.setImage(int(self.fImageNum)) | ||||
@@ -301,81 +143,13 @@ class ScalableDial(QDial): | |||||
return QSize(self.fImageBaseSize, self.fImageBaseSize) | return QSize(self.fImageBaseSize, self.fImageBaseSize) | ||||
def changeEvent(self, event): | def changeEvent(self, event): | ||||
QDial.changeEvent(self, event) | |||||
CommonDial.changeEvent(self, event) | |||||
# Force svg update if enabled state changes | # Force svg update if enabled state changes | ||||
if event.type() == QEvent.EnabledChange: | if event.type() == QEvent.EnabledChange: | ||||
self.slot_updateImage() | self.slot_updateImage() | ||||
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: | |||||
return QDial.mousePressEvent(self, event) | |||||
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: | |||||
return QDial.mouseMoveEvent(self, event) | |||||
if not self.fIsPressed: | |||||
return | |||||
range = (self.fMaximum - self.fMinimum) / 4.0 | |||||
pos = event.pos() | |||||
dx = range * float(pos.x() - self.fLastDragPos.x()) / self.width() | |||||
dy = range * 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: | |||||
return QDial.mouseReleaseEvent(self, event) | |||||
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) | |||||
def paintDial(self, painter): | |||||
if self.isEnabled(): | if self.isEnabled(): | ||||
normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | ||||
curLayer = int((self.fImageLayersCount - 1) * normValue) | curLayer = int((self.fImageLayersCount - 1) * normValue) | ||||
@@ -521,26 +295,4 @@ class ScalableDial(QDial): | |||||
else: | else: | ||||
self.fImage.renderer().render(painter, target) | self.fImage.renderer().render(painter, target) | ||||
painter.restore() | |||||
def resizeEvent(self, event): | |||||
QDial.resizeEvent(self, event) | |||||
self.updateSizes() | |||||
# ------------------------------------------------------------------------------------------------------------ | |||||
# Main Testing | |||||
if __name__ == '__main__': | |||||
import sys | |||||
from PyQt5.QtWidgets import QApplication | |||||
import resources_rc | |||||
app = QApplication(sys.argv) | |||||
gui = ScalableDial(None) | |||||
#gui.setEnabled(True) | |||||
#gui.setEnabled(False) | |||||
gui.setSvg(3) | |||||
gui.setLabel("hahaha") | |||||
gui.show() | |||||
sys.exit(app.exec_()) | |||||
# --------------------------------------------------------------------------------------------------------------------- |