Chris@923: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@923: 
Chris@923: /*
Chris@923:     Sonic Visualiser
Chris@923:     An audio file viewer and annotation editor.
Chris@923:     Centre for Digital Music, Queen Mary, University of London.
Chris@923:     
Chris@923:     This program is free software; you can redistribute it and/or
Chris@923:     modify it under the terms of the GNU General Public License as
Chris@923:     published by the Free Software Foundation; either version 2 of the
Chris@923:     License, or (at your option) any later version.  See the file
Chris@923:     COPYING included with this distribution for more information.
Chris@923: */
Chris@923: 
Chris@923: #include "LevelPanWidget.h"
Chris@923: 
Chris@923: #include <QPainter>
Chris@923: #include <QMouseEvent>
Chris@923: #include <QWheelEvent>
Chris@923: 
Chris@923: #include "layer/ColourMapper.h"
Chris@925: #include "base/AudioLevel.h"
Chris@923: 
Chris@923: #include <iostream>
Chris@926: #include <cmath>
Chris@940: #include <cassert>
Chris@923: 
Chris@923: using std::cerr;
Chris@923: using std::endl;
Chris@923: 
Chris@940: static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
Chris@923: static const int maxPan = 2; // range is -maxPan to maxPan
Chris@923: 
Chris@923: LevelPanWidget::LevelPanWidget(QWidget *parent) :
Chris@923:     QWidget(parent),
Chris@923:     m_level(maxLevel),
Chris@923:     m_pan(0),
Chris@940:     m_editable(true),
Chris@940:     m_includeMute(true)
Chris@923: {
Chris@923: }
Chris@923: 
Chris@923: LevelPanWidget::~LevelPanWidget()
Chris@923: {
Chris@923: }
Chris@923: 
Chris@929: QSize
Chris@929: LevelPanWidget::sizeHint() const
Chris@929: {
Chris@929:     static double ratio = 0.0;
Chris@929:     if (ratio == 0.0) {
Chris@929:         double baseEm;
Chris@929: #ifdef Q_OS_MAC
Chris@929:         baseEm = 17.0;
Chris@929: #else
Chris@929:         baseEm = 15.0;
Chris@929: #endif
Chris@929:         double em = QFontMetrics(QFont()).height();
Chris@929:         ratio = em / baseEm;
Chris@929:     }
Chris@929: 
Chris@929:     int pixels = 40;
Chris@929:     int scaled = int(pixels * ratio + 0.5);
Chris@929:     if (pixels != 0 && scaled == 0) scaled = 1;
Chris@929:     return QSize(scaled, scaled);
Chris@929: }
Chris@929: 
Chris@940: static int
Chris@940: db_to_level(double db)
Chris@940: {
Chris@940:     // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940:     // Levels are: +6 0 -6 -12 -20
Chris@940:     assert(maxLevel == 4);
Chris@940:     if (db > 3.) return 4;
Chris@940:     else if (db > -3.) return 3;
Chris@940:     else if (db > -9.) return 2;
Chris@940:     else if (db > -16.) return 1;
Chris@940:     else return 0;
Chris@940: }
Chris@940: 
Chris@940: static double
Chris@940: level_to_db(int level)
Chris@940: {
Chris@940:     // Only if !m_includeMute, otherwise AudioLevel is used.
Chris@940:     // Levels are: +6 0 -6 -12 -20
Chris@940:     assert(maxLevel == 4);
Chris@940:     if (level >= 4) return 6.;
Chris@940:     else if (level == 3) return 0.;
Chris@940:     else if (level == 2) return -6.;
Chris@940:     else if (level == 1) return -12.;
Chris@940:     else return -20.;
Chris@940: }
Chris@940: 
Chris@923: void
Chris@925: LevelPanWidget::setLevel(float flevel)
Chris@923: {
Chris@940:     int level;
Chris@940:     if (m_includeMute) {
Chris@940:         level = AudioLevel::multiplier_to_fader
Chris@940:             (flevel, maxLevel, AudioLevel::ShortFader);
Chris@940:     } else {
Chris@940:         level = db_to_level(AudioLevel::multiplier_to_dB(flevel));
Chris@940:     }
Chris@925:     if (level < 0) level = 0;
Chris@925:     if (level > maxLevel) level = maxLevel;
Chris@925:     if (level != m_level) {
Chris@925: 	m_level = level;
Chris@925: 	float convertsTo = getLevel();
Chris@925: 	if (fabsf(convertsTo - flevel) > 1e-5) {
Chris@925: 	    emitLevelChanged();
Chris@925: 	}
Chris@925: 	update();
Chris@925:     }
Chris@923: }
Chris@923: 
Chris@940: float
Chris@940: LevelPanWidget::getLevel() const
Chris@940: {
Chris@940:     if (m_includeMute) {
Chris@940:         return float(AudioLevel::fader_to_multiplier
Chris@940:                      (m_level, maxLevel, AudioLevel::ShortFader));
Chris@940:     } else {
Chris@940:         return float(AudioLevel::dB_to_multiplier(level_to_db(m_level)));
Chris@940:     }
Chris@940: }
Chris@940: 
Chris@923: void
Chris@923: LevelPanWidget::setPan(float pan)
Chris@923: {
Chris@923:     m_pan = int(round(pan * maxPan));
Chris@923:     if (m_pan < -maxPan) m_pan = -maxPan;
Chris@923:     if (m_pan > maxPan) m_pan = maxPan;
Chris@923:     update();
Chris@923: }
Chris@923: 
Chris@940: bool
Chris@940: LevelPanWidget::isEditable() const
Chris@940: {
Chris@940:     return m_editable;
Chris@940: }
Chris@940: 
Chris@940: bool
Chris@940: LevelPanWidget::includesMute() const
Chris@940: {
Chris@940:     return m_includeMute;
Chris@940: }
Chris@940: 
Chris@923: void
Chris@923: LevelPanWidget::setEditable(bool editable)
Chris@923: {
Chris@923:     m_editable = editable;
Chris@923:     update();
Chris@923: }
Chris@923: 
Chris@940: void
Chris@940: LevelPanWidget::setIncludeMute(bool include)
Chris@923: {
Chris@940:     m_includeMute = include;
Chris@940:     emitLevelChanged();
Chris@940:     update();
Chris@923: }
Chris@923: 
Chris@923: float
Chris@923: LevelPanWidget::getPan() const
Chris@923: {
Chris@923:     return float(m_pan) / float(maxPan);
Chris@923: }
Chris@923: 
Chris@923: void
Chris@923: LevelPanWidget::emitLevelChanged()
Chris@923: {
Chris@923:     cerr << "emitting levelChanged(" << getLevel() << ")" << endl;
Chris@923:     emit levelChanged(getLevel());
Chris@923: }
Chris@923: 
Chris@923: void
Chris@923: LevelPanWidget::emitPanChanged()
Chris@923: {
Chris@923:     cerr << "emitting panChanged(" << getPan() << ")" << endl;
Chris@923:     emit panChanged(getPan());
Chris@923: }
Chris@923: 
Chris@923: void
Chris@923: LevelPanWidget::mousePressEvent(QMouseEvent *e)
Chris@923: {
Chris@923:     mouseMoveEvent(e);
Chris@923: }
Chris@923: 
Chris@923: void
Chris@923: LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
Chris@923: {
Chris@923:     if (!m_editable) return;
Chris@923:     
Chris@923:     int level, pan;
Chris@929:     toCell(rect(), e->pos(), level, pan);
Chris@923:     if (level == m_level && pan == m_pan) {
Chris@923: 	return;
Chris@923:     }
Chris@923:     if (level != m_level) {
Chris@923: 	m_level = level;
Chris@923: 	emitLevelChanged();
Chris@923:     }
Chris@923:     if (pan != m_pan) {
Chris@923: 	m_pan = pan;
Chris@923: 	emitPanChanged();
Chris@923:     }
Chris@923:     update();
Chris@923: }
Chris@923: 
Chris@923: void
Chris@923: LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
Chris@923: {
Chris@923:     mouseMoveEvent(e);
Chris@923: }
Chris@923: 
Chris@923: void
Chris@923: LevelPanWidget::wheelEvent(QWheelEvent *e)
Chris@923: {
Chris@923:     if (e->modifiers() & Qt::ControlModifier) {
Chris@923: 	if (e->delta() > 0) {
Chris@923: 	    if (m_pan < maxPan) {
Chris@923: 		++m_pan;
Chris@923: 		emitPanChanged();
Chris@923: 		update();
Chris@923: 	    }
Chris@923: 	} else {
Chris@923: 	    if (m_pan > -maxPan) {
Chris@923: 		--m_pan;
Chris@923: 		emitPanChanged();
Chris@923: 		update();
Chris@923: 	    }
Chris@923: 	}
Chris@923:     } else {
Chris@923: 	if (e->delta() > 0) {
Chris@923: 	    if (m_level < maxLevel) {
Chris@923: 		++m_level;
Chris@923: 		emitLevelChanged();
Chris@923: 		update();
Chris@923: 	    }
Chris@923: 	} else {
Chris@923: 	    if (m_level > 0) {
Chris@923: 		--m_level;
Chris@923: 		emitLevelChanged();
Chris@923: 		update();
Chris@923: 	    }
Chris@923: 	}
Chris@923:     }
Chris@923: }
Chris@923: 
Chris@923: void
Chris@929: LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
Chris@923: {
Chris@929:     double w = rect.width(), h = rect.height();
Chris@929: 
Chris@923:     int npan = maxPan * 2 + 1;
Chris@924:     int nlevel = maxLevel + 1;
Chris@929: 
Chris@924:     double wcell = w / npan, hcell = h / nlevel;
Chris@929: 
Chris@932:     level = int((h - (loc.y() - rect.y())) / hcell);
Chris@924:     if (level < 0) level = 0;
Chris@923:     if (level > maxLevel) level = maxLevel;
Chris@929: 
Chris@932:     pan = int((loc.x() - rect.x()) / wcell) - maxPan;
Chris@923:     if (pan < -maxPan) pan = -maxPan;
Chris@923:     if (pan > maxPan) pan = maxPan;
Chris@923: }
Chris@923: 
Chris@923: QSizeF
Chris@929: LevelPanWidget::cellSize(QRectF rect) const
Chris@923: {
Chris@929:     double w = rect.width(), h = rect.height();
Chris@923:     int npan = maxPan * 2 + 1;
Chris@924:     int nlevel = maxLevel + 1;
Chris@924:     double wcell = w / npan, hcell = h / nlevel;
Chris@923:     return QSizeF(wcell, hcell);
Chris@923: }
Chris@923: 
Chris@923: QPointF
Chris@929: LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
Chris@923: {
Chris@929:     QSizeF cs = cellSize(rect);
Chris@932:     return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
Chris@932: 		   rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
Chris@923: }
Chris@923: 
Chris@923: QSizeF
Chris@929: LevelPanWidget::cellLightSize(QRectF rect) const
Chris@923: {
Chris@923:     double extent = 3. / 4.;
Chris@929:     QSizeF cs = cellSize(rect);
Chris@923:     double m = std::min(cs.width(), cs.height());
Chris@923:     return QSizeF(m * extent, m * extent);
Chris@923: }
Chris@923: 
Chris@923: QRectF
Chris@929: LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
Chris@923: {
Chris@929:     QSizeF cls = cellLightSize(rect);
Chris@929:     QPointF cc = cellCentre(rect, level, pan);
Chris@923:     return QRectF(cc.x() - cls.width() / 2., 
Chris@923: 		  cc.y() - cls.height() / 2.,
Chris@923: 		  cls.width(),
Chris@923: 		  cls.height());
Chris@923: }
Chris@923: 
Chris@923: double
Chris@929: LevelPanWidget::thinLineWidth(QRectF rect) const
Chris@923: {
Chris@929:     double tw = ceil(rect.width() / (maxPan * 2. * 10.));
Chris@929:     double th = ceil(rect.height() / (maxLevel * 10.));
Chris@923:     return std::min(th, tw);
Chris@923: }
Chris@923: 
Chris@941: static QColor
Chris@941: level_to_colour(int level)
Chris@941: {
Chris@941:     assert(maxLevel == 4);
Chris@941:     if (level == 0) return Qt::black;
Chris@941:     else if (level == 1) return QColor(80, 0, 0);
Chris@941:     else if (level == 2) return QColor(160, 0, 0);
Chris@941:     else if (level == 3) return QColor(255, 0, 0);
Chris@941:     else return QColor(255, 255, 0);
Chris@941: }
Chris@941: 
Chris@923: void
Chris@929: LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
Chris@923: {
Chris@929:     QPainter paint(dev);
Chris@923: 
Chris@923:     paint.setRenderHint(QPainter::Antialiasing, true);
Chris@923: 
Chris@923:     QPen pen;
Chris@923: 
Chris@929:     double thin = thinLineWidth(rect);
Chris@938: 
Chris@923:     pen.setColor(QColor(127, 127, 127, 127));
Chris@929:     pen.setWidthF(cellLightSize(rect).width() + thin);
Chris@923:     pen.setCapStyle(Qt::RoundCap);
Chris@923:     paint.setPen(pen);
Chris@923: 
Chris@923:     for (int pan = -maxPan; pan <= maxPan; ++pan) {
Chris@929: 	paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
Chris@924:     }
Chris@924: 
Chris@924:     if (isEnabled()) {
Chris@924: 	pen.setColor(Qt::black);
Chris@924:     } else {
Chris@924: 	pen.setColor(Qt::darkGray);
Chris@923:     }
Chris@929: 
Chris@940:     if (!asIfEditable && m_includeMute && m_level == 0) {
Chris@929:         pen.setWidthF(thin * 2);
Chris@929:         pen.setCapStyle(Qt::RoundCap);
Chris@929:         paint.setPen(pen);
Chris@930:         paint.drawLine(cellCentre(rect, 0, -maxPan),
Chris@930:                        cellCentre(rect, maxLevel, maxPan));
Chris@930:         paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
Chris@930:                        cellCentre(rect, 0, maxPan));
Chris@929:         return;
Chris@929:     }
Chris@923:     
Chris@923:     pen.setWidthF(thin);
Chris@923:     pen.setCapStyle(Qt::FlatCap);
Chris@923:     paint.setPen(pen);
Chris@923:     
Chris@924:     for (int level = 0; level <= m_level; ++level) {
Chris@924: 	if (isEnabled()) {
Chris@941: 	    paint.setBrush(level_to_colour(level));
Chris@924: 	}
Chris@929: 	QRectF clr = cellLightRect(rect, level, m_pan);
Chris@940: 	if (m_includeMute && m_level == 0) {
Chris@924: 	    paint.drawLine(clr.topLeft(), clr.bottomRight());
Chris@924: 	    paint.drawLine(clr.bottomLeft(), clr.topRight());
Chris@924: 	} else {
Chris@924: 	    paint.drawEllipse(clr);
Chris@924: 	}
Chris@923:     }
Chris@923: }
Chris@923: 
Chris@929: void
Chris@929: LevelPanWidget::paintEvent(QPaintEvent *)
Chris@929: {
Chris@929:     renderTo(this, rect(), m_editable);
Chris@929: }
Chris@923: 
Chris@929: 
Chris@929: