Browse Source

Start python level automated tests, WIP

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.5.0
falkTX 2 years ago
parent
commit
121d7f617b
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
14 changed files with 437 additions and 758 deletions
  1. +11
    -0
      source/frontend/Makefile
  2. +3
    -19
      source/frontend/widgets/canvaspreviewframe.py
  3. +3
    -2
      source/frontend/widgets/collapsablewidget.py
  4. +278
    -0
      source/frontend/widgets/commondial.py
  5. +45
    -51
      source/frontend/widgets/digitalpeakmeter.py
  6. +14
    -13
      source/frontend/widgets/draggablegraphicsview.py
  7. +9
    -7
      source/frontend/widgets/ledbutton.py
  8. +5
    -5
      source/frontend/widgets/paramspinbox.py
  9. +23
    -14
      source/frontend/widgets/pianoroll.py
  10. +0
    -99
      source/frontend/widgets/pixmapbutton.py
  11. +13
    -257
      source/frontend/widgets/pixmapdial.py
  12. +16
    -28
      source/frontend/widgets/pixmapkeyboard.py
  13. +4
    -2
      source/frontend/widgets/scalablebutton.py
  14. +13
    -261
      source/frontend/widgets/scalabledial.py

+ 11
- 0
source/frontend/Makefile View File

@@ -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)



+ 3
- 19
source/frontend/widgets/canvaspreviewframe.py View File

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

+ 3
- 2
source/frontend/widgets/collapsablewidget.py View File

@@ -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

# ------------------------------------------------------------------------------------------------------------

+ 278
- 0
source/frontend/widgets/commondial.py View File

@@ -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()

# ---------------------------------------------------------------------------------------------------------------------

+ 45
- 51
source/frontend/widgets/digitalpeakmeter.py View File

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

# ------------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------

+ 14
- 13
source/frontend/widgets/draggablegraphicsview.py View File

@@ -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)

# --------------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------------------------

+ 9
- 7
source/frontend/widgets/ledbutton.py View File

@@ -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

# ---------------------------------------------------------------------------------------------------------------------

+ 5
- 5
source/frontend/widgets/paramspinbox.py View File

@@ -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)



+ 23
- 14
source/frontend/widgets/pianoroll.py View File

@@ -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


+ 0
- 99
source/frontend/widgets/pixmapbutton.py View File

@@ -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()

+ 13
- 257
source/frontend/widgets/pixmapdial.py View File

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

+ 16
- 28
source/frontend/widgets/pixmapkeyboard.py View File

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

+ 4
- 2
source/frontend/widgets/scalablebutton.py View File

@@ -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)

# ---------------------------------------------------------------------------------------------------------------------

+ 13
- 261
source/frontend/widgets/scalabledial.py View File

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

Loading…
Cancel
Save