@@ -0,0 +1,270 @@ | |||
/* | |||
* Digital Peak Meter, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#include "digitalpeakmeter.hpp" | |||
#include <QtGui/QPainter> | |||
DigitalPeakMeter::DigitalPeakMeter(QWidget* parent) | |||
: QWidget(parent), | |||
m_paintTimer(this) | |||
{ | |||
m_channels = 0; | |||
m_orientation = VERTICAL; | |||
m_smoothMultiplier = 1; | |||
m_colorBackground = QColor("#111111"); | |||
m_gradientMeter = QLinearGradient(0, 0, 1, 1); | |||
setChannels(0); | |||
setColor(GREEN); | |||
m_paintTimer.setInterval(60); | |||
connect(&m_paintTimer, SIGNAL(timeout()), this, SLOT(update())); | |||
m_paintTimer.start(); | |||
} | |||
void DigitalPeakMeter::displayMeter(int meter, float level) | |||
{ | |||
Q_ASSERT(meter > 0 && meter <= m_channels); | |||
if (meter <= 0 || meter > m_channels) | |||
return qCritical("DigitalPeakMeter::displayMeter(%i, %f) - invalid meter number", meter, level); | |||
if (level < 0.0f) | |||
level = -level; | |||
else if (level > 1.0f) | |||
level = 1.0f; | |||
m_channelsData[meter-1] = level; | |||
} | |||
void DigitalPeakMeter::setChannels(int channels) | |||
{ | |||
Q_ASSERT(channels >= 0); | |||
if (channels < 0) | |||
return qCritical("DigitalPeakMeter::setChannels(%i) - 'channels' must be a positive integer", channels); | |||
m_channels = channels; | |||
m_channelsData.clear(); | |||
m_lastValueData.clear(); | |||
for (int i=0; i < channels; i++) | |||
{ | |||
m_channelsData.append(0.0f); | |||
m_lastValueData.append(0.0f); | |||
} | |||
} | |||
void DigitalPeakMeter::setColor(Color color) | |||
{ | |||
if (color == GREEN) | |||
{ | |||
m_colorBase = QColor(93, 231, 61); | |||
m_colorBaseT = QColor(15, 110, 15, 100); | |||
} | |||
else if (color == BLUE) | |||
{ | |||
m_colorBase = QColor(82, 238, 248); | |||
m_colorBaseT = QColor(15, 15, 110, 100); | |||
} | |||
else | |||
return qCritical("DigitalPeakMeter::setColor(%i) - invalid color", color); | |||
setOrientation(m_orientation); | |||
} | |||
void DigitalPeakMeter::setOrientation(Orientation orientation) | |||
{ | |||
m_orientation = orientation; | |||
if (m_orientation == HORIZONTAL) | |||
{ | |||
m_gradientMeter.setColorAt(0.0f, m_colorBase); | |||
m_gradientMeter.setColorAt(0.2f, m_colorBase); | |||
m_gradientMeter.setColorAt(0.4f, m_colorBase); | |||
m_gradientMeter.setColorAt(0.6f, m_colorBase); | |||
m_gradientMeter.setColorAt(0.8f, Qt::yellow); | |||
m_gradientMeter.setColorAt(1.0f, Qt::red); | |||
} | |||
else if (m_orientation == VERTICAL) | |||
{ | |||
m_gradientMeter.setColorAt(0.0f, Qt::red); | |||
m_gradientMeter.setColorAt(0.2f, Qt::yellow); | |||
m_gradientMeter.setColorAt(0.4f, m_colorBase); | |||
m_gradientMeter.setColorAt(0.6f, m_colorBase); | |||
m_gradientMeter.setColorAt(0.8f, m_colorBase); | |||
m_gradientMeter.setColorAt(1.0f, m_colorBase); | |||
} | |||
else | |||
return qCritical("DigitalPeakMeter::setOrientation(%i) - invalid orientation", orientation); | |||
updateSizes(); | |||
} | |||
void DigitalPeakMeter::setRefreshRate(int rate) | |||
{ | |||
Q_ASSERT(rate > 0); | |||
m_paintTimer.stop(); | |||
m_paintTimer.setInterval(rate); | |||
m_paintTimer.start(); | |||
} | |||
void DigitalPeakMeter::setSmoothRelease(int value) | |||
{ | |||
Q_ASSERT(value >= 0 && value <= 5); | |||
if (value < 0) | |||
value = 0; | |||
else if (value > 5) | |||
value = 5; | |||
m_smoothMultiplier = value; | |||
} | |||
QSize DigitalPeakMeter::minimumSizeHint() const | |||
{ | |||
return QSize(30, 30); | |||
} | |||
QSize DigitalPeakMeter::sizeHint() const | |||
{ | |||
return QSize(m_width, m_height); | |||
} | |||
void DigitalPeakMeter::updateSizes() | |||
{ | |||
m_width = width(); | |||
m_height = height(); | |||
m_sizeMeter = 0; | |||
if (m_orientation == HORIZONTAL) | |||
{ | |||
m_gradientMeter.setFinalStop(m_width, 0); | |||
if (m_channels > 0) | |||
m_sizeMeter = m_height/m_channels; | |||
} | |||
else if (m_orientation == VERTICAL) | |||
{ | |||
m_gradientMeter.setFinalStop(0, m_height); | |||
if (m_channels > 0) | |||
m_sizeMeter = m_width/m_channels; | |||
} | |||
} | |||
void DigitalPeakMeter::paintEvent(QPaintEvent*) | |||
{ | |||
QPainter painter(this); | |||
painter.setPen(Qt::black); | |||
painter.setBrush(Qt::black); | |||
painter.drawRect(0, 0, m_width, m_height); | |||
int meterX = 0; | |||
painter.setPen(m_colorBackground); | |||
painter.setBrush(m_gradientMeter); | |||
for (int i=0; i < m_channels; i++) | |||
{ | |||
float value, level = m_channelsData[i]; | |||
if (level == m_lastValueData[i]) | |||
continue; | |||
if (m_orientation == HORIZONTAL) | |||
value = level * m_width; | |||
else if (m_orientation == VERTICAL) | |||
value = float(m_height) - (level * m_height); | |||
else | |||
value = 0.0f; | |||
if (value < 0.0f) | |||
value = 0.0f; | |||
else if (m_smoothMultiplier > 0) | |||
value = (m_lastValueData[i] * m_smoothMultiplier + value) / (m_smoothMultiplier + 1); | |||
if (m_orientation == HORIZONTAL) | |||
painter.drawRect(0, meterX, value, m_sizeMeter); | |||
else if (m_orientation == VERTICAL) | |||
painter.drawRect(meterX, value, m_sizeMeter, m_height); | |||
meterX += m_sizeMeter; | |||
m_lastValueData[i] = value; | |||
} | |||
painter.setBrush(QColor(0, 0, 0, 0)); | |||
if (m_orientation == HORIZONTAL) | |||
{ | |||
// Variables | |||
int lsmall = m_width; | |||
int lfull = m_height - 1; | |||
// Base | |||
painter.setPen(m_colorBaseT); | |||
painter.drawLine(lsmall * 0.25f, 2, lsmall * 0.25f, lfull-2); | |||
painter.drawLine(lsmall * 0.50f, 2, lsmall * 0.50f, lfull-2); | |||
// Yellow | |||
painter.setPen(QColor(110, 110, 15, 100)); | |||
painter.drawLine(lsmall * 0.70f, 2, lsmall * 0.70f, lfull-2); | |||
painter.drawLine(lsmall * 0.83f, 2, lsmall * 0.83f, lfull-2); | |||
// Orange | |||
painter.setPen(QColor(180, 110, 15, 100)); | |||
painter.drawLine(lsmall * 0.90f, 2, lsmall * 0.90f, lfull-2); | |||
// Red | |||
painter.setPen(QColor(110, 15, 15, 100)); | |||
painter.drawLine(lsmall * 0.96f, 2, lsmall * 0.96f, lfull-2); | |||
} | |||
else if (m_orientation == VERTICAL) | |||
{ | |||
// Variables | |||
int lsmall = m_height; | |||
int lfull = m_width - 1; | |||
// Base | |||
painter.setPen(m_colorBaseT); | |||
painter.drawLine(2, lsmall - (lsmall * 0.25f), lfull-2, lsmall - (lsmall * 0.25f)); | |||
painter.drawLine(2, lsmall - (lsmall * 0.50f), lfull-2, lsmall - (lsmall * 0.50f)); | |||
// Yellow | |||
painter.setPen(QColor(110, 110, 15, 100)); | |||
painter.drawLine(2, lsmall - (lsmall * 0.70f), lfull-2, lsmall - (lsmall * 0.70f)); | |||
painter.drawLine(2, lsmall - (lsmall * 0.83f), lfull-2, lsmall - (lsmall * 0.83f)); | |||
// Orange | |||
painter.setPen(QColor(180, 110, 15, 100)); | |||
painter.drawLine(2, lsmall - (lsmall * 0.90f), lfull-2, lsmall - (lsmall * 0.90f)); | |||
// Red | |||
painter.setPen(QColor(110, 15, 15, 100)); | |||
painter.drawLine(2, lsmall - (lsmall * 0.96f), lfull-2, lsmall - (lsmall * 0.96f)); | |||
} | |||
} | |||
void DigitalPeakMeter::resizeEvent(QResizeEvent* event) | |||
{ | |||
updateSizes(); | |||
QWidget::resizeEvent(event); | |||
} |
@@ -0,0 +1,73 @@ | |||
/* | |||
* Digital Peak Meter, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#ifndef DIGITALPEAKMETER_HPP | |||
#define DIGITALPEAKMETER_HPP | |||
#include <QtCore/QTimer> | |||
#include <QtGui/QWidget> | |||
class DigitalPeakMeter : public QWidget | |||
{ | |||
public: | |||
enum Orientation { | |||
HORIZONTAL = 1, | |||
VERTICAL = 2 | |||
}; | |||
enum Color { | |||
GREEN = 1, | |||
BLUE = 2 | |||
}; | |||
DigitalPeakMeter(QWidget* parent); | |||
void displayMeter(int meter, float level); | |||
void setChannels(int channels); | |||
void setColor(Color color); | |||
void setOrientation(Orientation orientation); | |||
void setRefreshRate(int rate); | |||
void setSmoothRelease(int value); | |||
QSize minimumSizeHint() const; | |||
QSize sizeHint() const; | |||
protected: | |||
void updateSizes(); | |||
void paintEvent(QPaintEvent* event); | |||
void resizeEvent(QResizeEvent* event); | |||
private: | |||
int m_channels; | |||
int m_smoothMultiplier; | |||
int m_width, m_height, m_sizeMeter; | |||
Orientation m_orientation; | |||
QColor m_colorBackground; | |||
QLinearGradient m_gradientMeter; | |||
QColor m_colorBase; | |||
QColor m_colorBaseT; | |||
QList<float> m_channelsData; | |||
QList<float> m_lastValueData; | |||
QTimer m_paintTimer; | |||
}; | |||
#endif // DIGITALPEAKMETER_HPP |
@@ -0,0 +1,233 @@ | |||
#!/usr/bin/env python3 | |||
# -*- coding: utf-8 -*- | |||
# Digital Peak Meter, a custom Qt4 widget | |||
# Copyright (C) 2011-2012 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 COPYING file | |||
# Imports (Global) | |||
from PyQt4.QtCore import qCritical, Qt, QTimer, QSize | |||
from PyQt4.QtGui import QColor, QLinearGradient, QPainter, QWidget | |||
# Widget Class | |||
class DigitalPeakMeter(QWidget): | |||
# enum Orientation | |||
HORIZONTAL = 1 | |||
VERTICAL = 2 | |||
# enum Color | |||
GREEN = 1 | |||
BLUE = 2 | |||
def __init__(self, parent): | |||
QWidget.__init__(self, parent) | |||
self.m_channels = 0 | |||
self.m_orientation = self.VERTICAL | |||
self.m_smoothMultiplier = 1 | |||
self.m_colorBackground = QColor("#111111") | |||
self.m_gradientMeter = QLinearGradient(0, 0, 1, 1) | |||
self.setChannels(0) | |||
self.setColor(self.GREEN) | |||
self.m_paintTimer = QTimer(self) | |||
self.m_paintTimer.setInterval(60) | |||
self.m_paintTimer.timeout.connect(self.update) | |||
self.m_paintTimer.start() | |||
def displayMeter(self, meter, level): | |||
if meter <= 0 or meter > self.m_channels: | |||
return qCritical("DigitalPeakMeter::displayMeter(%i, %f) - invalid meter number" % (meter, level)) | |||
if level < 0.0: | |||
level = -level | |||
elif level > 1.0: | |||
level = 1.0 | |||
self.m_channelsData[meter-1] = level | |||
def setChannels(self, channels): | |||
if (channels < 0): | |||
return qCritical("DigitalPeakMeter::setChannels(%i) - 'channels' must be a positive integer" % channels) | |||
self.m_channels = channels | |||
self.m_channelsData = [] | |||
self.m_lastValueData = [] | |||
for x in range(channels): | |||
self.m_channelsData.append(0.0) | |||
self.m_lastValueData.append(0.0) | |||
def setColor(self, color): | |||
if color == self.GREEN: | |||
self.m_colorBase = QColor(93, 231, 61) | |||
self.m_colorBaseT = QColor(15, 110, 15, 100) | |||
elif color == self.BLUE: | |||
self.m_colorBase = QColor(82, 238, 248) | |||
self.m_colorBaseT = QColor(15, 15, 110, 100) | |||
else: | |||
return qCritical("DigitalPeakMeter::setColor(%i) - invalid color" % color) | |||
self.setOrientation(self.m_orientation) | |||
def setOrientation(self, orientation): | |||
self.m_orientation = orientation | |||
if self.m_orientation == self.HORIZONTAL: | |||
self.m_gradientMeter.setColorAt(0.0, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(0.2, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(0.4, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(0.6, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(0.8, Qt.yellow) | |||
self.m_gradientMeter.setColorAt(1.0, Qt.red) | |||
elif self.m_orientation == self.VERTICAL: | |||
self.m_gradientMeter.setColorAt(0.0, Qt.red) | |||
self.m_gradientMeter.setColorAt(0.2, Qt.yellow) | |||
self.m_gradientMeter.setColorAt(0.4, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(0.6, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(0.8, self.m_colorBase) | |||
self.m_gradientMeter.setColorAt(1.0, self.m_colorBase) | |||
else: | |||
return qCritical("DigitalPeakMeter::setOrientation(%i) - invalid orientation" % orientation) | |||
self.updateSizes() | |||
def setRefreshRate(self, rate): | |||
self.m_paintTimer.stop() | |||
self.m_paintTimer.setInterval(rate) | |||
self.m_paintTimer.start() | |||
def setSmoothRelease(self, value): | |||
if value < 0: | |||
value = 0 | |||
elif value > 5: | |||
value = 5 | |||
self.m_smoothMultiplier = value | |||
def minimumSizeHint(self): | |||
return QSize(30, 30) | |||
def sizeHint(self): | |||
return QSize(self.m_width, self.m_height) | |||
def updateSizes(self): | |||
self.m_width = self.width() | |||
self.m_height = self.height() | |||
self.m_sizeMeter = 0 | |||
if self.m_orientation == self.HORIZONTAL: | |||
self.m_gradientMeter.setFinalStop(self.m_width, 0) | |||
if self.m_channels > 0: | |||
self.m_sizeMeter = self.m_height / self.m_channels | |||
elif self.m_orientation == self.VERTICAL: | |||
self.m_gradientMeter.setFinalStop(0, self.m_height) | |||
if self.m_channels > 0: | |||
self.m_sizeMeter = self.m_width / self.m_channels | |||
def paintEvent(self, event): | |||
painter = QPainter(self) | |||
painter.setPen(Qt.black) | |||
painter.setBrush(Qt.black) | |||
painter.drawRect(0, 0, self.m_width, self.m_height) | |||
meterX = 0 | |||
painter.setPen(self.m_colorBackground) | |||
painter.setBrush(self.m_gradientMeter) | |||
for i in range(self.m_channels): | |||
level = self.m_channelsData[i] | |||
if level == self.m_lastValueData[i]: | |||
continue | |||
if self.m_orientation == self.HORIZONTAL: | |||
value = level * self.m_width | |||
elif self.m_orientation == self.VERTICAL: | |||
value = float(self.m_height) - (level * self.m_height) | |||
else: | |||
value = 0.0 | |||
if value < 0.0: | |||
value = 0.0 | |||
elif self.m_smoothMultiplier > 0: | |||
value = (self.m_lastValueData[i] * self.m_smoothMultiplier + value) / (self.m_smoothMultiplier + 1) | |||
if self.m_orientation == self.HORIZONTAL: | |||
painter.drawRect(0, meterX, value, self.m_sizeMeter) | |||
elif self.m_orientation == self.VERTICAL: | |||
painter.drawRect(meterX, value, self.m_sizeMeter, self.m_height) | |||
meterX += self.m_sizeMeter | |||
self.m_lastValueData[i] = value | |||
painter.setBrush(QColor(0, 0, 0, 0)) | |||
if self.m_orientation == self.HORIZONTAL: | |||
# Variables | |||
lsmall = self.m_width | |||
lfull = self.m_height - 1 | |||
# Base | |||
painter.setPen(self.m_colorBaseT) | |||
painter.drawLine(lsmall * 0.25, 2, lsmall * 0.25, lfull-2) | |||
painter.drawLine(lsmall * 0.50, 2, lsmall * 0.50, lfull-2) | |||
# Yellow | |||
painter.setPen(QColor(110, 110, 15, 100)) | |||
painter.drawLine(lsmall * 0.70, 2, lsmall * 0.70, lfull-2) | |||
painter.drawLine(lsmall * 0.83, 2, lsmall * 0.83, lfull-2) | |||
# Orange | |||
painter.setPen(QColor(180, 110, 15, 100)) | |||
painter.drawLine(lsmall * 0.90, 2, lsmall * 0.90, lfull-2) | |||
# Red | |||
painter.setPen(QColor(110, 15, 15, 100)) | |||
painter.drawLine(lsmall * 0.96, 2, lsmall * 0.96, lfull-2) | |||
elif self.m_orientation == self.VERTICAL: | |||
# Variables | |||
lsmall = self.m_height | |||
lfull = self.m_width - 1 | |||
# Base | |||
painter.setPen(self.m_colorBaseT) | |||
painter.drawLine(2, lsmall - (lsmall * 0.25), lfull-2, lsmall - (lsmall * 0.25)) | |||
painter.drawLine(2, lsmall - (lsmall * 0.50), lfull-2, lsmall - (lsmall * 0.50)) | |||
# Yellow | |||
painter.setPen(QColor(110, 110, 15, 100)) | |||
painter.drawLine(2, lsmall - (lsmall * 0.70), lfull-2, lsmall - (lsmall * 0.70)) | |||
painter.drawLine(2, lsmall - (lsmall * 0.82), lfull-2, lsmall - (lsmall * 0.82)) | |||
# Orange | |||
painter.setPen(QColor(180, 110, 15, 100)) | |||
painter.drawLine(2, lsmall - (lsmall * 0.90), lfull-2, lsmall - (lsmall * 0.90)) | |||
# Red | |||
painter.setPen(QColor(110, 15, 15, 100)) | |||
painter.drawLine(2, lsmall - (lsmall * 0.96), lfull-2, lsmall - (lsmall * 0.96)) | |||
def resizeEvent(self, event): | |||
self.updateSizes() | |||
QWidget.resizeEvent(self, event) |
@@ -0,0 +1,92 @@ | |||
/* | |||
* Pixmap Button, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#include "ledbutton.hpp" | |||
#include <QtGui/QPainter> | |||
LEDButton::LEDButton(QWidget* parent): | |||
QPushButton(parent) | |||
{ | |||
m_pixmap_rect = QRectF(0, 0, 0, 0); | |||
setCheckable(true); | |||
setText(""); | |||
setColor(BLUE); | |||
} | |||
LEDButton::~LEDButton() | |||
{ | |||
} | |||
void LEDButton::setColor(Color color) | |||
{ | |||
m_color = color; | |||
int size; | |||
if (1) //color in (self.BLUE, self.GREEN, self.RED, self.YELLOW): | |||
size = 14; | |||
else if (color == BIG_RED) | |||
size = 64; | |||
else | |||
return qCritical("LEDButton::setColor(%i) - Invalid color", color); | |||
setPixmapSize(size); | |||
} | |||
void LEDButton::setPixmapSize(int size) | |||
{ | |||
m_pixmap_rect = QRectF(0, 0, size, size); | |||
setMinimumWidth(size); | |||
setMaximumWidth(size); | |||
setMinimumHeight(size); | |||
setMaximumHeight(size); | |||
} | |||
void LEDButton::paintEvent(QPaintEvent*) | |||
{ | |||
QPainter painter(this); | |||
if (isChecked()) | |||
{ | |||
if (m_color == BLUE) | |||
m_pixmap.load(":/bitmaps/led_blue.png"); | |||
else if (m_color == GREEN) | |||
m_pixmap.load(":/bitmaps/led_green.png"); | |||
else if (m_color == RED) | |||
m_pixmap.load(":/bitmaps/led_red.png"); | |||
else if (m_color == YELLOW) | |||
m_pixmap.load(":/bitmaps/led_yellow.png"); | |||
else if (m_color == BIG_RED) | |||
m_pixmap.load(":/bitmaps/led-big_on.png"); | |||
else | |||
return; | |||
} | |||
else | |||
{ | |||
if (1) //self.m_color in (self.BLUE, self.GREEN, self.RED, self.YELLOW): | |||
m_pixmap.load(":/bitmaps/led_off.png"); | |||
else if (m_color == BIG_RED) | |||
m_pixmap.load(":/bitmaps/led-big_off.png"); | |||
else | |||
return; | |||
} | |||
painter.drawPixmap(m_pixmap_rect, m_pixmap, m_pixmap_rect); | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Pixmap Button, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#ifndef LEDBUTTON_HPP | |||
#define LEDBUTTON_HPP | |||
#include <QtGui/QPixmap> | |||
#include <QtGui/QPushButton> | |||
class LEDButton : public QPushButton | |||
{ | |||
Q_OBJECT | |||
public: | |||
enum Color { | |||
BLUE = 1, | |||
GREEN = 2, | |||
RED = 3, | |||
YELLOW = 4, | |||
BIG_RED = 5 | |||
}; | |||
LEDButton(QWidget* parent); | |||
~LEDButton(); | |||
void setColor(Color color); | |||
protected: | |||
void setPixmapSize(int size); | |||
void paintEvent(QPaintEvent* event); | |||
private: | |||
QPixmap m_pixmap; | |||
QRectF m_pixmap_rect; | |||
Color m_color; | |||
}; | |||
#endif // LEDBUTTON_HPP |
@@ -0,0 +1,86 @@ | |||
#!/usr/bin/env python3 | |||
# -*- coding: utf-8 -*- | |||
# Pixmap Button, a custom Qt4 widget | |||
# Copyright (C) 2011-2012 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 COPYING file | |||
# Imports (Global) | |||
from PyQt4.QtCore import qCritical, QRectF | |||
from PyQt4.QtGui import QPainter, QPixmap, QPushButton | |||
# Widget Class | |||
class LEDButton(QPushButton): | |||
BLUE = 1 | |||
GREEN = 2 | |||
RED = 3 | |||
YELLOW = 4 | |||
BIG_RED = 5 | |||
def __init__(self, parent): | |||
QPushButton.__init__(self, parent) | |||
self.m_pixmap = QPixmap() | |||
self.m_pixmap_rect = QRectF(0, 0, 0, 0) | |||
self.setCheckable(True) | |||
self.setText("") | |||
self.setColor(self.BLUE) | |||
def setColor(self, color): | |||
self.m_color = color | |||
if color in (self.BLUE, self.GREEN, self.RED, self.YELLOW): | |||
size = 14 | |||
elif color == self.BIG_RED: | |||
size = 32 | |||
else: | |||
return qCritical("LEDButton::setColor(%i) - Invalid color" % color) | |||
self.setPixmapSize(size) | |||
def setPixmapSize(self, size): | |||
self.m_pixmap_rect = QRectF(0, 0, size, size) | |||
self.setMinimumWidth(size) | |||
self.setMaximumWidth(size) | |||
self.setMinimumHeight(size) | |||
self.setMaximumHeight(size) | |||
def paintEvent(self, event): | |||
painter = QPainter(self) | |||
if self.isChecked(): | |||
if self.m_color == self.BLUE: | |||
self.m_pixmap.load(":/bitmaps/led_blue.png") | |||
elif self.m_color == self.GREEN: | |||
self.m_pixmap.load(":/bitmaps/led_green.png") | |||
elif self.m_color == self.RED: | |||
self.m_pixmap.load(":/bitmaps/led_red.png") | |||
elif self.m_color == self.YELLOW: | |||
self.m_pixmap.load(":/bitmaps/led_yellow.png") | |||
elif self.m_color == self.BIG_RED: | |||
self.m_pixmap.load(":/bitmaps/led-big_on.png") | |||
else: | |||
return | |||
else: | |||
if self.m_color in (self.BLUE, self.GREEN, self.RED, self.YELLOW): | |||
self.m_pixmap.load(":/bitmaps/led_off.png") | |||
elif self.m_color == self.BIG_RED: | |||
self.m_pixmap.load(":/bitmaps/led-big_off.png") | |||
else: | |||
return | |||
painter.drawPixmap(self.m_pixmap_rect, self.m_pixmap, self.m_pixmap_rect) |
@@ -0,0 +1,118 @@ | |||
/* | |||
* Parameter Progress-Bar, a custom Qt4 widget | |||
* Copyright (C) 2012 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 COPYING file | |||
*/ | |||
#include "paramspinbox.h" | |||
#include <QtGui/QMouseEvent> | |||
ParamProgressBar::ParamProgressBar(QWidget* parent) | |||
: QProgressBar(parent) | |||
{ | |||
m_leftClickDown = false; | |||
m_minimum = 0.0f; | |||
m_maximum = 1.0f; | |||
m_rvalue = 0.0f; | |||
m_textCall = nullptr; | |||
setMinimum(0); | |||
setMaximum(1000); | |||
setValue(0); | |||
setFormat("(none)"); | |||
} | |||
void ParamProgressBar::set_minimum(float value) | |||
{ | |||
m_minimum = value; | |||
} | |||
void ParamProgressBar::set_maximum(float value) | |||
{ | |||
m_maximum = value; | |||
} | |||
void ParamProgressBar::set_value(float value) | |||
{ | |||
m_rvalue = value; | |||
float vper = (value - m_minimum) / (m_maximum - m_minimum); | |||
setValue(vper * 1000); | |||
} | |||
void ParamProgressBar::set_label(QString label) | |||
{ | |||
m_label = label; | |||
if (m_label == "(coef)") | |||
{ | |||
m_label = ""; | |||
m_preLabel = "*"; | |||
} | |||
} | |||
void ParamProgressBar::set_text_call(TextCallback* textCall) | |||
{ | |||
m_textCall = textCall; | |||
} | |||
void ParamProgressBar::handleMouseEventPos(const QPoint& pos) | |||
{ | |||
float xper = float(pos.x()) / width(); | |||
float value = xper * (m_maximum - m_minimum) + m_minimum; | |||
if (value < m_minimum) | |||
value = m_minimum; | |||
else if (value > m_maximum) | |||
value = m_maximum; | |||
emit valueChangedFromBar(value); | |||
} | |||
void ParamProgressBar::mousePressEvent(QMouseEvent* event) | |||
{ | |||
if (event->button() == Qt::LeftButton) | |||
{ | |||
handleMouseEventPos(event->pos()); | |||
m_leftClickDown = true; | |||
} | |||
else | |||
m_leftClickDown = false; | |||
QProgressBar::mousePressEvent(event); | |||
} | |||
void ParamProgressBar::mouseMoveEvent(QMouseEvent* event) | |||
{ | |||
if (m_leftClickDown) | |||
handleMouseEventPos(event->pos()); | |||
QProgressBar::mouseMoveEvent(event); | |||
} | |||
void ParamProgressBar::mouseReleaseEvent(QMouseEvent* event) | |||
{ | |||
m_leftClickDown = false; | |||
QProgressBar::mouseReleaseEvent(event); | |||
} | |||
void ParamProgressBar::paintEvent(QPaintEvent* event) | |||
{ | |||
if (m_textCall) | |||
setFormat(QString("%1 %2 %3").arg(m_preLabel).arg(m_textCall->textCallBack()).arg(m_label)); | |||
else | |||
setFormat(QString("%1 %2 %3").arg(m_preLabel).arg(m_rvalue).arg(m_label)); | |||
QProgressBar::paintEvent(event); | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* Parameter Progress-Bar, a custom Qt4 widget | |||
* Copyright (C) 2012 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 COPYING file | |||
*/ | |||
#ifndef PARAMPROGRESSBAR_H | |||
#define PARAMPROGRESSBAR_H | |||
#include <QtGui/QProgressBar> | |||
class TextCallback | |||
{ | |||
public: | |||
virtual ~TextCallback() {} | |||
virtual const char* textCallBack() = 0; | |||
}; | |||
class ParamProgressBar : public QProgressBar | |||
{ | |||
Q_OBJECT | |||
public: | |||
ParamProgressBar(QWidget* parent); | |||
void set_minimum(float value); | |||
void set_maximum(float value); | |||
void set_value(float value); | |||
void set_label(QString label); | |||
void set_text_call(TextCallback* textCall); | |||
signals: | |||
void valueChangedFromBar(float value); | |||
protected: | |||
void handleMouseEventPos(const QPoint& pos); | |||
void mousePressEvent(QMouseEvent* event); | |||
void mouseMoveEvent(QMouseEvent* event); | |||
void mouseReleaseEvent(QMouseEvent* event); | |||
void paintEvent(QPaintEvent* event); | |||
private: | |||
bool m_leftClickDown; | |||
float m_minimum; | |||
float m_maximum; | |||
float m_rvalue; | |||
QString m_label; | |||
QString m_preLabel; | |||
TextCallback* m_textCall; | |||
}; | |||
#endif // #define PARAMPROGRESSBAR_H |
@@ -0,0 +1,383 @@ | |||
#!/usr/bin/env python | |||
# -*- coding: utf-8 -*- | |||
# Imports (Global) | |||
from PyQt4.QtCore import pyqtSlot, Qt, QTimer, SIGNAL, SLOT | |||
from PyQt4.QtGui import QAbstractSpinBox, QComboBox, QCursor, QDialog, QInputDialog, QMenu, QPainter, QProgressBar, QValidator | |||
from PyQt4.QtGui import QStyleFactory | |||
from math import isnan | |||
# Imports (Custom) | |||
import ui_inputdialog_value | |||
def fix_value(value, minimum, maximum): | |||
if isnan(value): | |||
print("Parameter is NaN! - %f" % value) | |||
return minimum | |||
elif value < minimum: | |||
print("Parameter too low! - %f/%f" % (value, minimum)) | |||
return minimum | |||
elif value > maximum: | |||
print("Parameter too high! - %f/%f" % (value, maximum)) | |||
return maximum | |||
else: | |||
return value | |||
#QPlastiqueStyle = QStyleFactory.create("Plastique") | |||
# Custom InputDialog with Scale Points support | |||
class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog): | |||
def __init__(self, parent, label, current, minimum, maximum, step, scalePoints): | |||
QDialog.__init__(self, parent) | |||
self.setupUi(self) | |||
self.label.setText(label) | |||
self.doubleSpinBox.setMinimum(minimum) | |||
self.doubleSpinBox.setMaximum(maximum) | |||
self.doubleSpinBox.setValue(current) | |||
self.doubleSpinBox.setSingleStep(step) | |||
self.ret_value = current | |||
if not scalePoints: | |||
self.groupBox.setVisible(False) | |||
self.resize(200, 0) | |||
else: | |||
text = "<table>" | |||
for scalePoint in scalePoints: | |||
text += "<tr><td align='right'>%f</td><td align='left'> - %s</td></tr>" % (scalePoint['value'], scalePoint['label']) | |||
text += "</table>" | |||
self.textBrowser.setText(text) | |||
self.resize(200, 300) | |||
self.connect(self, SIGNAL("accepted()"), self.setReturnValue) | |||
def setReturnValue(self): | |||
self.ret_value = self.doubleSpinBox.value() | |||
def done(self, r): | |||
QDialog.done(self, r) | |||
self.close() | |||
# Progress-Bar used for ParamSpinBox | |||
class ParamProgressBar(QProgressBar): | |||
def __init__(self, parent): | |||
QProgressBar.__init__(self, parent) | |||
self.m_leftClickDown = False | |||
self.m_minimum = 0.0 | |||
self.m_maximum = 1.0 | |||
self.m_rvalue = 0.0 | |||
self.m_label = "" | |||
self.m_preLabel = " " | |||
self.m_textCall = None | |||
self.setMinimum(0) | |||
self.setMaximum(1000) | |||
self.setValue(0) | |||
self.setFormat("(none)") | |||
def set_minimum(self, value): | |||
self.m_minimum = value | |||
def set_maximum(self, value): | |||
self.m_maximum = value | |||
def set_value(self, value): | |||
self.m_rvalue = value | |||
vper = (value - self.m_minimum) / (self.m_maximum - self.m_minimum) | |||
self.setValue(vper * 1000) | |||
def set_label(self, label): | |||
self.m_label = label.strip() | |||
if self.m_label == "(coef)": | |||
self.m_label = "" | |||
self.m_preLabel = "*" | |||
self.update() | |||
def set_text_call(self, textCall): | |||
self.m_textCall = textCall | |||
def handleMouseEventPos(self, pos): | |||
xper = float(pos.x()) / self.width() | |||
value = xper * (self.m_maximum - self.m_minimum) + self.m_minimum | |||
if value < self.m_minimum: | |||
value = self.m_minimum | |||
elif value > self.m_maximum: | |||
value = self.m_maximum | |||
self.emit(SIGNAL("valueChangedFromBar(double)"), value) | |||
def mousePressEvent(self, event): | |||
if event.button() == Qt.LeftButton: | |||
self.handleMouseEventPos(event.pos()) | |||
self.m_leftClickDown = True | |||
else: | |||
self.m_leftClickDown = False | |||
QProgressBar.mousePressEvent(self, event) | |||
def mouseMoveEvent(self, event): | |||
if self.m_leftClickDown: | |||
self.handleMouseEventPos(event.pos()) | |||
QProgressBar.mouseMoveEvent(self, event) | |||
def mouseReleaseEvent(self, event): | |||
self.m_leftClickDown = False | |||
QProgressBar.mouseReleaseEvent(self, event) | |||
def paintEvent(self, event): | |||
if self.m_textCall: | |||
self.setFormat("%s %s %s" % (self.m_preLabel, self.m_textCall(), self.m_label)) | |||
else: | |||
self.setFormat("%s %f %s" % (self.m_preLabel, self.m_rvalue, self.m_label)) | |||
QProgressBar.paintEvent(self, event) | |||
# Special SpinBox used for parameters | |||
class ParamSpinBox(QAbstractSpinBox): | |||
def __init__(self, parent): | |||
QAbstractSpinBox.__init__(self, parent) | |||
self._minimum = 0.0 | |||
self._maximum = 1.0 | |||
self._default = 0.0 | |||
self._value = None | |||
self._step = 0.0 | |||
self._step_small = 0.0 | |||
self._step_large = 0.0 | |||
self._read_only = False | |||
self._scalepoints = None | |||
self._have_scalepoints = False | |||
self.bar = ParamProgressBar(self) | |||
self.bar.setContextMenuPolicy(Qt.NoContextMenu) | |||
self.bar.show() | |||
self.lineEdit().setVisible(False) | |||
self.connect(self.bar, SIGNAL("valueChangedFromBar(double)"), self.handleValueChangedFromBar) | |||
self.connect(self, SIGNAL("customContextMenuRequested(QPoint)"), self.showCustomMenu) | |||
QTimer.singleShot(0, self, SLOT("slot_updateBarGeometry()")) | |||
#def force_plastique_style(self): | |||
#self.setStyle(QPlastiqueStyle) | |||
def set_minimum(self, value): | |||
self._minimum = value | |||
self.bar.set_minimum(value) | |||
def set_maximum(self, value): | |||
self._maximum = value | |||
self.bar.set_maximum(value) | |||
def set_default(self, value): | |||
value = fix_value(value, self._minimum, self._maximum) | |||
self._default = value | |||
def set_value(self, value, send=True): | |||
value = fix_value(value, self._minimum, self._maximum) | |||
if self._value != value: | |||
self._value = value | |||
self.bar.set_value(value) | |||
if self._have_scalepoints: | |||
self.set_scalepoint_value(value) | |||
if send: | |||
self.emit(SIGNAL("valueChanged(double)"), value) | |||
self.update() | |||
return True | |||
else: | |||
return False | |||
def set_step(self, value): | |||
if value == 0.0: | |||
self._step = 0.001 | |||
else: | |||
self._step = value | |||
if self._step_small > value: | |||
self._step_small = value | |||
if self._step_large < value: | |||
self._step_large = value | |||
def set_step_small(self, value): | |||
if value == 0.0: | |||
self._step_small = 0.0001 | |||
elif value > self._step: | |||
self._step_small = self._step | |||
else: | |||
self._step_small = value | |||
def set_step_large(self, value): | |||
if value == 0.0: | |||
self._step_large = 0.1 | |||
elif value < self._step: | |||
self._step_large = self._step | |||
else: | |||
self._step_large = value | |||
def set_label(self, label): | |||
self.bar.set_label(label) | |||
def set_text_call(self, textCall): | |||
self.bar.set_text_call(textCall) | |||
def set_read_only(self, yesno): | |||
self.setButtonSymbols(QAbstractSpinBox.UpDownArrows if yesno else QAbstractSpinBox.NoButtons) | |||
self._read_only = yesno | |||
self.setReadOnly(yesno) | |||
def set_scalepoints(self, scalepoints, use_scalepoints): | |||
if len(scalepoints) > 0: | |||
self._scalepoints = scalepoints | |||
self._have_scalepoints = use_scalepoints | |||
if use_scalepoints: | |||
# Hide ProgressBar and create a ComboBox | |||
self.bar.close() | |||
self.box = QComboBox(self) | |||
self.box.setContextMenuPolicy(Qt.NoContextMenu) | |||
self.box.show() | |||
self.slot_updateBarGeometry() | |||
for scalepoint in scalepoints: | |||
self.box.addItem("%f - %s" % (scalepoint['value'], scalepoint['label'])) | |||
if self._value != None: | |||
self.set_scalepoint_value(self._value) | |||
self.connect(self.box, SIGNAL("currentIndexChanged(QString)"), self.handleValueChangedFromBox) | |||
else: | |||
self._scalepoints = None | |||
def set_scalepoint_value(self, value): | |||
value = self.get_nearest_scalepoint(value) | |||
for i in range(self.box.count()): | |||
if float(self.box.itemText(i).split(" - ", 1)[0] == value): | |||
self.box.setCurrentIndex(i) | |||
break | |||
def get_nearest_scalepoint(self, real_value): | |||
final_value = 0.0 | |||
for i in range(len(self._scalepoints)): | |||
scale_value = self._scalepoints[i]['value'] | |||
if i == 0: | |||
final_value = scale_value | |||
else: | |||
srange1 = abs(real_value - scale_value) | |||
srange2 = abs(real_value - final_value) | |||
if srange2 > srange1: | |||
final_value = scale_value | |||
return final_value | |||
def handleValueChangedFromBar(self, value): | |||
if self._read_only: | |||
return | |||
step = int(0.5 + ((value - self._minimum) / self._step)) | |||
real_value = self._minimum + (step * self._step) | |||
self.set_value(real_value) | |||
def handleValueChangedFromBox(self, box_text): | |||
if self._read_only: | |||
return | |||
value = float(box_text.split(" - ", 1)[0]) | |||
last_scale_value = self._scalepoints[len(self._scalepoints) - 1]['value'] | |||
if value == last_scale_value: | |||
value = self._maximum | |||
self.set_value(value) | |||
def showCustomMenu(self, pos): | |||
menu = QMenu(self) | |||
act_x_reset = menu.addAction(self.tr("Reset (%f)" % self._default)) | |||
menu.addSeparator() | |||
act_x_copy = menu.addAction(self.tr("Copy (%f)" % self._value)) | |||
if False and not self._read_only: | |||
act_x_paste = menu.addAction(self.tr("Paste (%s)" % "TODO")) | |||
else: | |||
act_x_paste = menu.addAction(self.tr("Paste")) | |||
act_x_paste.setEnabled(False) | |||
menu.addSeparator() | |||
act_x_set = menu.addAction(self.tr("Set value...")) | |||
if self._read_only: | |||
act_x_reset.setEnabled(False) | |||
act_x_paste.setEnabled(False) | |||
act_x_set.setEnabled(False) | |||
# TODO - NOT IMPLEMENTED YET | |||
act_x_copy.setEnabled(False) | |||
act_x_sel = menu.exec_(QCursor.pos()) | |||
if act_x_sel == act_x_set: | |||
dialog = CustomInputDialog(self, self.parent().label.text(), self._value, self._minimum, self._maximum, self._step, self._scalepoints) | |||
if dialog.exec_(): | |||
value = dialog.ret_value | |||
self.set_value(value) | |||
elif act_x_sel == act_x_copy: | |||
pass | |||
elif act_x_sel == act_x_paste: | |||
pass | |||
elif act_x_sel == act_x_reset: | |||
self.set_value(self._default) | |||
def stepBy(self, steps): | |||
if steps == 0 or self._value == None: | |||
return | |||
value = self._value + (steps * self._step) | |||
if value < self._minimum: | |||
value = self._minimum | |||
elif value > self._maximum: | |||
value = self._maximum | |||
self.set_value(value) | |||
def stepEnabled(self): | |||
if self._read_only or self._value == None: | |||
return QAbstractSpinBox.StepNone | |||
elif self._value <= self._minimum: | |||
return QAbstractSpinBox.StepUpEnabled | |||
elif self._value >= self._maximum: | |||
return QAbstractSpinBox.StepDownEnabled | |||
else: | |||
return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled | |||
def updateAll(self): | |||
self.update() | |||
self.bar.update() | |||
if self._have_scalepoints: | |||
self.box.update() | |||
@pyqtSlot() | |||
def slot_updateBarGeometry(self): | |||
self.bar.setGeometry(self.lineEdit().geometry()) | |||
if self._have_scalepoints: | |||
self.box.setGeometry(self.lineEdit().geometry()) | |||
def resizeEvent(self, event): | |||
QTimer.singleShot(0, self, SLOT("slot_updateBarGeometry()")) | |||
QAbstractSpinBox.resizeEvent(self, event) |
@@ -0,0 +1,327 @@ | |||
/* | |||
* Pixmap Dial, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#include "pixmapdial.hpp" | |||
#include <cmath> | |||
#include <QtCore/QTimer> | |||
#include <QtGui/QPainter> | |||
PixmapDial::PixmapDial(QWidget* parent) | |||
: QDial(parent) | |||
{ | |||
m_pixmap.load(":/bitmaps/dial_01d.png"); | |||
m_pixmap_n_str = "01"; | |||
m_custom_paint = CUSTOM_PAINT_NULL; | |||
m_hovered = false; | |||
m_hover_step = HOVER_MIN; | |||
if (m_pixmap.width() > m_pixmap.height()) | |||
m_orientation = HORIZONTAL; | |||
else | |||
m_orientation = VERTICAL; | |||
m_label = ""; | |||
m_label_pos = QPointF(0.0f, 0.0f); | |||
m_label_width = 0; | |||
m_label_height = 0; | |||
m_label_gradient = QLinearGradient(0, 0, 0, 1); | |||
if (palette().window().color().lightness() > 100) | |||
{ | |||
// Light background | |||
QColor c = palette().dark().color(); | |||
m_color1 = c; | |||
m_color2 = QColor(c.red(), c.green(), c.blue(), 0); | |||
m_colorT[0] = palette().buttonText().color(); | |||
m_colorT[1] = palette().mid().color(); | |||
} | |||
else | |||
{ | |||
// Dark background | |||
m_color1 = QColor(0, 0, 0, 255); | |||
m_color2 = QColor(0, 0, 0, 0); | |||
m_colorT[0] = Qt::white; | |||
m_colorT[1] = Qt::darkGray; | |||
} | |||
updateSizes(); | |||
} | |||
int PixmapDial::getSize() const | |||
{ | |||
return p_size; | |||
} | |||
void PixmapDial::setCustomPaint(CustomPaint paint) | |||
{ | |||
m_custom_paint = paint; | |||
update(); | |||
} | |||
void PixmapDial::setEnabled(bool enabled) | |||
{ | |||
if (isEnabled() != enabled) | |||
{ | |||
m_pixmap.load(QString(":/dial_%1%2.png").arg(m_pixmap_n_str).arg(enabled ? "" : "d")); | |||
updateSizes(); | |||
update(); | |||
} | |||
QDial::setEnabled(enabled); | |||
} | |||
void PixmapDial::setLabel(QString label) | |||
{ | |||
m_label = label; | |||
m_label_width = QFontMetrics(font()).width(label); | |||
m_label_height = QFontMetrics(font()).height(); | |||
m_label_pos.setX(float(p_size)/2 - float(m_label_width)/2); | |||
m_label_pos.setY(p_size + m_label_height); | |||
m_label_gradient.setColorAt(0.0f, m_color1); | |||
m_label_gradient.setColorAt(0.6f, m_color1); | |||
m_label_gradient.setColorAt(1.0f, m_color2); | |||
m_label_gradient.setStart(0, float(p_size)/2); | |||
m_label_gradient.setFinalStop(0, p_size+m_label_height+5); | |||
m_label_gradient_rect = QRectF(float(p_size)/8, float(p_size)/2, float(p_size*6)/8, p_size+m_label_height+5); | |||
update(); | |||
} | |||
void PixmapDial::setPixmap(int pixmapId) | |||
{ | |||
m_pixmap_n_str.sprintf("%02i", pixmapId); | |||
m_pixmap.load(QString(":/bitmaps/dial_%1%2.png").arg(m_pixmap_n_str).arg(isEnabled() ? "" : "d")); | |||
if (m_pixmap.width() > m_pixmap.height()) | |||
m_orientation = HORIZONTAL; | |||
else | |||
m_orientation = VERTICAL; | |||
updateSizes(); | |||
update(); | |||
} | |||
QSize PixmapDial::minimumSizeHint() const | |||
{ | |||
return QSize(p_size, p_size); | |||
} | |||
QSize PixmapDial::sizeHint() const | |||
{ | |||
return QSize(p_size, p_size); | |||
} | |||
void PixmapDial::updateSizes() | |||
{ | |||
p_width = m_pixmap.width(); | |||
p_height = m_pixmap.height(); | |||
if (p_width < 1) | |||
p_width = 1; | |||
if (p_height < 1) | |||
p_height = 1; | |||
if (m_orientation == HORIZONTAL) | |||
{ | |||
p_size = p_height; | |||
p_count = p_width/p_height; | |||
} | |||
else | |||
{ | |||
p_size = p_width; | |||
p_count = p_height/p_width; | |||
} | |||
setMinimumSize(p_size, p_size + m_label_height + 5); | |||
setMaximumSize(p_size, p_size + m_label_height + 5); | |||
} | |||
void PixmapDial::enterEvent(QEvent* event) | |||
{ | |||
m_hovered = true; | |||
if (m_hover_step == HOVER_MIN) | |||
m_hover_step += 1; | |||
QDial::enterEvent(event); | |||
} | |||
void PixmapDial::leaveEvent(QEvent* event) | |||
{ | |||
m_hovered = false; | |||
if (m_hover_step == HOVER_MAX) | |||
m_hover_step -= 1; | |||
QDial::leaveEvent(event); | |||
} | |||
void PixmapDial::paintEvent(QPaintEvent*) | |||
{ | |||
QPainter painter(this); | |||
if (! m_label.isEmpty()) | |||
{ | |||
painter.setPen(m_color2); | |||
painter.setBrush(m_label_gradient); | |||
painter.drawRect(m_label_gradient_rect); | |||
painter.setPen(m_colorT[isEnabled() ? 0 : 1]); | |||
painter.drawText(m_label_pos, m_label); | |||
} | |||
if (isEnabled()) | |||
{ | |||
float current = value()-minimum(); | |||
float divider = maximum()-minimum(); | |||
if (divider == 0.0f) | |||
return; | |||
float value = current/divider; | |||
QRectF source, target(0.0f, 0.0f, p_size, p_size); | |||
int xpos, ypos, per = (p_count-1)*value; | |||
if (m_orientation == HORIZONTAL) | |||
{ | |||
xpos = p_size*per; | |||
ypos = 0.0f; | |||
} | |||
else | |||
{ | |||
xpos = 0.0f; | |||
ypos = p_size*per; | |||
} | |||
source = QRectF(xpos, ypos, p_size, p_size); | |||
painter.drawPixmap(target, m_pixmap, source); | |||
// Custom knobs (Dry/Wet and Volume) | |||
if (m_custom_paint == CUSTOM_PAINT_CARLA_WET || m_custom_paint == CUSTOM_PAINT_CARLA_VOL) | |||
{ | |||
// knob color | |||
QColor colorGreen(0x5D, 0xE7, 0x3D, 191 + m_hover_step*7); | |||
QColor colorBlue(0x3E, 0xB8, 0xBE, 191 + m_hover_step*7); | |||
// draw small circle | |||
QRectF ballRect(8.0, 8.0, 15.0, 15.0); | |||
QPainterPath ballPath; | |||
ballPath.addEllipse(ballRect); | |||
//painter.drawRect(ballRect); | |||
float tmpValue = (0.375f + 0.75f*value); | |||
float ballValue = tmpValue - floorf(tmpValue); | |||
QPointF ballPoint(ballPath.pointAtPercent(ballValue)); | |||
// draw arc | |||
int startAngle = 216*16; | |||
int spanAngle = -252*16*value; | |||
if (m_custom_paint == CUSTOM_PAINT_CARLA_WET) | |||
{ | |||
painter.setBrush(colorBlue); | |||
painter.setPen(QPen(colorBlue, 0)); | |||
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)); | |||
QConicalGradient gradient(15.5, 15.5, -45); | |||
gradient.setColorAt(0.0, colorBlue); | |||
gradient.setColorAt(0.125, colorBlue); | |||
gradient.setColorAt(0.625, colorGreen); | |||
gradient.setColorAt(0.75, colorGreen); | |||
gradient.setColorAt(0.76, colorGreen); | |||
gradient.setColorAt(1.0, colorGreen); | |||
painter.setBrush(gradient); | |||
painter.setPen(QPen(gradient, 3)); | |||
} | |||
else | |||
{ | |||
painter.setBrush(colorBlue); | |||
painter.setPen(QPen(colorBlue, 0)); | |||
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)); | |||
painter.setBrush(colorBlue); | |||
painter.setPen(QPen(colorBlue, 3)); | |||
} | |||
painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle); | |||
} | |||
// Custom knobs (L and R) | |||
else if (m_custom_paint == CUSTOM_PAINT_CARLA_L || m_custom_paint == CUSTOM_PAINT_CARLA_R) | |||
{ | |||
// knob color | |||
QColor color(0xAD + m_hover_step*5, 0xD5 + m_hover_step*4, 0x4B + m_hover_step*5); | |||
// draw small circle | |||
QRectF ballRect(7.0, 8.0, 11.0, 12.0); | |||
QPainterPath ballPath; | |||
ballPath.addEllipse(ballRect); | |||
//painter.drawRect(ballRect); | |||
float tmpValue = (0.375f + 0.75f*value); | |||
float ballValue = tmpValue - floorf(tmpValue); | |||
QPointF ballPoint(ballPath.pointAtPercent(ballValue)); | |||
painter.setBrush(color); | |||
painter.setPen(QPen(color, 0)); | |||
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0f, 2.0f)); | |||
int startAngle, spanAngle; | |||
// draw arc | |||
if (m_custom_paint == CUSTOM_PAINT_CARLA_L) | |||
{ | |||
startAngle = 216*16; | |||
spanAngle = -252.0*16*value; | |||
} | |||
else if (m_custom_paint == CUSTOM_PAINT_CARLA_R) | |||
{ | |||
startAngle = 324.0*16; | |||
spanAngle = 252.0*16*(1.0-value); | |||
} | |||
else | |||
return; | |||
painter.setPen(QPen(color, 2)); | |||
painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle); | |||
if (HOVER_MIN < m_hover_step && m_hover_step < HOVER_MAX) | |||
{ | |||
m_hover_step += m_hovered ? 1 : -1; | |||
QTimer::singleShot(20, this, SLOT(update())); | |||
} | |||
} | |||
if (HOVER_MIN < m_hover_step && m_hover_step < HOVER_MAX) | |||
{ | |||
m_hover_step += m_hovered ? 1 : -1; | |||
QTimer::singleShot(20, this, SLOT(update())); | |||
} | |||
} | |||
else | |||
{ | |||
QRectF target(0.0, 0.0, p_size, p_size); | |||
painter.drawPixmap(target, m_pixmap, target); | |||
} | |||
} | |||
void PixmapDial::resizeEvent(QResizeEvent* event) | |||
{ | |||
updateSizes(); | |||
QDial::resizeEvent(event); | |||
} |
@@ -0,0 +1,89 @@ | |||
/* | |||
* Pixmap Dial, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#ifndef PIXMAPDIAL_HPP | |||
#define PIXMAPDIAL_HPP | |||
#include <QtGui/QDial> | |||
#include <QtGui/QPixmap> | |||
class PixmapDial : public QDial | |||
{ | |||
public: | |||
enum CustomPaint { | |||
CUSTOM_PAINT_NULL = 0, | |||
CUSTOM_PAINT_CARLA_WET = 1, | |||
CUSTOM_PAINT_CARLA_VOL = 2, | |||
CUSTOM_PAINT_CARLA_L = 3, | |||
CUSTOM_PAINT_CARLA_R = 4 | |||
}; | |||
PixmapDial(QWidget* parent); | |||
int getSize() const; | |||
void setCustomPaint(CustomPaint paint); | |||
void setEnabled(bool enabled); | |||
void setLabel(QString label); | |||
void setPixmap(int pixmapId); | |||
QSize minimumSizeHint() const; | |||
QSize sizeHint() const; | |||
protected: | |||
void updateSizes(); | |||
void enterEvent(QEvent* event); | |||
void leaveEvent(QEvent* event); | |||
void paintEvent(QPaintEvent* event); | |||
void resizeEvent(QResizeEvent* event); | |||
private: | |||
enum Orientation { | |||
HORIZONTAL = 0, | |||
VERTICAL = 1 | |||
}; | |||
static const unsigned short HOVER_MIN = 0; | |||
static const unsigned short HOVER_MAX = 9; | |||
// ------------------------------------- | |||
QPixmap m_pixmap; | |||
QString m_pixmap_n_str; | |||
CustomPaint m_custom_paint; | |||
Orientation m_orientation; | |||
bool m_hovered; | |||
unsigned short m_hover_step; | |||
QString m_label; | |||
QPointF m_label_pos; | |||
int m_label_width; | |||
int m_label_height; | |||
QLinearGradient m_label_gradient; | |||
QRectF m_label_gradient_rect; | |||
QColor m_color1; | |||
QColor m_color2; | |||
QColor m_colorT[2]; | |||
int p_width, p_height, p_size, p_count; | |||
}; | |||
#endif // PIXMAPDIAL_HPP |
@@ -0,0 +1,277 @@ | |||
#!/usr/bin/env python3 | |||
# -*- coding: utf-8 -*- | |||
# Pixmap Dial, a custom Qt4 widget | |||
# Copyright (C) 2011-2012 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 COPYING file | |||
# Imports (Global) | |||
from math import floor | |||
from PyQt4.QtCore import Qt, QPointF, QRectF, QTimer, QSize, SLOT | |||
from PyQt4.QtGui import QColor, QConicalGradient, QDial, QFontMetrics, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap | |||
# Widget Class | |||
class PixmapDial(QDial): | |||
# enum Orientation | |||
HORIZONTAL = 0 | |||
VERTICAL = 1 | |||
# enum CustomPaint | |||
CUSTOM_PAINT_NULL = 0 | |||
CUSTOM_PAINT_CARLA_WET = 1 | |||
CUSTOM_PAINT_CARLA_VOL = 2 | |||
CUSTOM_PAINT_CARLA_L = 3 | |||
CUSTOM_PAINT_CARLA_R = 4 | |||
HOVER_MIN = 0 | |||
HOVER_MAX = 9 | |||
def __init__(self, parent): | |||
QDial.__init__(self, parent) | |||
self.m_pixmap = QPixmap(":/bitmaps/dial_01d.png") | |||
self.m_pixmap_n_str = "01" | |||
self.m_custom_paint = self.CUSTOM_PAINT_NULL | |||
self.m_hovered = False | |||
self.m_hover_step = self.HOVER_MIN | |||
if self.m_pixmap.width() > self.m_pixmap.height(): | |||
self.m_orientation = self.HORIZONTAL | |||
else: | |||
self.m_orientation = self.VERTICAL | |||
self.m_label = "" | |||
self.m_label_pos = QPointF(0.0, 0.0) | |||
self.m_label_width = 0 | |||
self.m_label_height = 0 | |||
self.m_label_gradient = QLinearGradient(0, 0, 0, 1) | |||
if self.palette().window().color().lightness() > 100: | |||
# Light background | |||
c = self.palette().dark().color() | |||
self.m_color1 = c | |||
self.m_color2 = QColor(c.red(), c.green(), c.blue(), 0) | |||
self.m_colorT = [self.palette().buttonText().color(), self.palette().mid().color()] | |||
else: | |||
# Dark background | |||
self.m_color1 = QColor(0, 0, 0, 255) | |||
self.m_color2 = QColor(0, 0, 0, 0) | |||
self.m_colorT = [Qt.white, Qt.darkGray] | |||
self.updateSizes() | |||
def getSize(self): | |||
return self.p_size | |||
def setCustomPaint(self, paint): | |||
self.m_custom_paint = paint | |||
self.update() | |||
def setEnabled(self, enabled): | |||
if self.isEnabled() != enabled: | |||
self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmap_n_str, "" if enabled else "d")) | |||
self.updateSizes() | |||
self.update() | |||
QDial.setEnabled(self, enabled) | |||
def setLabel(self, label): | |||
self.m_label = label | |||
self.m_label_width = QFontMetrics(self.font()).width(label) | |||
self.m_label_height = QFontMetrics(self.font()).height() | |||
self.m_label_pos.setX(float(self.p_size)/2 - float(self.m_label_width)/2) | |||
self.m_label_pos.setY(self.p_size + self.m_label_height) | |||
self.m_label_gradient.setColorAt(0.0, self.m_color1) | |||
self.m_label_gradient.setColorAt(0.6, self.m_color1) | |||
self.m_label_gradient.setColorAt(1.0, self.m_color2) | |||
self.m_label_gradient.setStart(0, float(self.p_size)/2) | |||
self.m_label_gradient.setFinalStop(0, self.p_size + self.m_label_height + 5) | |||
self.m_label_gradient_rect = QRectF(float(self.p_size)/8, float(self.p_size)/2, float(self.p_size)*6/8, self.p_size+self.m_label_height+5) | |||
self.update() | |||
def setPixmap(self, pixmapId): | |||
self.m_pixmap_n_str = "%02i" % pixmapId | |||
self.m_pixmap.load(":/bitmaps/dial_%s%s.png" % (self.m_pixmap_n_str, "" if self.isEnabled() else "d")) | |||
if self.m_pixmap.width() > self.m_pixmap.height(): | |||
self.m_orientation = self.HORIZONTAL | |||
else: | |||
self.m_orientation = self.VERTICAL | |||
self.updateSizes() | |||
self.update() | |||
def minimumSizeHint(self): | |||
return QSize(self.p_size, self.p_size) | |||
def sizeHint(self): | |||
return QSize(self.p_size, self.p_size) | |||
def updateSizes(self): | |||
self.p_width = self.m_pixmap.width() | |||
self.p_height = self.m_pixmap.height() | |||
if self.p_width < 1: | |||
self.p_width = 1 | |||
if self.p_height < 1: | |||
self.p_height = 1 | |||
if self.m_orientation == self.HORIZONTAL: | |||
self.p_size = self.p_height | |||
self.p_count = self.p_width / self.p_height | |||
else: | |||
self.p_size = self.p_width | |||
self.p_count = self.p_height / self.p_width | |||
self.setMinimumSize(self.p_size, self.p_size + self.m_label_height + 5) | |||
self.setMaximumSize(self.p_size, self.p_size + self.m_label_height + 5) | |||
def enterEvent(self, event): | |||
self.m_hovered = True | |||
if self.m_hover_step == self.HOVER_MIN: | |||
self.m_hover_step += 1 | |||
QDial.enterEvent(self, event) | |||
def leaveEvent(self, event): | |||
self.m_hovered = False | |||
if self.m_hover_step == self.HOVER_MAX: | |||
self.m_hover_step -= 1 | |||
QDial.leaveEvent(self, event) | |||
def paintEvent(self, event): | |||
painter = QPainter(self) | |||
painter.setRenderHint(QPainter.Antialiasing, True) | |||
if self.m_label: | |||
painter.setPen(self.m_color2) | |||
painter.setBrush(self.m_label_gradient) | |||
painter.drawRect(self.m_label_gradient_rect) | |||
painter.setPen(self.m_colorT[0 if self.isEnabled() else 1]) | |||
painter.drawText(self.m_label_pos, self.m_label) | |||
if self.isEnabled(): | |||
current = float(self.value() - self.minimum()) | |||
divider = float(self.maximum() - self.minimum()) | |||
if divider == 0.0: | |||
return | |||
value = current / divider | |||
target = QRectF(0.0, 0.0, self.p_size, self.p_size) | |||
per = int((self.p_count - 1) * value) | |||
if self.m_orientation == self.HORIZONTAL: | |||
xpos = self.p_size * per | |||
ypos = 0.0 | |||
else: | |||
xpos = 0.0 | |||
ypos = self.p_size * per | |||
source = QRectF(xpos, ypos, self.p_size, self.p_size) | |||
painter.drawPixmap(target, self.m_pixmap, source) | |||
# Custom knobs (Dry/Wet and Volume) | |||
if self.m_custom_paint in (self.CUSTOM_PAINT_CARLA_WET, self.CUSTOM_PAINT_CARLA_VOL): | |||
# knob color | |||
colorGreen = QColor(0x5D, 0xE7, 0x3D, 191 + self.m_hover_step*7) | |||
colorBlue = QColor(0x3E, 0xB8, 0xBE, 191 + self.m_hover_step*7) | |||
# draw small circle | |||
ballRect = QRectF(8.0, 8.0, 15.0, 15.0) | |||
ballPath = QPainterPath() | |||
ballPath.addEllipse(ballRect) | |||
#painter.drawRect(ballRect) | |||
tmpValue = (0.375 + 0.75*value) | |||
ballValue = tmpValue - floor(tmpValue) | |||
ballPoint = ballPath.pointAtPercent(ballValue) | |||
# draw arc | |||
startAngle = 216*16 | |||
spanAngle = -252*16*value | |||
if self.m_custom_paint == self.CUSTOM_PAINT_CARLA_WET: | |||
painter.setBrush(colorBlue) | |||
painter.setPen(QPen(colorBlue, 0)) | |||
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) | |||
gradient = QConicalGradient(15.5, 15.5, -45) | |||
gradient.setColorAt(0.0, colorBlue) | |||
gradient.setColorAt(0.125, colorBlue) | |||
gradient.setColorAt(0.625, colorGreen) | |||
gradient.setColorAt(0.75, colorGreen) | |||
gradient.setColorAt(0.76, colorGreen) | |||
gradient.setColorAt(1.0, colorGreen) | |||
painter.setBrush(gradient) | |||
painter.setPen(QPen(gradient, 3)) | |||
else: | |||
painter.setBrush(colorBlue) | |||
painter.setPen(QPen(colorBlue, 0)) | |||
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) | |||
painter.setBrush(colorBlue) | |||
painter.setPen(QPen(colorBlue, 3)) | |||
painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle) | |||
# Custom knobs (L and R) | |||
elif self.m_custom_paint in (self.CUSTOM_PAINT_CARLA_L, self.CUSTOM_PAINT_CARLA_R): | |||
# knob color | |||
color = QColor(0xAD + self.m_hover_step*5, 0xD5 + self.m_hover_step*4, 0x4B + self.m_hover_step*5) | |||
# draw small circle | |||
ballRect = QRectF(7.0, 8.0, 11.0, 12.0) | |||
ballPath = QPainterPath() | |||
ballPath.addEllipse(ballRect) | |||
#painter.drawRect(ballRect) | |||
tmpValue = (0.375 + 0.75*value) | |||
ballValue = tmpValue - floor(tmpValue) | |||
ballPoint = ballPath.pointAtPercent(ballValue) | |||
painter.setBrush(color) | |||
painter.setPen(QPen(color, 0)) | |||
painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0)) | |||
# draw arc | |||
if self.m_custom_paint == self.CUSTOM_PAINT_CARLA_L: | |||
startAngle = 216*16 | |||
spanAngle = -252.0*16*value | |||
elif self.m_custom_paint == self.CUSTOM_PAINT_CARLA_R: | |||
startAngle = 324.0*16 | |||
spanAngle = 252.0*16*(1.0-value) | |||
else: | |||
return | |||
painter.setPen(QPen(color, 2)) | |||
painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle) | |||
if self.HOVER_MIN < self.m_hover_step < self.HOVER_MAX: | |||
self.m_hover_step += 1 if self.m_hovered else -1 | |||
QTimer.singleShot(20, self, SLOT("update()")) | |||
else: | |||
target = QRectF(0.0, 0.0, self.p_size, self.p_size) | |||
painter.drawPixmap(target, self.m_pixmap, target) | |||
def resizeEvent(self, event): | |||
self.updateSizes() | |||
QDial.resizeEvent(self, event) |
@@ -0,0 +1,524 @@ | |||
/* | |||
* Pixmap Keyboard, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#include "pixmapkeyboard.hpp" | |||
#include <QtCore/QMap> | |||
#include <QtCore/QTimer> | |||
#include <QtGui/QKeyEvent> | |||
#include <QtGui/QMouseEvent> | |||
#include <QtGui/QPainter> | |||
QMap<int, QRectF> midi_key2rect_map_horizontal; | |||
QMap<int, QRectF> midi_key2rect_map_vertical; | |||
QMap<int, int> midi_keyboard2key_map; | |||
QVector<int> blackNotes; | |||
static bool pixmapkeyboard_initiated = false; | |||
void pixmapkeyboard_init() | |||
{ | |||
if (pixmapkeyboard_initiated) | |||
return; | |||
pixmapkeyboard_initiated = true; | |||
// midi_key2rect_map_horizontal ------ | |||
midi_key2rect_map_horizontal[0] = QRectF(0, 0, 18, 64); // C | |||
midi_key2rect_map_horizontal[1] = QRectF(13, 0, 11, 42); // C# | |||
midi_key2rect_map_horizontal[2] = QRectF(18, 0, 25, 64); // D | |||
midi_key2rect_map_horizontal[3] = QRectF(37, 0, 11, 42); // D# | |||
midi_key2rect_map_horizontal[4] = QRectF(42, 0, 18, 64); // E | |||
midi_key2rect_map_horizontal[5] = QRectF(60, 0, 18, 64); // F | |||
midi_key2rect_map_horizontal[6] = QRectF(73, 0, 11, 42); // F# | |||
midi_key2rect_map_horizontal[7] = QRectF(78, 0, 25, 64); // G | |||
midi_key2rect_map_horizontal[8] = QRectF(97, 0, 11, 42); // G# | |||
midi_key2rect_map_horizontal[9] = QRectF(102, 0, 25, 64); // A | |||
midi_key2rect_map_horizontal[10] = QRectF(121, 0, 11, 42); // A# | |||
midi_key2rect_map_horizontal[11] = QRectF(126, 0, 18, 64); // B | |||
// midi_key2rect_map_vertical -------- | |||
midi_key2rect_map_vertical[11] = QRectF(0, 0, 64, 18); // B | |||
midi_key2rect_map_vertical[10] = QRectF(0, 14, 42, 7); // A# | |||
midi_key2rect_map_vertical[9] = QRectF(0, 18, 64, 24); // A | |||
midi_key2rect_map_vertical[8] = QRectF(0, 38, 42, 7); // G# | |||
midi_key2rect_map_vertical[7] = QRectF(0, 42, 64, 24); // G | |||
midi_key2rect_map_vertical[6] = QRectF(0, 62, 42, 7); // F# | |||
midi_key2rect_map_vertical[5] = QRectF(0, 66, 64, 18); // F | |||
midi_key2rect_map_vertical[4] = QRectF(0, 84, 64, 18); // E | |||
midi_key2rect_map_vertical[3] = QRectF(0, 98, 42, 7); // D# | |||
midi_key2rect_map_vertical[2] = QRectF(0, 102, 64, 24); // D | |||
midi_key2rect_map_vertical[1] = QRectF(0, 122, 42, 7); // C# | |||
midi_key2rect_map_vertical[0] = QRectF(0, 126, 64, 18); // C | |||
// midi_keyboard2key_map ------------- | |||
// 3th octave | |||
midi_keyboard2key_map[Qt::Key_Z] = 48; | |||
midi_keyboard2key_map[Qt::Key_S] = 49; | |||
midi_keyboard2key_map[Qt::Key_X] = 50; | |||
midi_keyboard2key_map[Qt::Key_D] = 51; | |||
midi_keyboard2key_map[Qt::Key_C] = 52; | |||
midi_keyboard2key_map[Qt::Key_V] = 53; | |||
midi_keyboard2key_map[Qt::Key_G] = 54; | |||
midi_keyboard2key_map[Qt::Key_B] = 55; | |||
midi_keyboard2key_map[Qt::Key_H] = 56; | |||
midi_keyboard2key_map[Qt::Key_N] = 57; | |||
midi_keyboard2key_map[Qt::Key_J] = 58; | |||
midi_keyboard2key_map[Qt::Key_M] = 59; | |||
// 4th octave | |||
midi_keyboard2key_map[Qt::Key_Q] = 60; | |||
midi_keyboard2key_map[Qt::Key_2] = 61; | |||
midi_keyboard2key_map[Qt::Key_W] = 62; | |||
midi_keyboard2key_map[Qt::Key_3] = 63; | |||
midi_keyboard2key_map[Qt::Key_E] = 64; | |||
midi_keyboard2key_map[Qt::Key_R] = 65; | |||
midi_keyboard2key_map[Qt::Key_5] = 66; | |||
midi_keyboard2key_map[Qt::Key_T] = 67; | |||
midi_keyboard2key_map[Qt::Key_6] = 68; | |||
midi_keyboard2key_map[Qt::Key_Y] = 69; | |||
midi_keyboard2key_map[Qt::Key_7] = 70; | |||
midi_keyboard2key_map[Qt::Key_U] = 71; | |||
blackNotes << 1; | |||
blackNotes << 3; | |||
blackNotes << 6; | |||
blackNotes << 8; | |||
blackNotes << 10; | |||
} | |||
PixmapKeyboard::PixmapKeyboard(QWidget* parent) | |||
: QWidget(parent), | |||
m_font("Monospace", 8, QFont::Normal) | |||
{ | |||
pixmapkeyboard_init(); | |||
m_octaves = 6; | |||
m_lastMouseNote = -1; | |||
m_needsUpdate = false; | |||
setCursor(Qt::PointingHandCursor); | |||
setMode(HORIZONTAL); | |||
} | |||
void PixmapKeyboard::allNotesOff() | |||
{ | |||
m_enabledKeys.clear(); | |||
m_needsUpdate = true; | |||
QTimer::singleShot(0, this, SLOT(updateOnce())); | |||
emit notesOff(); | |||
} | |||
void PixmapKeyboard::sendNoteOn(int note, bool sendSignal) | |||
{ | |||
if (0 <= note && note <= 127 && ! m_enabledKeys.contains(note)) | |||
{ | |||
m_enabledKeys.append(note); | |||
if (sendSignal) | |||
emit noteOn(note); | |||
m_needsUpdate = true; | |||
QTimer::singleShot(0, this, SLOT(updateOnce())); | |||
} | |||
if (m_enabledKeys.count() == 1) | |||
emit notesOn(); | |||
} | |||
void PixmapKeyboard::sendNoteOff(int note, bool sendSignal) | |||
{ | |||
if (note >= 0 && note <= 127 && m_enabledKeys.contains(note)) | |||
{ | |||
m_enabledKeys.removeOne(note); | |||
if (sendSignal) | |||
emit noteOff(note); | |||
m_needsUpdate = true; | |||
QTimer::singleShot(0, this, SLOT(updateOnce())); | |||
} | |||
if (m_enabledKeys.count() == 0) | |||
emit notesOff(); | |||
} | |||
void PixmapKeyboard::setMode(Orientation mode, Color color) | |||
{ | |||
if (color == COLOR_CLASSIC) | |||
{ | |||
m_colorStr = "classic"; | |||
} | |||
else if (color == COLOR_ORANGE) | |||
{ | |||
m_colorStr = "orange"; | |||
} | |||
else | |||
{ | |||
qCritical("PixmapKeyboard::setMode(%i, %i) - invalid color", mode, color); | |||
return setMode(mode); | |||
} | |||
if (mode == HORIZONTAL) | |||
{ | |||
m_midi_map = &midi_key2rect_map_horizontal; | |||
m_pixmap.load(QString(":/bitmaps/kbd_h_%1.png").arg(m_colorStr)); | |||
m_pixmap_mode = HORIZONTAL; | |||
p_width = m_pixmap.width(); | |||
p_height = m_pixmap.height() / 2; | |||
} | |||
else if (mode == VERTICAL) | |||
{ | |||
m_midi_map = &midi_key2rect_map_vertical; | |||
m_pixmap.load(QString(":/bitmaps/kbd_v_%1.png").arg(m_colorStr)); | |||
m_pixmap_mode = VERTICAL; | |||
p_width = m_pixmap.width() / 2; | |||
p_height = m_pixmap.height(); | |||
} | |||
else | |||
{ | |||
qCritical("PixmapKeyboard::setMode(%i, %i) - invalid mode", mode, color); | |||
return setMode(HORIZONTAL); | |||
} | |||
setOctaves(m_octaves); | |||
} | |||
void PixmapKeyboard::setOctaves(int octaves) | |||
{ | |||
Q_ASSERT(octaves >= 1 && octaves <= 6); | |||
if (octaves < 1) | |||
octaves = 1; | |||
else if (octaves > 6) | |||
octaves = 6; | |||
m_octaves = octaves; | |||
if (m_pixmap_mode == HORIZONTAL) | |||
{ | |||
setMinimumSize(p_width * m_octaves, p_height); | |||
setMaximumSize(p_width * m_octaves, p_height); | |||
} | |||
else if (m_pixmap_mode == VERTICAL) | |||
{ | |||
setMinimumSize(p_width, p_height * m_octaves); | |||
setMaximumSize(p_width, p_height * m_octaves); | |||
} | |||
update(); | |||
} | |||
void PixmapKeyboard::handleMousePos(const QPoint& pos) | |||
{ | |||
int note, octave; | |||
QPointF n_pos; | |||
if (m_pixmap_mode == HORIZONTAL) | |||
{ | |||
if (pos.x() < 0 or pos.x() > m_octaves * 144) | |||
return; | |||
int posX = pos.x() - 1; | |||
octave = posX / p_width; | |||
n_pos = QPointF(posX % p_width, pos.y()); | |||
} | |||
else if (m_pixmap_mode == VERTICAL) | |||
{ | |||
if (pos.y() < 0 or pos.y() > m_octaves * 144) | |||
return; | |||
int posY = pos.y() - 1; | |||
octave = m_octaves - posY / p_height; | |||
n_pos = QPointF(pos.x(), posY % p_height); | |||
} | |||
else | |||
return; | |||
octave += 3; | |||
if ((*m_midi_map)[1].contains(n_pos)) // C# | |||
note = 1; | |||
else if ((*m_midi_map)[3].contains(n_pos)) // D# | |||
note = 3; | |||
else if ((*m_midi_map)[6].contains(n_pos)) // F# | |||
note = 6; | |||
else if ((*m_midi_map)[8].contains(n_pos)) // G# | |||
note = 8; | |||
else if ((*m_midi_map)[10].contains(n_pos))// A# | |||
note = 10; | |||
else if ((*m_midi_map)[0].contains(n_pos)) // C | |||
note = 0; | |||
else if ((*m_midi_map)[2].contains(n_pos)) // D | |||
note = 2; | |||
else if ((*m_midi_map)[4].contains(n_pos)) // E | |||
note = 4; | |||
else if ((*m_midi_map)[5].contains(n_pos)) // F | |||
note = 5; | |||
else if ((*m_midi_map)[7].contains(n_pos)) // G | |||
note = 7; | |||
else if ((*m_midi_map)[9].contains(n_pos)) // A | |||
note = 9; | |||
else if ((*m_midi_map)[11].contains(n_pos))// B | |||
note = 11; | |||
else | |||
note = -1; | |||
if (note != -1) | |||
{ | |||
note += octave * 12; | |||
if (m_lastMouseNote != note) | |||
{ | |||
sendNoteOff(m_lastMouseNote); | |||
sendNoteOn(note); | |||
} | |||
} | |||
else | |||
sendNoteOff(m_lastMouseNote); | |||
m_lastMouseNote = note; | |||
} | |||
void PixmapKeyboard::keyPressEvent(QKeyEvent* event) | |||
{ | |||
if (! event->isAutoRepeat()) | |||
{ | |||
int qKey = event->key(); | |||
if (midi_keyboard2key_map.keys().contains(qKey)) | |||
sendNoteOn(midi_keyboard2key_map[qKey]); | |||
} | |||
QWidget::keyPressEvent(event); | |||
} | |||
void PixmapKeyboard::keyReleaseEvent(QKeyEvent* event) | |||
{ | |||
if (! event->isAutoRepeat()) | |||
{ | |||
int qKey = event->key(); | |||
if (midi_keyboard2key_map.keys().contains(qKey)) | |||
sendNoteOff(midi_keyboard2key_map[qKey]); | |||
} | |||
QWidget::keyReleaseEvent(event); | |||
} | |||
void PixmapKeyboard::mousePressEvent(QMouseEvent* event) | |||
{ | |||
m_lastMouseNote = -1; | |||
handleMousePos(event->pos()); | |||
setFocus(); | |||
QWidget::mousePressEvent(event); | |||
} | |||
void PixmapKeyboard::mouseMoveEvent(QMouseEvent* event) | |||
{ | |||
handleMousePos(event->pos()); | |||
QWidget::mousePressEvent(event); | |||
} | |||
void PixmapKeyboard::mouseReleaseEvent(QMouseEvent* event) | |||
{ | |||
if (m_lastMouseNote != -1) | |||
{ | |||
sendNoteOff(m_lastMouseNote); | |||
m_lastMouseNote = -1; | |||
} | |||
QWidget::mouseReleaseEvent(event); | |||
} | |||
void PixmapKeyboard::paintEvent(QPaintEvent*) | |||
{ | |||
QPainter painter(this); | |||
// ------------------------------------------------------------- | |||
// Paint clean keys (as background) | |||
for (int octave=0; octave < m_octaves; octave++) | |||
{ | |||
QRectF target; | |||
if (m_pixmap_mode == HORIZONTAL) | |||
target = QRectF(p_width * octave, 0, p_width, p_height); | |||
else if (m_pixmap_mode == VERTICAL) | |||
target = QRectF(0, p_height * octave, p_width, p_height); | |||
else | |||
return; | |||
QRectF source = QRectF(0, 0, p_width, p_height); | |||
painter.drawPixmap(target, m_pixmap, source); | |||
} | |||
// ------------------------------------------------------------- | |||
// Paint (white) pressed keys | |||
bool paintedWhite = false; | |||
for (int i=0; i < m_enabledKeys.count(); i++) | |||
{ | |||
int octave, note = m_enabledKeys[i]; | |||
QRectF pos = _getRectFromMidiNote(note); | |||
if (_isNoteBlack(note)) | |||
continue; | |||
if (note < 36) | |||
// cannot paint this note | |||
continue; | |||
else if (note < 48) | |||
octave = 0; | |||
else if (note < 60) | |||
octave = 1; | |||
else if (note < 72) | |||
octave = 2; | |||
else if (note < 84) | |||
octave = 3; | |||
else if (note < 96) | |||
octave = 4; | |||
else if (note < 108) | |||
octave = 5; | |||
else | |||
// cannot paint this note either | |||
continue; | |||
if (m_pixmap_mode == VERTICAL) | |||
octave = m_octaves - octave - 1; | |||
QRectF target, source; | |||
if (m_pixmap_mode == HORIZONTAL) | |||
{ | |||
target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); | |||
source = QRectF(pos.x(), p_height, pos.width(), pos.height()); | |||
} | |||
else if (m_pixmap_mode == VERTICAL) | |||
{ | |||
target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); | |||
source = QRectF(p_width, pos.y(), pos.width(), pos.height()); | |||
} | |||
else | |||
return; | |||
paintedWhite = true; | |||
painter.drawPixmap(target, m_pixmap, source); | |||
} | |||
// ------------------------------------------------------------- | |||
// Clear white keys border | |||
if (paintedWhite) | |||
{ | |||
for (int octave=0; octave < m_octaves; octave++) | |||
{ | |||
foreach (int note, blackNotes) | |||
{ | |||
QRectF target, source; | |||
QRectF pos = _getRectFromMidiNote(note); | |||
if (m_pixmap_mode == HORIZONTAL) | |||
{ | |||
target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); | |||
source = QRectF(pos.x(), 0, pos.width(), pos.height()); | |||
} | |||
else if (m_pixmap_mode == VERTICAL) | |||
{ | |||
target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); | |||
source = QRectF(0, pos.y(), pos.width(), pos.height()); | |||
} | |||
else | |||
return; | |||
painter.drawPixmap(target, m_pixmap, source); | |||
} | |||
} | |||
} | |||
// ------------------------------------------------------------- | |||
// Paint (black) pressed keys | |||
for (int i=0; i < m_enabledKeys.count(); i++) | |||
{ | |||
int octave, note = m_enabledKeys[i]; | |||
QRectF pos = _getRectFromMidiNote(note); | |||
if (! _isNoteBlack(note)) | |||
continue; | |||
if (note < 36) | |||
// cannot paint this note | |||
continue; | |||
else if (note < 48) | |||
octave = 0; | |||
else if (note < 60) | |||
octave = 1; | |||
else if (note < 72) | |||
octave = 2; | |||
else if (note < 84) | |||
octave = 3; | |||
else if (note < 96) | |||
octave = 4; | |||
else if (note < 108) | |||
octave = 5; | |||
else | |||
// cannot paint this note either | |||
continue; | |||
if (m_pixmap_mode == VERTICAL) | |||
octave = m_octaves - octave - 1; | |||
QRectF target, source; | |||
if (m_pixmap_mode == HORIZONTAL) | |||
{ | |||
target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); | |||
source = QRectF(pos.x(), p_height, pos.width(), pos.height()); | |||
} | |||
else if (m_pixmap_mode == VERTICAL) | |||
{ | |||
target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); | |||
source = QRectF(p_width, pos.y(), pos.width(), pos.height()); | |||
} | |||
else | |||
return; | |||
painter.drawPixmap(target, m_pixmap, source); | |||
} | |||
// Paint C-number note info | |||
painter.setFont(m_font); | |||
painter.setPen(Qt::black); | |||
for (int i=0; i < m_octaves; i++) | |||
{ | |||
if (m_pixmap_mode == HORIZONTAL) | |||
painter.drawText(i * 144, 48, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2)); | |||
else if (m_pixmap_mode == VERTICAL) | |||
painter.drawText(45, (m_octaves * 144) - (i * 144) - 16, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2)); | |||
} | |||
} | |||
void PixmapKeyboard::updateOnce() | |||
{ | |||
if (m_needsUpdate) | |||
{ | |||
update(); | |||
m_needsUpdate = false; | |||
} | |||
} | |||
bool PixmapKeyboard::_isNoteBlack(int note) | |||
{ | |||
int baseNote = note % 12; | |||
return blackNotes.contains(baseNote); | |||
} | |||
QRectF PixmapKeyboard::_getRectFromMidiNote(int note) | |||
{ | |||
return (*m_midi_map)[note % 12]; | |||
} |
@@ -0,0 +1,85 @@ | |||
/* | |||
* Pixmap Keyboard, a custom Qt4 widget | |||
* Copyright (C) 2011-2012 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 COPYING file | |||
*/ | |||
#ifndef PIXMAPKEYBOARD_HPP | |||
#define PIXMAPKEYBOARD_HPP | |||
#include <QtGui/QPixmap> | |||
#include <QtGui/QWidget> | |||
class PixmapKeyboard : public QWidget | |||
{ | |||
Q_OBJECT | |||
public: | |||
enum Color { | |||
COLOR_CLASSIC = 0, | |||
COLOR_ORANGE = 1 | |||
}; | |||
enum Orientation { | |||
HORIZONTAL = 0, | |||
VERTICAL = 1 | |||
}; | |||
PixmapKeyboard(QWidget* parent); | |||
void allNotesOff(); | |||
void sendNoteOn(int note, bool sendSignal=true); | |||
void sendNoteOff(int note, bool sendSignal=true); | |||
void setMode(Orientation mode, Color color=COLOR_ORANGE); | |||
void setOctaves(int octaves); | |||
signals: | |||
void noteOn(int); | |||
void noteOff(int); | |||
void notesOn(); | |||
void notesOff(); | |||
protected: | |||
void handleMousePos(const QPoint&); | |||
void keyPressEvent(QKeyEvent*); | |||
void keyReleaseEvent(QKeyEvent*); | |||
void mousePressEvent(QMouseEvent*); | |||
void mouseMoveEvent(QMouseEvent*); | |||
void mouseReleaseEvent(QMouseEvent*); | |||
void paintEvent(QPaintEvent*); | |||
private Q_SLOTS: | |||
void updateOnce(); | |||
private: | |||
QPixmap m_pixmap; | |||
Orientation m_pixmap_mode; | |||
QString m_colorStr; | |||
QFont m_font; | |||
int m_octaves; | |||
int m_lastMouseNote; | |||
int p_width, p_height; | |||
bool m_needsUpdate; | |||
QList<int> m_enabledKeys; | |||
QMap<int, QRectF> *m_midi_map; | |||
bool _isNoteBlack(int note); | |||
QRectF _getRectFromMidiNote(int note); | |||
}; | |||
#endif // PIXMAPKEYBOARD_HPP |
@@ -0,0 +1,420 @@ | |||
#!/usr/bin/env python3 | |||
# -*- coding: utf-8 -*- | |||
# Pixmap Keyboard, a custom Qt4 widget | |||
# Copyright (C) 2011-2012 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 COPYING file | |||
# Imports (Global) | |||
from PyQt4.QtCore import pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer, SIGNAL, SLOT | |||
from PyQt4.QtGui import QFont, QPainter, QPixmap, QWidget | |||
midi_key2rect_map_horizontal = { | |||
'0': QRectF(0, 0, 18, 64), # C | |||
'1': QRectF(13, 0, 11, 42), # C# | |||
'2': QRectF(18, 0, 25, 64), # D | |||
'3': QRectF(37, 0, 11, 42), # D# | |||
'4': QRectF(42, 0, 18, 64), # E | |||
'5': QRectF(60, 0, 18, 64), # F | |||
'6': QRectF(73, 0, 11, 42), # F# | |||
'7': QRectF(78, 0, 25, 64), # G | |||
'8': QRectF(97, 0, 11, 42), # G# | |||
'9': QRectF(102, 0, 25, 64), # A | |||
'10': QRectF(121, 0, 11, 42), # A# | |||
'11': QRectF(126, 0, 18, 64) # B | |||
} | |||
midi_key2rect_map_vertical = { | |||
'11': QRectF(0, 0, 64, 18), # B | |||
'10': QRectF(0, 14, 42, 7), # A# | |||
'9': QRectF(0, 18, 64, 24), # A | |||
'8': QRectF(0, 38, 42, 7), # G# | |||
'7': QRectF(0, 42, 64, 24), # G | |||
'6': QRectF(0, 62, 42, 7), # F# | |||
'5': QRectF(0, 66, 64, 18), # F | |||
'4': QRectF(0, 84, 64, 18), # E | |||
'3': QRectF(0, 98, 42, 7), # D# | |||
'2': QRectF(0, 102, 64, 24), # D | |||
'1': QRectF(0, 122, 42, 7), # C# | |||
'0': QRectF(0, 126, 64, 18) # C | |||
} | |||
midi_keyboard2key_map = { | |||
# 3th octave | |||
'%i' % Qt.Key_Z: 48, | |||
'%i' % Qt.Key_S: 49, | |||
'%i' % Qt.Key_X: 50, | |||
'%i' % Qt.Key_D: 51, | |||
'%i' % Qt.Key_C: 52, | |||
'%i' % Qt.Key_V: 53, | |||
'%i' % Qt.Key_G: 54, | |||
'%i' % Qt.Key_B: 55, | |||
'%i' % Qt.Key_H: 56, | |||
'%i' % Qt.Key_N: 57, | |||
'%i' % Qt.Key_J: 58, | |||
'%i' % Qt.Key_M: 59, | |||
# 4th octave | |||
'%i' % Qt.Key_Q: 60, | |||
'%i' % Qt.Key_2: 61, | |||
'%i' % Qt.Key_W: 62, | |||
'%i' % Qt.Key_3: 63, | |||
'%i' % Qt.Key_E: 64, | |||
'%i' % Qt.Key_R: 65, | |||
'%i' % Qt.Key_5: 66, | |||
'%i' % Qt.Key_T: 67, | |||
'%i' % Qt.Key_6: 68, | |||
'%i' % Qt.Key_Y: 69, | |||
'%i' % Qt.Key_7: 70, | |||
'%i' % Qt.Key_U: 71, | |||
} | |||
# MIDI Keyboard, using a pixmap for painting | |||
class PixmapKeyboard(QWidget): | |||
# enum Color | |||
COLOR_CLASSIC = 0 | |||
COLOR_ORANGE = 1 | |||
# enum Orientation | |||
HORIZONTAL = 0 | |||
VERTICAL = 1 | |||
def __init__(self, parent): | |||
QWidget.__init__(self, parent) | |||
self.m_octaves = 6 | |||
self.m_lastMouseNote = -1 | |||
self.m_needsUpdate = False | |||
self.m_enabledKeys = [] | |||
self.m_font = QFont("Monospace", 8, QFont.Normal) | |||
self.m_pixmap = QPixmap("") | |||
self.setCursor(Qt.PointingHandCursor) | |||
self.setMode(self.HORIZONTAL) | |||
def allNotesOff(self): | |||
self.m_enabledKeys = [] | |||
self.m_needsUpdate = True | |||
QTimer.singleShot(0, self, SLOT("slot_updateOnce()")) | |||
self.emit(SIGNAL("notesOff()")) | |||
def sendNoteOn(self, note, sendSignal=True): | |||
if 0 <= note <= 127 and note not in self.m_enabledKeys: | |||
self.m_enabledKeys.append(note) | |||
if sendSignal: | |||
self.emit(SIGNAL("noteOn(int)"), note) | |||
self.m_needsUpdate = True | |||
QTimer.singleShot(0, self, SLOT("slot_updateOnce()")) | |||
if len(self.m_enabledKeys) == 1: | |||
self.emit(SIGNAL("notesOn()")) | |||
def sendNoteOff(self, note, sendSignal=True): | |||
if 0 <= note <= 127 and note in self.m_enabledKeys: | |||
self.m_enabledKeys.remove(note) | |||
if sendSignal: | |||
self.emit(SIGNAL("noteOff(int)"), note) | |||
self.m_needsUpdate = True | |||
QTimer.singleShot(0, self, SLOT("slot_updateOnce()")) | |||
if len(self.m_enabledKeys) == 0: | |||
self.emit(SIGNAL("notesOff()")) | |||
def setMode(self, mode, color=COLOR_ORANGE): | |||
if color == self.COLOR_CLASSIC: | |||
self.m_colorStr = "classic" | |||
elif color == self.COLOR_ORANGE: | |||
self.m_colorStr = "orange" | |||
else: | |||
qCritical("PixmapKeyboard::setMode(%i, %i) - invalid color" % (mode, color)) | |||
return self.setMode(mode) | |||
if mode == self.HORIZONTAL: | |||
self.m_midi_map = midi_key2rect_map_horizontal | |||
self.m_pixmap.load(":/bitmaps/kbd_h_%s.png" % self.m_colorStr) | |||
self.m_pixmap_mode = self.HORIZONTAL | |||
self.p_width = self.m_pixmap.width() | |||
self.p_height = self.m_pixmap.height() / 2 | |||
elif mode == self.VERTICAL: | |||
self.m_midi_map = midi_key2rect_map_vertical | |||
self.m_pixmap.load(":/bitmaps/kbd_v_%s.png" % self.m_colorStr) | |||
self.m_pixmap_mode = self.VERTICAL | |||
self.p_width = self.m_pixmap.width() / 2 | |||
self.p_height = self.m_pixmap.height() | |||
else: | |||
qCritical("PixmapKeyboard::setMode(%i, %i) - invalid mode" % (mode, color)) | |||
return self.setMode(self.HORIZONTAL) | |||
self.setOctaves(self.m_octaves) | |||
def setOctaves(self, octaves): | |||
if octaves < 1: | |||
octaves = 1 | |||
elif octaves > 8: | |||
octaves = 8 | |||
self.m_octaves = octaves | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
self.setMinimumSize(self.p_width * self.m_octaves, self.p_height) | |||
self.setMaximumSize(self.p_width * self.m_octaves, self.p_height) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
self.setMinimumSize(self.p_width, self.p_height * self.m_octaves) | |||
self.setMaximumSize(self.p_width, self.p_height * self.m_octaves) | |||
self.update() | |||
def handleMousePos(self, pos): | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
if pos.x() < 0 or pos.x() > self.m_octaves * 144: | |||
return | |||
posX = pos.x() - 1 | |||
octave = int(posX / self.p_width) | |||
n_pos = QPointF(posX % self.p_width, pos.y()) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
if pos.y() < 0 or pos.y() > self.m_octaves * 144: | |||
return | |||
posY = pos.y() - 1 | |||
octave = int(self.m_octaves - posY / self.p_height) | |||
n_pos = QPointF(pos.x(), posY % self.p_height) | |||
else: | |||
return | |||
octave += 3 | |||
if self.m_midi_map['1'].contains(n_pos): # C# | |||
note = 1 | |||
elif self.m_midi_map['3'].contains(n_pos): # D# | |||
note = 3 | |||
elif self.m_midi_map['6'].contains(n_pos): # F# | |||
note = 6 | |||
elif self.m_midi_map['8'].contains(n_pos): # G# | |||
note = 8 | |||
elif self.m_midi_map['10'].contains(n_pos):# A# | |||
note = 10 | |||
elif self.m_midi_map['0'].contains(n_pos): # C | |||
note = 0 | |||
elif self.m_midi_map['2'].contains(n_pos): # D | |||
note = 2 | |||
elif self.m_midi_map['4'].contains(n_pos): # E | |||
note = 4 | |||
elif self.m_midi_map['5'].contains(n_pos): # F | |||
note = 5 | |||
elif self.m_midi_map['7'].contains(n_pos): # G | |||
note = 7 | |||
elif self.m_midi_map['9'].contains(n_pos): # A | |||
note = 9 | |||
elif self.m_midi_map['11'].contains(n_pos):# B | |||
note = 11 | |||
else: | |||
note = -1 | |||
if note != -1: | |||
note += octave * 12 | |||
if self.m_lastMouseNote != note: | |||
self.sendNoteOff(self.m_lastMouseNote) | |||
self.sendNoteOn(note) | |||
else: | |||
self.sendNoteOff(self.m_lastMouseNote) | |||
self.m_lastMouseNote = note | |||
def keyPressEvent(self, event): | |||
if not event.isAutoRepeat(): | |||
qKey = str(event.key()) | |||
if qKey in midi_keyboard2key_map.keys(): | |||
self.sendNoteOn(midi_keyboard2key_map.get(qKey)) | |||
QWidget.keyPressEvent(self, event) | |||
def keyReleaseEvent(self, event): | |||
if not event.isAutoRepeat(): | |||
qKey = str(event.key()) | |||
if qKey in midi_keyboard2key_map.keys(): | |||
self.sendNoteOff(midi_keyboard2key_map.get(qKey)) | |||
QWidget.keyReleaseEvent(self, event) | |||
def mousePressEvent(self, event): | |||
self.m_lastMouseNote = -1 | |||
self.handleMousePos(event.pos()) | |||
self.setFocus() | |||
QWidget.mousePressEvent(self, event) | |||
def mouseMoveEvent(self, event): | |||
self.handleMousePos(event.pos()) | |||
QWidget.mousePressEvent(self, event) | |||
def mouseReleaseEvent(self, event): | |||
if self.m_lastMouseNote != -1: | |||
self.sendNoteOff(self.m_lastMouseNote) | |||
self.m_lastMouseNote = -1 | |||
QWidget.mouseReleaseEvent(self, event) | |||
def paintEvent(self, event): | |||
painter = QPainter(self) | |||
# ------------------------------------------------------------- | |||
# Paint clean keys (as background) | |||
for octave in range(self.m_octaves): | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
target = QRectF(self.p_width * octave, 0, self.p_width, self.p_height) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
target = QRectF(0, self.p_height * octave, self.p_width, self.p_height) | |||
else: | |||
return | |||
source = QRectF(0, 0, self.p_width, self.p_height) | |||
painter.drawPixmap(target, self.m_pixmap, source) | |||
# ------------------------------------------------------------- | |||
# Paint (white) pressed keys | |||
paintedWhite = False | |||
for i in range(len(self.m_enabledKeys)): | |||
note = self.m_enabledKeys[i] | |||
pos = self._getRectFromMidiNote(note) | |||
if self._isNoteBlack(note): | |||
continue | |||
if note < 36: | |||
# cannot paint this note | |||
continue | |||
elif note < 48: | |||
octave = 0 | |||
elif note < 60: | |||
octave = 1 | |||
elif note < 72: | |||
octave = 2 | |||
elif note < 84: | |||
octave = 3 | |||
elif note < 96: | |||
octave = 4 | |||
elif note < 108: | |||
octave = 5 | |||
elif note < 120: | |||
octave = 6 | |||
elif note < 132: | |||
octave = 7 | |||
else: | |||
# cannot paint this note either | |||
continue | |||
if self.m_pixmap_mode == self.VERTICAL: | |||
octave = self.m_octaves - octave - 1 | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height()) | |||
source = QRectF(pos.x(), self.p_height, pos.width(), pos.height()) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height()) | |||
source = QRectF(self.p_width, pos.y(), pos.width(), pos.height()) | |||
else: | |||
return | |||
paintedWhite = True | |||
painter.drawPixmap(target, self.m_pixmap, source) | |||
# ------------------------------------------------------------- | |||
# Clear white keys border | |||
if paintedWhite: | |||
for octave in range(self.m_octaves): | |||
for note in (1, 3, 6, 8, 10): | |||
pos = self._getRectFromMidiNote(note) | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height()) | |||
source = QRectF(pos.x(), 0, pos.width(), pos.height()) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height()) | |||
source = QRectF(0, pos.y(), pos.width(), pos.height()) | |||
else: | |||
return | |||
painter.drawPixmap(target, self.m_pixmap, source) | |||
# ------------------------------------------------------------- | |||
# Paint (black) pressed keys | |||
for i in range(len(self.m_enabledKeys)): | |||
note = self.m_enabledKeys[i] | |||
pos = self._getRectFromMidiNote(note) | |||
if not self._isNoteBlack(note): | |||
continue | |||
if note < 36: | |||
# cannot paint this note | |||
continue | |||
elif note < 48: | |||
octave = 0 | |||
elif note < 60: | |||
octave = 1 | |||
elif note < 72: | |||
octave = 2 | |||
elif note < 84: | |||
octave = 3 | |||
elif note < 96: | |||
octave = 4 | |||
elif note < 108: | |||
octave = 5 | |||
elif note < 120: | |||
octave = 6 | |||
elif note < 132: | |||
octave = 7 | |||
else: | |||
# cannot paint this note either | |||
continue | |||
if self.m_pixmap_mode == self.VERTICAL: | |||
octave = self.m_octaves - octave - 1 | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
target = QRectF(pos.x() + (self.p_width * octave), 0, pos.width(), pos.height()) | |||
source = QRectF(pos.x(), self.p_height, pos.width(), pos.height()) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
target = QRectF(pos.x(), pos.y() + (self.p_height * octave), pos.width(), pos.height()) | |||
source = QRectF(self.p_width, pos.y(), pos.width(), pos.height()) | |||
else: | |||
return | |||
painter.drawPixmap(target, self.m_pixmap, source) | |||
# Paint C-number note info | |||
painter.setFont(self.m_font) | |||
painter.setPen(Qt.black) | |||
for i in range(self.m_octaves): | |||
if self.m_pixmap_mode == self.HORIZONTAL: | |||
painter.drawText(i * 144, 48, 18, 18, Qt.AlignCenter, "C%i" % int(i + 2)) | |||
elif self.m_pixmap_mode == self.VERTICAL: | |||
painter.drawText(45, (self.m_octaves * 144) - (i * 144) - 16, 18, 18, Qt.AlignCenter, "C%i" % int(i + 2)) | |||
@pyqtSlot() | |||
def slot_updateOnce(self): | |||
if self.m_needsUpdate: | |||
self.update() | |||
self.m_needsUpdate = False | |||
def _isNoteBlack(self, note): | |||
baseNote = note % 12 | |||
return bool(baseNote in (1, 3, 6, 8, 10)) | |||
def _getRectFromMidiNote(self, note): | |||
return self.m_midi_map.get(str(note % 12)) |