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_()) | |||||
| # --------------------------------------------------------------------------------------------------------------------- | |||||