Chris@58: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@59: Sonic Visualiser Chris@59: An audio file viewer and annotation editor. Chris@59: Centre for Digital Music, Queen Mary, University of London. Chris@0: Chris@59: This program is free software; you can redistribute it and/or Chris@59: modify it under the terms of the GNU General Public License as Chris@59: published by the Free Software Foundation; either version 2 of the Chris@59: License, or (at your option) any later version. See the file Chris@59: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: /** Chris@0: * A rotary dial widget. Chris@0: * Chris@0: * Based on an original design by Thorsten Wilms. Chris@0: * Chris@0: * Implemented as a widget for the Rosegarden MIDI and audio sequencer Chris@0: * and notation editor by Chris Cannam. Chris@0: * Chris@0: * Extracted into a standalone Qt3 widget by Pedro Lopez-Cabanillas Chris@0: * and adapted for use in QSynth. Chris@0: * Chris@0: * Ported to Qt4 by Chris Cannam. Chris@0: * Chris@168: * This file copyright 2003-2006 Chris Cannam, copyright 2005 Pedro Chris@182: * Lopez-Cabanillas, copyright 2006 Queen Mary, University of London. Chris@0: * Chris@0: * This program is free software; you can redistribute it and/or Chris@0: * modify it under the terms of the GNU General Public License as Chris@0: * published by the Free Software Foundation; either version 2 of the Chris@0: * License, or (at your option) any later version. See the file Chris@0: * COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include "AudioDial.h" Chris@0: Chris@167: #include "base/RangeMapper.h" Chris@167: Chris@0: #include Chris@0: #include Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@34: #include Chris@0: Chris@382: #include "base/Profiler.h" Chris@382: Chris@682: Chris@682: Chris@0: Chris@0: Chris@0: //!!! Pedro updated his version to use my up/down response code from RG -- need to grab that code in preference to this version from Rui Chris@0: Chris@0: Chris@0: //------------------------------------------------------------------------- Chris@0: // AudioDial - Instance knob widget class. Chris@0: // Chris@0: Chris@0: #define AUDIO_DIAL_MIN (0.25 * M_PI) Chris@0: #define AUDIO_DIAL_MAX (1.75 * M_PI) Chris@0: #define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) Chris@0: Chris@0: Chris@212: //static int dialsExtant = 0; Chris@189: Chris@189: Chris@0: // Constructor. Chris@0: AudioDial::AudioDial(QWidget *parent) : Chris@0: QDial(parent), Chris@1443: m_knobColor(Qt::black), // shorthand for "background colour" in paint() Chris@1443: m_meterColor(Qt::white), // shorthand for "foreground colour" in paint() Chris@167: m_defaultValue(0), Chris@344: m_defaultMappedValue(0), Chris@168: m_mappedValue(0), Chris@168: m_noMappedUpdate(false), Chris@187: m_showTooltip(true), Chris@1408: m_rangeMapper(nullptr) Chris@0: { Chris@0: m_mouseDial = false; Chris@0: m_mousePressed = false; Chris@212: // ++dialsExtant; Chris@0: } Chris@0: Chris@0: Chris@0: // Destructor. Chris@0: AudioDial::~AudioDial (void) Chris@0: { Chris@167: delete m_rangeMapper; Chris@212: // --dialsExtant; Chris@167: } Chris@167: Chris@167: Chris@167: void AudioDial::setRangeMapper(RangeMapper *mapper) Chris@167: { Chris@682: // cerr << "AudioDial[" << this << "][\"" << objectName() << "\"::setRangeMapper(" << mapper << ") [current is " << m_rangeMapper << "] (have " << dialsExtant << " dials extant)" << endl; Chris@189: Chris@187: if (m_rangeMapper == mapper) return; Chris@187: Chris@170: if (!m_rangeMapper && mapper) { Chris@168: connect(this, SIGNAL(valueChanged(int)), Chris@168: this, SLOT(updateMappedValue(int))); Chris@168: } Chris@170: Chris@167: delete m_rangeMapper; Chris@167: m_rangeMapper = mapper; Chris@170: Chris@187: updateMappedValue(value()); Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::paintEvent(QPaintEvent *) Chris@0: { Chris@382: Profiler profiler("AudioDial::paintEvent"); Chris@382: Chris@0: QPainter paint; Chris@0: Chris@908: double angle = AUDIO_DIAL_MIN // offset Chris@1266: + (AUDIO_DIAL_RANGE * Chris@1266: (double(QDial::value() - QDial::minimum()) / Chris@1266: (double(QDial::maximum() - QDial::minimum())))); Chris@0: int degrees = int(angle * 180.0 / M_PI); Chris@0: Chris@0: int ns = notchSize(); Chris@0: int numTicks = 1 + (maximum() + ns - minimum()) / ns; Chris@1266: Chris@0: QColor knobColor(m_knobColor); Chris@1443: if (knobColor == Qt::black) { Chris@1476: knobColor = palette().window().color().lighter(150); Chris@1443: } Chris@1443: bool knobIsDark = Chris@1443: (knobColor.red() + knobColor.green() + knobColor.blue() <= 384); Chris@0: Chris@0: QColor meterColor(m_meterColor); Chris@1443: if (!isEnabled()) { Chris@1266: meterColor = palette().mid().color(); Chris@1443: } else if (m_meterColor == Qt::white) { Chris@1443: if (knobIsDark) { Chris@1545: meterColor = palette().text().color(); Chris@1443: } else { Chris@1443: meterColor = palette().highlight().color(); Chris@1443: } Chris@1443: } Chris@0: Chris@1443: QColor notchColor(palette().dark().color()); Chris@1443: if (knobIsDark) { Chris@1545: notchColor = palette().text().color(); Chris@1443: } Chris@1443: Chris@0: int m_size = width() < height() ? width() : height(); Chris@0: int scale = 1; Chris@249: int width = m_size - 2*scale; Chris@0: Chris@0: paint.begin(this); Chris@0: paint.setRenderHint(QPainter::Antialiasing, true); Chris@0: paint.translate(1, 1); Chris@0: Chris@0: QPen pen; Chris@0: QColor c; Chris@0: Chris@0: // Knob body and face... Chris@0: Chris@0: c = knobColor; Chris@0: pen.setColor(knobColor); Chris@0: pen.setWidth(scale * 2); Chris@0: pen.setCapStyle(Qt::FlatCap); Chris@1266: Chris@0: paint.setPen(pen); Chris@0: paint.setBrush(c); Chris@0: Chris@0: int indent = (int)(width * 0.15 + 1); Chris@0: Chris@0: paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent); Chris@0: Chris@0: pen.setWidth(3 * scale); Chris@0: int pos = indent-1 + (width-2*indent) / 20; Chris@0: int darkWidth = (width-2*indent) * 3 / 4; Chris@0: while (darkWidth) { Chris@1443: if (knobIsDark) { Chris@1476: c = c.darker(102); Chris@1443: } else { Chris@1476: c = c.lighter(102); Chris@1443: } Chris@1266: pen.setColor(c); Chris@1266: paint.setPen(pen); Chris@1266: paint.drawEllipse(pos, pos, darkWidth, darkWidth); Chris@1266: if (!--darkWidth) break; Chris@1266: paint.drawEllipse(pos, pos, darkWidth, darkWidth); Chris@1266: if (!--darkWidth) break; Chris@1266: paint.drawEllipse(pos, pos, darkWidth, darkWidth); Chris@1266: ++pos; --darkWidth; Chris@0: } Chris@0: Chris@0: // Tick notches... Chris@0: Chris@34: if ( notchesVisible() ) { Chris@1443: pen.setColor(notchColor); Chris@1266: pen.setWidth(scale); Chris@1266: paint.setPen(pen); Chris@1266: for (int i = 0; i < numTicks; ++i) { Chris@1266: int div = numTicks; Chris@1266: if (div > 1) --div; Chris@1266: drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, Chris@1266: width, true); Chris@1266: } Chris@0: } Chris@0: Chris@0: // The bright metering bit... Chris@0: Chris@0: c = meterColor; Chris@0: pen.setColor(c); Chris@0: pen.setWidth(indent); Chris@0: paint.setPen(pen); Chris@0: Chris@682: // cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << endl; Chris@0: Chris@0: int arcLen = -(degrees - 45) * 16; Chris@0: if (arcLen == 0) arcLen = -16; Chris@0: Chris@0: paint.drawArc(indent/2, indent/2, Chris@1266: width-indent, width-indent, (180 + 45) * 16, arcLen); Chris@0: Chris@0: paint.setBrush(Qt::NoBrush); Chris@0: Chris@0: // Shadowing... Chris@0: Chris@0: pen.setWidth(scale); Chris@0: paint.setPen(pen); Chris@0: Chris@0: // Knob shadow... Chris@0: Chris@0: int shadowAngle = -720; Chris@1443: if (knobIsDark) { Chris@1476: c = knobColor.lighter(); Chris@1443: } else { Chris@1476: c = knobColor.darker(); Chris@1443: } Chris@0: for (int arc = 120; arc < 2880; arc += 240) { Chris@1266: pen.setColor(c); Chris@1266: paint.setPen(pen); Chris@1266: paint.drawArc(indent, indent, Chris@1266: width-2*indent, width-2*indent, shadowAngle + arc, 240); Chris@1266: paint.drawArc(indent, indent, Chris@1266: width-2*indent, width-2*indent, shadowAngle - arc, 240); Chris@1443: if (knobIsDark) { Chris@1476: c = c.darker(110); Chris@1443: } else { Chris@1476: c = c.lighter(110); Chris@1443: } Chris@0: } Chris@0: Chris@738: // Scale shadow, omitting the bottom part... Chris@0: Chris@0: shadowAngle = 2160; Chris@738: c = palette().shadow().color(); Chris@738: for (int i = 0; i < 5; ++i) { Chris@1266: pen.setColor(c); Chris@1266: paint.setPen(pen); Chris@738: int arc = i * 240 + 120; Chris@1266: paint.drawArc(scale/2, scale/2, Chris@1266: width-scale, width-scale, shadowAngle + arc, 240); Chris@1476: c = c.lighter(110); Chris@738: } Chris@738: c = palette().shadow().color(); Chris@738: for (int i = 0; i < 12; ++i) { Chris@1266: pen.setColor(c); Chris@1266: paint.setPen(pen); Chris@738: int arc = i * 240 + 120; Chris@1266: paint.drawArc(scale/2, scale/2, Chris@1266: width-scale, width-scale, shadowAngle - arc, 240); Chris@1476: c = c.lighter(110); Chris@0: } Chris@0: Chris@0: // Scale ends... Chris@0: Chris@1443: if (knobIsDark) { Chris@1443: pen.setColor(palette().mid().color()); Chris@1443: } else { Chris@1443: pen.setColor(palette().shadow().color()); Chris@1443: } Chris@0: pen.setWidth(scale); Chris@0: paint.setPen(pen); Chris@0: for (int i = 0; i < numTicks; ++i) { Chris@1266: if (i != 0 && i != numTicks - 1) continue; Chris@1266: int div = numTicks; Chris@1266: if (div > 1) --div; Chris@1266: drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, Chris@1266: width, false); Chris@0: } Chris@0: Chris@0: // Pointer notch... Chris@0: Chris@908: double hyp = double(width) / 2.0; Chris@908: double len = hyp - indent; Chris@0: --len; Chris@0: Chris@908: double x0 = hyp; Chris@908: double y0 = hyp; Chris@0: Chris@908: double x = hyp - len * sin(angle); Chris@908: double y = hyp + len * cos(angle); Chris@0: Chris@1443: c = notchColor; Chris@1443: if (isEnabled()) { Chris@1443: if (knobIsDark) { Chris@1476: c = c.lighter(130); Chris@1443: } else { Chris@1476: c = c.darker(130); Chris@1443: } Chris@1443: } Chris@1443: pen.setColor(c); Chris@0: pen.setWidth(scale * 2); Chris@0: paint.setPen(pen); Chris@0: paint.drawLine(int(x0), int(y0), int(x), int(y)); Chris@0: Chris@0: paint.end(); Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::drawTick(QPainter &paint, Chris@1266: double angle, int size, bool internal) Chris@0: { Chris@908: double hyp = double(size) / 2.0; Chris@908: double x0 = hyp - (hyp - 1) * sin(angle); Chris@908: double y0 = hyp + (hyp - 1) * cos(angle); Chris@0: Chris@0: // cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl; Chris@0: Chris@0: if (internal) { Chris@0: Chris@1266: double len = hyp / 4; Chris@1266: double x1 = hyp - (hyp - len) * sin(angle); Chris@1266: double y1 = hyp + (hyp - len) * cos(angle); Chris@1266: Chris@1266: paint.drawLine(int(x0), int(y0), int(x1), int(y1)); Chris@0: Chris@0: } else { Chris@0: Chris@1266: double len = hyp / 4; Chris@1266: double x1 = hyp - (hyp + len) * sin(angle); Chris@1266: double y1 = hyp + (hyp + len) * cos(angle); Chris@0: Chris@1266: paint.drawLine(int(x0), int(y0), int(x1), int(y1)); Chris@0: } Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::setKnobColor(const QColor& color) Chris@0: { Chris@0: m_knobColor = color; Chris@0: update(); Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::setMeterColor(const QColor& color) Chris@0: { Chris@0: m_meterColor = color; Chris@0: update(); Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::setMouseDial(bool mouseDial) Chris@0: { Chris@0: m_mouseDial = mouseDial; Chris@0: } Chris@0: Chris@0: Chris@34: void AudioDial::setDefaultValue(int defaultValue) Chris@34: { Chris@34: m_defaultValue = defaultValue; Chris@344: if (m_rangeMapper) { Chris@344: m_defaultMappedValue = m_rangeMapper->getValueForPosition(defaultValue); Chris@344: } Chris@34: } Chris@34: Chris@219: void AudioDial::setValue(int value) Chris@219: { Chris@219: QDial::setValue(value); Chris@219: updateMappedValue(value); Chris@219: } Chris@219: Chris@908: void AudioDial::setDefaultMappedValue(double value) Chris@344: { Chris@344: m_defaultMappedValue = value; Chris@344: if (m_rangeMapper) { Chris@344: m_defaultValue = m_rangeMapper->getPositionForValue(value); Chris@344: } Chris@344: } Chris@219: Chris@908: void AudioDial::setMappedValue(double mappedValue) Chris@177: { Chris@177: if (m_rangeMapper) { Chris@177: int newPosition = m_rangeMapper->getPositionForValue(mappedValue); Chris@190: bool changed = (m_mappedValue != mappedValue); Chris@177: m_mappedValue = mappedValue; Chris@177: m_noMappedUpdate = true; Chris@587: SVDEBUG << "AudioDial::setMappedValue(" << mappedValue << "): new position is " << newPosition << endl; Chris@177: if (newPosition != value()) { Chris@177: setValue(newPosition); Chris@190: } else if (changed) { Chris@177: emit valueChanged(newPosition); Chris@177: } Chris@177: m_noMappedUpdate = false; Chris@177: } else { Chris@187: setValue(int(mappedValue)); Chris@177: } Chris@177: } Chris@177: Chris@177: Chris@168: void AudioDial::setShowToolTip(bool show) Chris@168: { Chris@168: m_showTooltip = show; Chris@168: m_noMappedUpdate = true; Chris@168: updateMappedValue(value()); Chris@168: m_noMappedUpdate = false; Chris@168: } Chris@168: Chris@168: Chris@908: double AudioDial::mappedValue() const Chris@167: { Chris@168: if (m_rangeMapper) { Chris@587: // SVDEBUG << "AudioDial::mappedValue(): value = " << value() << ", mappedValue = " << m_mappedValue << endl; Chris@168: return m_mappedValue; Chris@168: } Chris@168: return value(); Chris@168: } Chris@168: Chris@168: Chris@168: void AudioDial::updateMappedValue(int value) Chris@168: { Chris@170: if (!m_noMappedUpdate) { Chris@170: if (m_rangeMapper) { Chris@168: m_mappedValue = m_rangeMapper->getValueForPosition(value); Chris@170: } else { Chris@170: m_mappedValue = value; Chris@168: } Chris@168: } Chris@168: Chris@168: if (m_showTooltip) { Chris@168: QString name = objectName(); Chris@1147: QString label; Chris@1147: if (m_rangeMapper) { Chris@1147: label = m_rangeMapper->getLabel(value); Chris@1147: } Chris@168: QString text; Chris@1147: if (label != "") { Chris@1147: if (name != "") { Chris@1147: text = tr("%1: %2").arg(name).arg(label); Chris@1147: } else { Chris@1147: text = label; Chris@1147: } Chris@168: } else { Chris@1147: QString unit = ""; Chris@1147: if (m_rangeMapper) { Chris@1147: unit = m_rangeMapper->getUnit(); Chris@1147: } Chris@1147: if (name != "") { Chris@1147: text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); Chris@1147: } else { Chris@1147: text = tr("%2%3").arg(m_mappedValue).arg(unit); Chris@1147: } Chris@168: } Chris@168: setToolTip(text); Chris@168: } Chris@167: } Chris@167: Chris@344: void Chris@344: AudioDial::setToDefault() Chris@344: { Chris@344: if (m_rangeMapper) { Chris@344: setMappedValue(m_defaultMappedValue); Chris@344: return; Chris@344: } Chris@344: int dv = m_defaultValue; Chris@344: if (dv < minimum()) dv = minimum(); Chris@344: if (dv > maximum()) dv = maximum(); Chris@344: setValue(m_defaultValue); Chris@344: } Chris@167: Chris@0: // Alternate mouse behavior event handlers. Chris@0: void AudioDial::mousePressEvent(QMouseEvent *mouseEvent) Chris@0: { Chris@0: if (m_mouseDial) { Chris@1266: QDial::mousePressEvent(mouseEvent); Chris@187: } else if (mouseEvent->button() == Qt::MidButton || Chris@187: ((mouseEvent->button() == Qt::LeftButton) && Chris@187: (mouseEvent->modifiers() & Qt::ControlModifier))) { Chris@344: setToDefault(); Chris@187: } else if (mouseEvent->button() == Qt::LeftButton) { Chris@1266: m_mousePressed = true; Chris@1266: m_posMouse = mouseEvent->pos(); Chris@34: } Chris@34: } Chris@34: Chris@34: Chris@34: void AudioDial::mouseDoubleClickEvent(QMouseEvent *mouseEvent) Chris@34: { Chris@187: //!!! needs a common base class with Thumbwheel Chris@187: Chris@34: if (m_mouseDial) { Chris@1266: QDial::mouseDoubleClickEvent(mouseEvent); Chris@187: } else if (mouseEvent->button() != Qt::LeftButton) { Chris@187: return; Chris@187: } Chris@167: Chris@187: bool ok = false; Chris@167: Chris@187: if (m_rangeMapper) { Chris@187: Chris@908: double min = m_rangeMapper->getValueForPosition(minimum()); Chris@908: double max = m_rangeMapper->getValueForPosition(maximum()); Chris@187: Chris@187: if (min > max) { Chris@908: double tmp = min; Chris@187: min = max; Chris@187: max = tmp; Chris@187: } Chris@167: Chris@187: QString unit = m_rangeMapper->getUnit(); Chris@187: Chris@187: QString text; Chris@187: if (objectName() != "") { Chris@187: if (unit != "") { Chris@187: text = tr("New value for %1, from %2 to %3 %4:") Chris@187: .arg(objectName()).arg(min).arg(max).arg(unit); Chris@167: } else { Chris@187: text = tr("New value for %1, from %2 to %3:") Chris@187: .arg(objectName()).arg(min).arg(max); Chris@167: } Chris@187: } else { Chris@187: if (unit != "") { Chris@187: text = tr("Enter a new value from %1 to %2 %3:") Chris@187: .arg(min).arg(max).arg(unit); Chris@187: } else { Chris@187: text = tr("Enter a new value from %1 to %2:") Chris@187: .arg(min).arg(max); Chris@168: } Chris@187: } Chris@187: Chris@908: double newValue = QInputDialog::getDouble Chris@187: (this, Chris@187: tr("Enter new value"), Chris@187: text, Chris@187: m_mappedValue, Chris@187: min, Chris@187: max, Chris@187: 4, Chris@187: &ok); Chris@187: Chris@187: if (ok) { Chris@187: setMappedValue(newValue); Chris@187: } Chris@187: Chris@187: } else { Chris@187: Chris@616: int newPosition = QInputDialog::getInt Chris@187: (this, Chris@187: tr("Enter new value"), Chris@187: tr("Enter a new value from %1 to %2:") Chris@187: .arg(minimum()).arg(maximum()), Chris@395: value(), minimum(), maximum(), singleStep(), &ok); Chris@187: Chris@187: if (ok) { Chris@187: setValue(newPosition); Chris@167: } Chris@0: } Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent) Chris@0: { Chris@0: if (m_mouseDial) { Chris@1266: QDial::mouseMoveEvent(mouseEvent); Chris@0: } else if (m_mousePressed) { Chris@1266: const QPoint& posMouse = mouseEvent->pos(); Chris@1266: int v = QDial::value() Chris@1266: + (posMouse.x() - m_posMouse.x()) Chris@1266: + (m_posMouse.y() - posMouse.y()); Chris@1266: if (v > QDial::maximum()) Chris@1266: v = QDial::maximum(); Chris@1266: else Chris@1266: if (v < QDial::minimum()) Chris@1266: v = QDial::minimum(); Chris@1266: m_posMouse = posMouse; Chris@1266: QDial::setValue(v); Chris@0: } Chris@0: } Chris@0: Chris@0: Chris@0: void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent) Chris@0: { Chris@0: if (m_mouseDial) { Chris@1266: QDial::mouseReleaseEvent(mouseEvent); Chris@0: } else if (m_mousePressed) { Chris@1266: m_mousePressed = false; Chris@0: } Chris@0: } Chris@0: Chris@189: void Chris@189: AudioDial::enterEvent(QEvent *e) Chris@189: { Chris@189: QDial::enterEvent(e); Chris@189: emit mouseEntered(); Chris@189: } Chris@189: Chris@189: void Chris@189: AudioDial::leaveEvent(QEvent *e) Chris@189: { Chris@189: QDial::enterEvent(e); Chris@189: emit mouseLeft(); Chris@189: } Chris@189: