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_release: $(QMs) | |||
@@ -162,9 +162,9 @@ class CanvasPreviewFrame(QFrame): | |||
if self.fZoomCursors[self._kCursorName] != cursorName: | |||
prefix = ":/cursors/zoom" | |||
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() | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
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 -*- | |||
# 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 | |||
# modify it under the terms of the GNU General Public License as | |||
@@ -20,7 +20,6 @@ | |||
# Imports (Global) | |||
from PyQt5.QtCore import pyqtSlot, Qt | |||
from PyQt5.QtGui import QCursor, QFont | |||
from PyQt5.QtWidgets import QFrame, QSizePolicy, QToolButton, QVBoxLayout, QWidget | |||
# ------------------------------------------------------------------------------------------------------------ | |||
@@ -75,3 +74,5 @@ class CollapsibleBox(QFrame): | |||
def getContentLayout(self): | |||
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 -*- | |||
# 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 | |||
# 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. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Global) | |||
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.QtWidgets import QWidget | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Widget Class | |||
class DigitalPeakMeter(QWidget): | |||
@@ -43,7 +43,7 @@ class DigitalPeakMeter(QWidget): | |||
STYLE_RNCBC = 3 | |||
STYLE_CALF = 4 | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def __init__(self, parent): | |||
QWidget.__init__(self, parent) | |||
@@ -71,7 +71,7 @@ class DigitalPeakMeter(QWidget): | |||
self.updateGrandient() | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def channelCount(self): | |||
return self.fChannelCount | |||
@@ -81,13 +81,14 @@ class DigitalPeakMeter(QWidget): | |||
return | |||
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.fChannelData = [] | |||
self.fLastChannelData = [] | |||
for x in range(count): | |||
for _ in range(count): | |||
self.fChannelData.append(0.0) | |||
self.fLastChannelData.append(0.0) | |||
@@ -98,7 +99,7 @@ class DigitalPeakMeter(QWidget): | |||
self.setMinimumSize(0, 0) | |||
self.setMaximumSize(9999, 9999) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def meterColor(self): | |||
return self.fMeterColor | |||
@@ -108,7 +109,8 @@ class DigitalPeakMeter(QWidget): | |||
return | |||
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: | |||
self.fMeterColorBase = QColor(93, 231, 61) | |||
@@ -121,7 +123,7 @@ class DigitalPeakMeter(QWidget): | |||
self.updateGrandient() | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def meterLinesEnabled(self): | |||
return self.fMeterLinesEnabled | |||
@@ -132,7 +134,7 @@ class DigitalPeakMeter(QWidget): | |||
self.fMeterLinesEnabled = yesNo | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def meterOrientation(self): | |||
return self.fMeterOrientation | |||
@@ -142,13 +144,14 @@ class DigitalPeakMeter(QWidget): | |||
return | |||
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.updateGrandient() | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def meterStyle(self): | |||
return self.fMeterStyle | |||
@@ -158,7 +161,8 @@ class DigitalPeakMeter(QWidget): | |||
return | |||
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: | |||
self.fMeterBackground = QColor("#070707") | |||
@@ -182,7 +186,7 @@ class DigitalPeakMeter(QWidget): | |||
self.updateGrandient() | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def smoothMultiplier(self): | |||
return self.fSmoothMultiplier | |||
@@ -192,28 +196,37 @@ class DigitalPeakMeter(QWidget): | |||
return | |||
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: | |||
return qCritical("DigitalPeakMeter::setSmoothMultiplier(%i) - value must be >= 0" % value) | |||
qCritical(f"DigitalPeakMeter::setSmoothMultiplier({value}) - value must be >= 0") | |||
return | |||
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 | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def displayMeter(self, meter, level, forced = False): | |||
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): | |||
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: | |||
return qCritical("DigitalPeakMeter::displayMeter(%i, %f) - invalid meter number" % (meter, level)) | |||
qCritical(f"DigitalPeakMeter::displayMeter({meter}, {level}) - invalid meter number") | |||
return | |||
i = meter - 1 | |||
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: | |||
level = 0.0 | |||
@@ -226,7 +239,7 @@ class DigitalPeakMeter(QWidget): | |||
self.fLastChannelData[i] = level | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def updateGrandient(self): | |||
self.fMeterGradient = QLinearGradient(0, 0, 1, 1) | |||
@@ -287,15 +300,15 @@ class DigitalPeakMeter(QWidget): | |||
elif self.fMeterOrientation == self.VERTICAL: | |||
self.fMeterGradient.setFinalStop(0, self.height()) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def minimumSizeHint(self): | |||
return QSize(10, 10) | |||
return QSize(20, 10) if self.fMeterOrientation == self.HORIZONTAL else QSize(10, 20) | |||
def sizeHint(self): | |||
return QSize(self.width(), self.height()) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def drawCalf(self, event): | |||
painter = QPainter(self) | |||
@@ -323,7 +336,8 @@ class DigitalPeakMeter(QWidget): | |||
def paintEvent(self, event): | |||
if self.fMeterStyle == self.STYLE_CALF: | |||
return self.drawCalf(event) | |||
self.drawCalf(event) | |||
return | |||
painter = QPainter(self) | |||
event.accept() | |||
@@ -440,30 +454,10 @@ class DigitalPeakMeter(QWidget): | |||
painter.setPen(QColor(110, 15, 15, 100)) | |||
painter.drawLine(QLineF(2, lsmall - (lsmall * 0.96), lfull-2.0, lsmall - (lsmall * 0.96))) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def resizeEvent(self, event): | |||
QWidget.resizeEvent(self, event) | |||
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 -*- | |||
# 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 | |||
# 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. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# 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.QtWidgets import QGraphicsView | |||
from PyQt5.QtWidgets import QGraphicsView, QMessageBox | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Custom Stuff) | |||
from carla_shared import * | |||
from carla_shared import MACOS, CustomMessageBox, gCarla | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Widget Class | |||
class DraggableGraphicsView(QGraphicsView): | |||
@@ -49,7 +50,7 @@ class DraggableGraphicsView(QGraphicsView): | |||
self.setAcceptDrops(True) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def isDragUrlValid(self, filename): | |||
lfilename = filename.lower() | |||
@@ -59,7 +60,7 @@ class DraggableGraphicsView(QGraphicsView): | |||
#return True | |||
if MACOS and lfilename.endswith(".vst"): | |||
return True | |||
elif lfilename.endswith(".vst3") and ".vst3" in self.fSupportedExtensions: | |||
if lfilename.endswith(".vst3") and ".vst3" in self.fSupportedExtensions: | |||
return True | |||
elif os.path.isfile(filename): | |||
@@ -68,7 +69,7 @@ class DraggableGraphicsView(QGraphicsView): | |||
return False | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def dragEnterEvent(self, event): | |||
urls = event.mimeData().urls() | |||
@@ -93,7 +94,7 @@ class DraggableGraphicsView(QGraphicsView): | |||
self.fWasLastDragValid = False | |||
QGraphicsView.dragLeaveEvent(self, event) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def dropEvent(self, event): | |||
event.acceptProposedAction() | |||
@@ -111,7 +112,7 @@ class DraggableGraphicsView(QGraphicsView): | |||
self.tr("Failed to load file"), | |||
gCarla.gui.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# ----------------------------------------------------------------------------------------------------------------- | |||
def mousePressEvent(self, event): | |||
if event.button() == self.fMiddleButton and not (event.modifiers() & Qt.ControlModifier): | |||
@@ -151,4 +152,4 @@ class DraggableGraphicsView(QGraphicsView): | |||
return | |||
QGraphicsView.wheelEvent(self, event) | |||
# -------------------------------------------------------------------------------------------------------- | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -16,7 +16,7 @@ | |||
# | |||
# For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Global) | |||
from PyQt5.QtCore import QRectF | |||
@@ -24,7 +24,7 @@ from PyQt5.QtGui import QPainter, QPixmap | |||
from PyQt5.QtSvg import QSvgWidget | |||
from PyQt5.QtWidgets import QPushButton | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Widget Class | |||
class LEDButton(QPushButton): | |||
@@ -61,10 +61,10 @@ class LEDButton(QPushButton): | |||
self.fLastColor = self.UNSET | |||
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.setFixedSize(self.fImage.size()) | |||
@@ -87,7 +87,7 @@ class LEDButton(QPushButton): | |||
def _loadImageNowIfNeeded(self): | |||
if self.isChecked(): | |||
if self.fLastColor == self.fColor: | |||
return | |||
return False | |||
if self.fColor == self.OFF: | |||
img = ":/scalable/led_off.svg" | |||
elif self.fColor == self.BLUE: | |||
@@ -122,3 +122,5 @@ class LEDButton(QPushButton): | |||
self.update() | |||
return True | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -76,7 +76,9 @@ class CustomInputDialog(QDialog): | |||
text = "<table>" | |||
for scalePoint in scalePoints: | |||
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>" | |||
self.ui.textBrowser.setText(text) | |||
self.resize(200, 300) | |||
@@ -341,10 +343,8 @@ class ParamSpinBox(QAbstractSpinBox): | |||
else: | |||
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) | |||
@@ -2,7 +2,7 @@ | |||
# -*- coding: utf-8 -*- | |||
# 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 | |||
# | |||
# This program is free software; you can redistribute it and/or | |||
@@ -21,15 +21,15 @@ | |||
# Imports (Global) | |||
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 QApplication, QComboBox, QHBoxLayout, QLabel, QStyle, QVBoxLayout, QWidget | |||
from PyQt5.QtWidgets import QApplication, QStyle, QWidget | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# Imports (Custom) | |||
from carla_shared import * | |||
#from carla_shared import * | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# 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 == '#' | |||
# 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 | |||
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 | |||
@@ -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 -*- | |||
# 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 | |||
# 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. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# 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 | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# 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): | |||
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.fPixmapNum = "01" | |||
@@ -82,60 +42,11 @@ class PixmapDial(QDial): | |||
else: | |||
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() | |||
# 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): | |||
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): | |||
self.fPixmapWidth = self.fPixmap.width() | |||
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) | |||
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): | |||
self.fPixmapNum = "%02i" % pixmapId | |||
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.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() | |||
def slot_updatePixmap(self): | |||
self.setPixmap(int(self.fPixmapNum)) | |||
@@ -280,81 +126,13 @@ class PixmapDial(QDial): | |||
return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize) | |||
def changeEvent(self, event): | |||
QDial.changeEvent(self, event) | |||
CommonDial.changeEvent(self, event) | |||
# Force pixmap update if enabled state changes | |||
if event.type() == QEvent.EnabledChange: | |||
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(): | |||
normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | |||
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) | |||
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 -*- | |||
# 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 | |||
# 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. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# 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 | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Custom) | |||
from carla_shared import QSafeSettings | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
kMidiKey2RectMapHorizontal = [ | |||
QRectF(0, 0, 24, 57), # C | |||
@@ -175,6 +175,12 @@ kValidColors = ("Blue", "Green", "Orange", "Red") | |||
kBlackNotes = (1, 3, 6, 8, 10) | |||
# ------------------------------------------------------------------------------------------------------------ | |||
def _isNoteBlack(note): | |||
baseNote = note % 12 | |||
return bool(baseNote in kBlackNotes) | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# MIDI Keyboard, using a pixmap for painting | |||
@@ -502,7 +508,7 @@ class PixmapKeyboard(QWidget): | |||
for note in self.fEnabledKeys: | |||
pos = self._getRectFromMidiNote(note) | |||
if self._isNoteBlack(note): | |||
if _isNoteBlack(note): | |||
continue | |||
if note < 12: | |||
@@ -558,7 +564,7 @@ class PixmapKeyboard(QWidget): | |||
for note in self.fEnabledKeys: | |||
pos = self._getRectFromMidiNote(note) | |||
if not self._isNoteBlack(note): | |||
if not _isNoteBlack(note): | |||
continue | |||
if note < 12: | |||
@@ -605,15 +611,11 @@ class PixmapKeyboard(QWidget): | |||
Qt.AlignCenter, | |||
"C{}".format(octave)) | |||
def _isNoteBlack(self, note): | |||
baseNote = note % 12 | |||
return bool(baseNote in kBlackNotes) | |||
def _getRectFromMidiNote(self, note): | |||
baseNote = note % 12 | |||
return self.fKey2RectMap[baseNote] | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Horizontal scroll area for keyboard | |||
class PixmapKeyboardHArea(QScrollArea): | |||
@@ -641,18 +643,4 @@ class PixmapKeyboardHArea(QScrollArea): | |||
def slot_initScrollbarValue(self): | |||
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. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Imports (Global) | |||
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.QtWidgets import QPushButton | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# Widget Class | |||
class ScalableButton(QPushButton): | |||
@@ -136,3 +136,5 @@ class ScalableButton(QPushButton): | |||
else: | |||
self.fImageNormal.renderer().render(painter, self.fImageRect) | |||
# --------------------------------------------------------------------------------------------------------------------- |
@@ -2,7 +2,7 @@ | |||
# -*- coding: utf-8 -*- | |||
# 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 | |||
# 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. | |||
# ------------------------------------------------------------------------------------------------------------ | |||
# --------------------------------------------------------------------------------------------------------------------- | |||
# 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.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): | |||
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.fImageNum = "01" | |||
@@ -83,60 +42,11 @@ class ScalableDial(QDial): | |||
else: | |||
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() | |||
# 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): | |||
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): | |||
if isinstance(self.fImage, QPixmap): | |||
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, | |||
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): | |||
self.fImageNum = "%02i" % imageId | |||
if imageId in (2,6,7,8,9,10,11,12,13): | |||
@@ -247,49 +132,6 @@ class ScalableDial(QDial): | |||
self.updateSizes() | |||
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() | |||
def slot_updateImage(self): | |||
self.setImage(int(self.fImageNum)) | |||
@@ -301,81 +143,13 @@ class ScalableDial(QDial): | |||
return QSize(self.fImageBaseSize, self.fImageBaseSize) | |||
def changeEvent(self, event): | |||
QDial.changeEvent(self, event) | |||
CommonDial.changeEvent(self, event) | |||
# Force svg update if enabled state changes | |||
if event.type() == QEvent.EnabledChange: | |||
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(): | |||
normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | |||
curLayer = int((self.fImageLayersCount - 1) * normValue) | |||
@@ -521,26 +295,4 @@ class ScalableDial(QDial): | |||
else: | |||
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_()) | |||
# --------------------------------------------------------------------------------------------------------------------- |