Mercurial > hg > svgui
view widgets/LevelPanWidget.cpp @ 1303:13f5f84fbfad
Collect the bits of bookkeeping for mouse wheel events, and use in all widgets
author | Chris Cannam |
---|---|
date | Fri, 22 Jun 2018 17:19:48 +0100 |
parents | f3d3fab250ac |
children | a575dae05fbf |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. 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 (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "LevelPanWidget.h" #include <QPainter> #include <QMouseEvent> #include <QWheelEvent> #include "layer/ColourMapper.h" #include "base/AudioLevel.h" #include "WidgetScale.h" #include <iostream> #include <cmath> #include <cassert> using std::cerr; using std::endl; /** * Gain and pan scales: * * Gain: we have 5 circles vertically in the display, each of which * has half-circle and full-circle versions, and we also have "no * circles", so there are in total 11 distinct levels, which we refer * to as "notches" and number 0-10. (We use "notch" because "level" is * used by the external API to refer to audio gain.) * * i.e. the levels are represented by these (schematic, rotated to * horizontal) displays: * * 0 X * 1 [ * 2 [] * 3 [][ * ... * 9 [][][][][ * 10 [][][][][] * * If we have mute enabled, then we map the range 0-10 to gain using * AudioLevel::fader_to_* with the ShortFader type, which treats fader * 0 as muted. If mute is disabled, then we map the range 1-10. * * We can also disable half-circles, which leaves the range unchanged * but limits the notches to even values. * * Pan: we have 5 columns with no finer resolution, so we only have 2 * possible pan values on each side of centre. */ static const int maxPan = 2; // range is -maxPan to maxPan LevelPanWidget::LevelPanWidget(QWidget *parent) : QWidget(parent), m_minNotch(0), m_maxNotch(10), m_notch(m_maxNotch), m_pan(0), m_monitorLeft(-1), m_monitorRight(-1), m_editable(true), m_editing(false), m_includeMute(true), m_includeHalfSteps(true) { setToolTip(tr("Drag vertically to adjust level, horizontally to adjust pan")); setLevel(1.0); setPan(0.0); } LevelPanWidget::~LevelPanWidget() { } void LevelPanWidget::setToDefault() { setLevel(1.0); setPan(0.0); emitLevelChanged(); emitPanChanged(); } QSize LevelPanWidget::sizeHint() const { return WidgetScale::scaleQSize(QSize(40, 40)); } int LevelPanWidget::clampNotch(int notch) const { if (notch < m_minNotch) notch = m_minNotch; if (notch > m_maxNotch) notch = m_maxNotch; if (!m_includeHalfSteps) { notch = (notch / 2) * 2; } return notch; } int LevelPanWidget::clampPan(int pan) const { if (pan < -maxPan) pan = -maxPan; if (pan > maxPan) pan = maxPan; return pan; } int LevelPanWidget::audioLevelToNotch(float audioLevel) const { int notch = AudioLevel::multiplier_to_fader (audioLevel, m_maxNotch, AudioLevel::ShortFader); return clampNotch(notch); } float LevelPanWidget::notchToAudioLevel(int notch) const { return float(AudioLevel::fader_to_multiplier (notch, m_maxNotch, AudioLevel::ShortFader)); } void LevelPanWidget::setLevel(float level) { int notch = audioLevelToNotch(level); if (notch != m_notch) { m_notch = notch; float convertsTo = getLevel(); if (fabsf(convertsTo - level) > 1e-5) { emitLevelChanged(); } update(); } SVCERR << "setLevel: level " << level << " -> notch " << m_notch << " (which converts back to level " << getLevel() << ")" << endl; } float LevelPanWidget::getLevel() const { return notchToAudioLevel(m_notch); } int LevelPanWidget::audioPanToPan(float audioPan) const { int pan = int(round(audioPan * maxPan)); pan = clampPan(pan); return pan; } float LevelPanWidget::panToAudioPan(int pan) const { return float(pan) / float(maxPan); } void LevelPanWidget::setPan(float fpan) { int pan = audioPanToPan(fpan); if (pan != m_pan) { m_pan = pan; update(); } } float LevelPanWidget::getPan() const { return panToAudioPan(m_pan); } void LevelPanWidget::setMonitoringLevels(float left, float right) { m_monitorLeft = left; m_monitorRight = right; update(); } bool LevelPanWidget::isEditable() const { return m_editable; } bool LevelPanWidget::includesMute() const { return m_includeMute; } void LevelPanWidget::setEditable(bool editable) { m_editable = editable; update(); } void LevelPanWidget::setIncludeMute(bool include) { m_includeMute = include; if (m_includeMute) { m_minNotch = 0; } else { m_minNotch = 1; } emitLevelChanged(); update(); } void LevelPanWidget::emitLevelChanged() { emit levelChanged(getLevel()); } void LevelPanWidget::emitPanChanged() { emit panChanged(getPan()); } void LevelPanWidget::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton || ((e->button() == Qt::LeftButton) && (e->modifiers() & Qt::ControlModifier))) { setToDefault(); } else if (e->button() == Qt::LeftButton) { m_editing = true; mouseMoveEvent(e); } } void LevelPanWidget::mouseReleaseEvent(QMouseEvent *e) { mouseMoveEvent(e); m_editing = false; } void LevelPanWidget::mouseMoveEvent(QMouseEvent *e) { if (!m_editable) return; if (!m_editing) return; int notch = coordsToNotch(rect(), e->pos()); int pan = coordsToPan(rect(), e->pos()); if (notch == m_notch && pan == m_pan) { return; } if (notch != m_notch) { m_notch = notch; emitLevelChanged(); } if (pan != m_pan) { m_pan = pan; emitPanChanged(); } update(); } void LevelPanWidget::wheelEvent(QWheelEvent *e) { int delta = m_wheelCounter.count(e); if (delta == 0) { return; } if (e->modifiers() & Qt::ControlModifier) { m_pan = clampPan(m_pan + delta); emitPanChanged(); update(); } else { m_notch = clampNotch(m_notch + delta); emitLevelChanged(); update(); } } int LevelPanWidget::coordsToNotch(QRectF rect, QPointF loc) const { double h = rect.height(); int nnotch = m_maxNotch + 1; double cell = h / nnotch; int notch = int((h - (loc.y() - rect.y())) / cell); notch = clampNotch(notch); return notch; } int LevelPanWidget::coordsToPan(QRectF rect, QPointF loc) const { double w = rect.width(); int npan = maxPan * 2 + 1; double cell = w / npan; int pan = int((loc.x() - rect.x()) / cell) - maxPan; pan = clampPan(pan); return pan; } QSizeF LevelPanWidget::cellSize(QRectF rect) const { double w = rect.width(), h = rect.height(); int ncol = maxPan * 2 + 1; int nrow = m_maxNotch/2; double wcell = w / ncol, hcell = h / nrow; return QSizeF(wcell, hcell); } QPointF LevelPanWidget::cellCentre(QRectF rect, int row, int col) const { QSizeF cs = cellSize(rect); return QPointF(rect.x() + cs.width() * (col + maxPan) + cs.width() / 2., rect.y() + rect.height() - cs.height() * (row + 1) + cs.height() / 2.); } QSizeF LevelPanWidget::cellLightSize(QRectF rect) const { double extent = 3. / 4.; QSizeF cs = cellSize(rect); double m = std::min(cs.width(), cs.height()); return QSizeF(m * extent, m * extent); } QRectF LevelPanWidget::cellLightRect(QRectF rect, int row, int col) const { QSizeF cls = cellLightSize(rect); QPointF cc = cellCentre(rect, row, col); return QRectF(cc.x() - cls.width() / 2., cc.y() - cls.height() / 2., cls.width(), cls.height()); } double LevelPanWidget::thinLineWidth(QRectF rect) const { double tw = ceil(rect.width() / (maxPan * 2. * 10.)); double th = ceil(rect.height() / (m_maxNotch/2 * 10.)); return std::min(th, tw); } QRectF LevelPanWidget::cellOutlineRect(QRectF rect, int row, int col) const { QRectF clr = cellLightRect(rect, row, col); double adj = thinLineWidth(rect)/2; return clr.adjusted(-adj, -adj, adj, adj); } QColor LevelPanWidget::notchToColour(int notch) const { if (notch < 3) return Qt::black; if (notch < 5) return QColor(80, 0, 0); if (notch < 7) return QColor(160, 0, 0); if (notch < 9) return QColor(255, 0, 0); return QColor(255, 255, 0); } void LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const { QPainter paint(dev); paint.setRenderHint(QPainter::Antialiasing, true); QPen pen; double thin = thinLineWidth(rect); QColor columnBackground = QColor(180, 180, 180); pen.setColor(columnBackground); pen.setWidthF(cellLightSize(rect).width() + thin); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); paint.setBrush(Qt::NoBrush); for (int pan = -maxPan; pan <= maxPan; ++pan) { paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, m_maxNotch/2 - 1, pan)); } bool monitoring = (m_monitorLeft > 0.f || m_monitorRight > 0.f); if (isEnabled()) { pen.setColor(Qt::black); } else { pen.setColor(Qt::darkGray); } if (!asIfEditable && m_includeMute && m_notch == 0) { // The X for mute takes up the whole display when we're not // being rendered in editable style pen.setWidthF(thin * 2); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); paint.drawLine(cellCentre(rect, 0, -maxPan), cellCentre(rect, m_maxNotch/2 - 1, maxPan)); paint.drawLine(cellCentre(rect, m_maxNotch/2 - 1, -maxPan), cellCentre(rect, 0, maxPan)); } else { // the normal case // pen a bit less thin than in theory, so that we can erase // semi-circles later without leaving a faint edge pen.setWidthF(thin * 0.8); pen.setCapStyle(Qt::FlatCap); paint.setPen(pen); if (m_includeMute && m_notch == 0) { QRectF clr = cellLightRect(rect, 0, m_pan); paint.drawLine(clr.topLeft(), clr.bottomRight()); paint.drawLine(clr.bottomLeft(), clr.topRight()); } else { for (int notch = 1; notch <= m_notch; notch += 2) { if (isEnabled() && !monitoring) { paint.setBrush(notchToColour(notch)); } QRectF clr = cellLightRect(rect, notch/2, m_pan); paint.drawEllipse(clr); } if (m_notch % 2 != 0) { QRectF clr = cellOutlineRect(rect, (m_notch-1)/2, m_pan); paint.save(); paint.setPen(Qt::NoPen); paint.setBrush(columnBackground); paint.drawPie(clr, 0, 180 * 16); paint.restore(); } } } if (monitoring) { paint.setPen(Qt::NoPen); for (int pan = -maxPan; pan <= maxPan; ++pan) { float audioPan = panToAudioPan(pan); float audioLevel; if (audioPan < 0.f) { audioLevel = m_monitorLeft + m_monitorRight * (1.f + audioPan); } else { audioLevel = m_monitorRight + m_monitorLeft * (1.f - audioPan); } int notchHere = audioLevelToNotch(audioLevel); for (int notch = 1; notch <= notchHere; notch += 2) { paint.setBrush(notchToColour(notch)); QRectF clr = cellLightRect(rect, (notch-1)/2, pan); double adj = thinLineWidth(rect)/2; clr = clr.adjusted(adj, adj, -adj, -adj); if (notch + 2 > notchHere && notchHere % 2 != 0) { paint.drawPie(clr, 180 * 16, 180 * 16); } else { paint.drawEllipse(clr); } } } paint.setPen(pen); paint.setBrush(Qt::NoBrush); } } void LevelPanWidget::paintEvent(QPaintEvent *) { renderTo(this, rect(), m_editable); } void LevelPanWidget::enterEvent(QEvent *e) { QWidget::enterEvent(e); emit mouseEntered(); } void LevelPanWidget::leaveEvent(QEvent *e) { QWidget::enterEvent(e); emit mouseLeft(); }