diff --git a/source/frontend/Makefile b/source/frontend/Makefile index d5932de03..15eae6f02 100644 --- a/source/frontend/Makefile +++ b/source/frontend/Makefile @@ -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) diff --git a/source/frontend/widgets/canvaspreviewframe.py b/source/frontend/widgets/canvaspreviewframe.py index 5f21ccfd0..f7a17ac99 100644 --- a/source/frontend/widgets/canvaspreviewframe.py +++ b/source/frontend/widgets/canvaspreviewframe.py @@ -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_()) diff --git a/source/frontend/widgets/collapsablewidget.py b/source/frontend/widgets/collapsablewidget.py index ead9d4afc..e3e0a01a6 100644 --- a/source/frontend/widgets/collapsablewidget.py +++ b/source/frontend/widgets/collapsablewidget.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Collapsible Box, a custom Qt widget -# Copyright (C) 2019 Filipe Coelho +# Copyright (C) 2019-2022 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -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 + +# ------------------------------------------------------------------------------------------------------------ diff --git a/source/frontend/widgets/commondial.py b/source/frontend/widgets/commondial.py new file mode 100644 index 000000000..68d4ac18a --- /dev/null +++ b/source/frontend/widgets/commondial.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Common Dial, a custom Qt widget +# Copyright (C) 2011-2022 Filipe Coelho +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# For a full copy of the GNU General Public License see the doc/GPL.txt file. + +# --------------------------------------------------------------------------------------------------------------------- +# Imports (Global) + +from math import isnan + +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF +from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter +from PyQt5.QtWidgets import QDial + +# --------------------------------------------------------------------------------------------------------------------- +# Widget Class + +# to be implemented by subclasses + #def updateSizes(self): + #def paintDial(self, painter): + +class CommonDial(QDial): + # enum CustomPaintMode + CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient) + CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3) + CUSTOM_PAINT_MODE_CARLA_VOL = 2 # color blue (reserved #3) + CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4) + CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4) + CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3) + CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3) + CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6) + CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient + + # enum Orientation + HORIZONTAL = 0 + VERTICAL = 1 + + HOVER_MIN = 0 + HOVER_MAX = 9 + + MODE_DEFAULT = 0 + MODE_LINEAR = 1 + + # signals + dragStateChanged = pyqtSignal(bool) + realValueChanged = pyqtSignal(float) + + def __init__(self, parent, index): + QDial.__init__(self, parent) + + self.fDialMode = self.MODE_LINEAR + + self.fMinimum = 0.0 + self.fMaximum = 1.0 + self.fRealValue = 0.0 + self.fPrecision = 10000 + self.fIsInteger = False + + self.fIsHovered = False + self.fIsPressed = False + self.fHoverStep = self.HOVER_MIN + + self.fLastDragPos = None + self.fLastDragValue = 0.0 + + self.fIndex = index + + self.fLabel = "" + self.fLabelPos = QPointF(0.0, 0.0) + self.fLabelFont = QFont(self.font()) + self.fLabelFont.setPixelSize(8) + self.fLabelWidth = 0 + self.fLabelHeight = 0 + + if self.palette().window().color().lightness() > 100: + # Light background + c = self.palette().dark().color() + self.fLabelGradientColor1 = c + self.fLabelGradientColor2 = QColor(c.red(), c.green(), c.blue(), 0) + self.fLabelGradientColorT = [self.palette().buttonText().color(), self.palette().mid().color()] + else: + # Dark background + self.fLabelGradientColor1 = QColor(0, 0, 0, 255) + self.fLabelGradientColor2 = QColor(0, 0, 0, 0) + self.fLabelGradientColorT = [Qt.white, Qt.darkGray] + + self.fLabelGradient = QLinearGradient(0, 0, 0, 1) + self.fLabelGradient.setColorAt(0.0, self.fLabelGradientColor1) + self.fLabelGradient.setColorAt(0.6, self.fLabelGradientColor1) + self.fLabelGradient.setColorAt(1.0, self.fLabelGradientColor2) + + self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) + + self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL + self.fCustomPaintColor = QColor(0xff, 0xff, 0xff) + + # Fake internal value, custom precision + QDial.setMinimum(self, 0) + QDial.setMaximum(self, self.fPrecision) + QDial.setValue(self, 0) + + self.valueChanged.connect(self.slot_valueChanged) + + def forceWhiteLabelGradientText(self): + self.fLabelGradientColor1 = QColor(0, 0, 0, 255) + self.fLabelGradientColor2 = QColor(0, 0, 0, 0) + self.fLabelGradientColorT = [Qt.white, Qt.darkGray] + + def setLabelColor(self, enabled, disabled): + self.fLabelGradientColor1 = QColor(0, 0, 0, 255) + self.fLabelGradientColor2 = QColor(0, 0, 0, 0) + self.fLabelGradientColorT = [enabled, disabled] + + def getIndex(self): + return self.fIndex + + def setIndex(self, index): + self.fIndex = index + + def setPrecision(self, value, isInteger): + self.fPrecision = value + self.fIsInteger = isInteger + QDial.setMaximum(self, int(value)) + + def setMinimum(self, value): + self.fMinimum = value + + def setMaximum(self, value): + self.fMaximum = value + + def rvalue(self): + return self.fRealValue + + def setValue(self, value, emitSignal=False): + if self.fRealValue == value or isnan(value): + return + + if value <= self.fMinimum: + qtValue = 0 + self.fRealValue = self.fMinimum + + elif value >= self.fMaximum: + qtValue = int(self.fPrecision) + self.fRealValue = self.fMaximum + + else: + qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision) + self.fRealValue = value + + # Block change signal, we'll handle it ourselves + self.blockSignals(True) + QDial.setValue(self, qtValue) + self.blockSignals(False) + + if emitSignal: + self.realValueChanged.emit(self.fRealValue) + + def setCustomPaintMode(self, paintMode): + if self.fCustomPaintMode == paintMode: + return + + self.fCustomPaintMode = paintMode + self.update() + + def setCustomPaintColor(self, color): + if self.fCustomPaintColor == color: + return + + self.fCustomPaintColor = color + self.update() + + def setLabel(self, label): + if self.fLabel == label: + return + + self.fLabel = label + self.updateSizes() + self.update() + + @pyqtSlot(int) + def slot_valueChanged(self, value): + self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum + self.realValueChanged.emit(self.fRealValue) + + def enterEvent(self, event): + self.fIsHovered = True + if self.fHoverStep == self.HOVER_MIN: + self.fHoverStep = self.HOVER_MIN + 1 + QDial.enterEvent(self, event) + + def leaveEvent(self, event): + self.fIsHovered = False + if self.fHoverStep == self.HOVER_MAX: + self.fHoverStep = self.HOVER_MAX - 1 + QDial.leaveEvent(self, event) + + def mousePressEvent(self, event): + if self.fDialMode == self.MODE_DEFAULT: + QDial.mousePressEvent(self, event) + return + + if event.button() == Qt.LeftButton: + self.fIsPressed = True + self.fLastDragPos = event.pos() + self.fLastDragValue = self.fRealValue + self.dragStateChanged.emit(True) + + def mouseMoveEvent(self, event): + if self.fDialMode == self.MODE_DEFAULT: + QDial.mouseMoveEvent(self, event) + return + + if not self.fIsPressed: + return + + diff = (self.fMaximum - self.fMinimum) / 4.0 + pos = event.pos() + dx = diff * float(pos.x() - self.fLastDragPos.x()) / self.width() + dy = diff * float(pos.y() - self.fLastDragPos.y()) / self.height() + value = self.fLastDragValue + dx - dy + + if value < self.fMinimum: + value = self.fMinimum + elif value > self.fMaximum: + value = self.fMaximum + elif self.fIsInteger: + value = float(round(value)) + + self.setValue(value, True) + + def mouseReleaseEvent(self, event): + if self.fDialMode == self.MODE_DEFAULT: + QDial.mouseReleaseEvent(self, event) + return + + if self.fIsPressed: + self.fIsPressed = False + self.dragStateChanged.emit(False) + + def paintEvent(self, event): + painter = QPainter(self) + event.accept() + + painter.save() + painter.setRenderHint(QPainter.Antialiasing, True) + + if self.fLabel: + if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: + painter.setPen(self.fLabelGradientColor2) + painter.setBrush(self.fLabelGradient) + painter.drawRect(self.fLabelGradientRect) + + painter.setFont(self.fLabelFont) + painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1]) + painter.drawText(self.fLabelPos, self.fLabel) + + self.paintDial(painter) + + painter.restore() + + def resizeEvent(self, event): + QDial.resizeEvent(self, event) + self.updateSizes() + +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/digitalpeakmeter.py b/source/frontend/widgets/digitalpeakmeter.py index 4343739e8..533429cc6 100644 --- a/source/frontend/widgets/digitalpeakmeter.py +++ b/source/frontend/widgets/digitalpeakmeter.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Digital Peak Meter, a custom Qt widget -# Copyright (C) 2011-2019 Filipe Coelho +# Copyright (C) 2011-2022 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -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_()) - -# ------------------------------------------------------------------------------------------------------------ +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/draggablegraphicsview.py b/source/frontend/widgets/draggablegraphicsview.py index 212c20e19..b4ba14df5 100644 --- a/source/frontend/widgets/draggablegraphicsview.py +++ b/source/frontend/widgets/draggablegraphicsview.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Middle-click draggable QGraphicsView -# Copyright (C) 2016-2020 Filipe Coelho +# Copyright (C) 2016-2022 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -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) - # -------------------------------------------------------------------------------------------------------- +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/ledbutton.py b/source/frontend/widgets/ledbutton.py index 90e14fcb1..766074e2f 100644 --- a/source/frontend/widgets/ledbutton.py +++ b/source/frontend/widgets/ledbutton.py @@ -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 + +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/paramspinbox.py b/source/frontend/widgets/paramspinbox.py index 3fc715fb3..3cfaa193d 100644 --- a/source/frontend/widgets/paramspinbox.py +++ b/source/frontend/widgets/paramspinbox.py @@ -76,7 +76,9 @@ class CustomInputDialog(QDialog): text = "" for scalePoint in scalePoints: valuestr = ("%i" if decimals == 0 else "%f") % scalePoint['value'] - text += "" % (valuestr, scalePoint['label']) + text += "" + text += f"" + text += "" text += "
%s - %s
{valuestr} - {scalePoint['label']}
" 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) diff --git a/source/frontend/widgets/pianoroll.py b/source/frontend/widgets/pianoroll.py index 386014f65..3c09426e2 100755 --- a/source/frontend/widgets/pianoroll.py +++ b/source/frontend/widgets/pianoroll.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # A piano roll viewer/editor -# Copyright (C) 2012-2021 Filipe Coelho +# Copyright (C) 2012-2022 Filipe Coelho # 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 diff --git a/source/frontend/widgets/pixmapbutton.py b/source/frontend/widgets/pixmapbutton.py deleted file mode 100644 index 5474b6ba3..000000000 --- a/source/frontend/widgets/pixmapbutton.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Pixmap Button, a custom Qt widget -# Copyright (C) 2013-2019 Filipe Coelho -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of -# the License, or any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# For a full copy of the GNU General Public License see the doc/GPL.txt file. - -# ------------------------------------------------------------------------------------------------------------ -# Imports (Global) - -from 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() diff --git a/source/frontend/widgets/pixmapdial.py b/source/frontend/widgets/pixmapdial.py index b6909a55f..9ee80b159 100644 --- a/source/frontend/widgets/pixmapdial.py +++ b/source/frontend/widgets/pixmapdial.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Pixmap Dial, a custom Qt widget -# Copyright (C) 2011-2019 Filipe Coelho +# Copyright (C) 2011-2022 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -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_()) +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/pixmapkeyboard.py b/source/frontend/widgets/pixmapkeyboard.py index 12c71b5e7..7218ebb8d 100755 --- a/source/frontend/widgets/pixmapkeyboard.py +++ b/source/frontend/widgets/pixmapkeyboard.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Pixmap Keyboard, a custom Qt widget -# Copyright (C) 2011-2020 Filipe Coelho +# Copyright (C) 2011-2022 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -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_()) +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/scalablebutton.py b/source/frontend/widgets/scalablebutton.py index 0b7bce266..d835ff880 100644 --- a/source/frontend/widgets/scalablebutton.py +++ b/source/frontend/widgets/scalablebutton.py @@ -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) + +# --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/scalabledial.py b/source/frontend/widgets/scalabledial.py index cd3e18e8d..1aebcd4e4 100644 --- a/source/frontend/widgets/scalabledial.py +++ b/source/frontend/widgets/scalabledial.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # Scalable Dial, a custom Qt widget -# Copyright (C) 2011-2020 Filipe Coelho +# Copyright (C) 2011-2022 Filipe Coelho # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -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_()) +# ---------------------------------------------------------------------------------------------------------------------