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@170: m_knobColor(Qt::black), Chris@170: m_meterColor(Qt::white), 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@167: m_rangeMapper(0) 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@0: float angle = AUDIO_DIAL_MIN // offset Chris@0: + (AUDIO_DIAL_RANGE * Chris@0: (float(QDial::value() - QDial::minimum()) / Chris@0: (float(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@0: Chris@0: QColor knobColor(m_knobColor); Chris@0: if (knobColor == Qt::black) Chris@287: knobColor = palette().window().color(); Chris@0: Chris@0: QColor meterColor(m_meterColor); Chris@0: if (!isEnabled()) Chris@0: meterColor = palette().mid().color(); Chris@0: else if (m_meterColor == Qt::white) Chris@0: meterColor = palette().highlight().color(); Chris@0: 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@0: 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@0: c = c.light(102); Chris@0: pen.setColor(c); Chris@0: paint.setPen(pen); Chris@0: paint.drawEllipse(pos, pos, darkWidth, darkWidth); Chris@0: if (!--darkWidth) break; Chris@0: paint.drawEllipse(pos, pos, darkWidth, darkWidth); Chris@0: if (!--darkWidth) break; Chris@0: paint.drawEllipse(pos, pos, darkWidth, darkWidth); Chris@0: ++pos; --darkWidth; Chris@0: } Chris@0: Chris@0: // Tick notches... Chris@0: Chris@34: if ( notchesVisible() ) { Chris@682: // cerr << "Notches visible" << endl; Chris@0: pen.setColor(palette().dark().color()); Chris@0: pen.setWidth(scale); Chris@0: paint.setPen(pen); Chris@0: for (int i = 0; i < numTicks; ++i) { Chris@0: int div = numTicks; Chris@0: if (div > 1) --div; Chris@0: drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, Chris@0: width, true); Chris@0: } 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@0: 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@0: c = knobColor.dark(); Chris@0: for (int arc = 120; arc < 2880; arc += 240) { Chris@0: pen.setColor(c); Chris@0: paint.setPen(pen); Chris@0: paint.drawArc(indent, indent, Chris@0: width-2*indent, width-2*indent, shadowAngle + arc, 240); Chris@0: paint.drawArc(indent, indent, Chris@0: width-2*indent, width-2*indent, shadowAngle - arc, 240); Chris@0: c = c.light(110); Chris@0: } Chris@0: Chris@0: // Scale shadow... Chris@0: Chris@0: shadowAngle = 2160; Chris@0: c = palette().dark().color(); Chris@0: for (int arc = 120; arc < 2880; arc += 240) { Chris@0: pen.setColor(c); Chris@0: paint.setPen(pen); Chris@0: paint.drawArc(scale/2, scale/2, Chris@0: width-scale, width-scale, shadowAngle + arc, 240); Chris@0: paint.drawArc(scale/2, scale/2, Chris@0: width-scale, width-scale, shadowAngle - arc, 240); Chris@0: c = c.light(108); Chris@0: } Chris@0: Chris@0: // Undraw the bottom part... Chris@0: Chris@0: pen.setColor(palette().background().color()); Chris@0: pen.setWidth(scale * 4); Chris@0: paint.setPen(pen); Chris@0: paint.drawArc(scale/2, scale/2, Chris@0: width-scale, width-scale, -45 * 16, -92 * 16); Chris@0: Chris@0: // Scale ends... Chris@0: Chris@0: pen.setColor(palette().dark().color()); Chris@0: pen.setWidth(scale); Chris@0: paint.setPen(pen); Chris@0: for (int i = 0; i < numTicks; ++i) { Chris@0: if (i != 0 && i != numTicks - 1) continue; Chris@0: int div = numTicks; Chris@0: if (div > 1) --div; Chris@0: drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div, Chris@0: width, false); Chris@0: } Chris@0: Chris@0: // Pointer notch... Chris@0: Chris@0: float hyp = float(width) / 2.0; Chris@0: float len = hyp - indent; Chris@0: --len; Chris@0: Chris@0: float x0 = hyp; Chris@0: float y0 = hyp; Chris@0: Chris@0: float x = hyp - len * sin(angle); Chris@0: float y = hyp + len * cos(angle); Chris@0: Chris@0: c = palette().dark().color(); Chris@0: pen.setColor(isEnabled() ? c.dark(130) : 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@0: float angle, int size, bool internal) Chris@0: { Chris@0: float hyp = float(size) / 2.0; Chris@0: float x0 = hyp - (hyp - 1) * sin(angle); Chris@0: float 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@0: float len = hyp / 4; Chris@0: float x1 = hyp - (hyp - len) * sin(angle); Chris@0: float y1 = hyp + (hyp - len) * cos(angle); Chris@0: Chris@0: paint.drawLine(int(x0), int(y0), int(x1), int(y1)); Chris@0: Chris@0: } else { Chris@0: Chris@0: float len = hyp / 4; Chris@0: float x1 = hyp - (hyp + len) * sin(angle); Chris@0: float y1 = hyp + (hyp + len) * cos(angle); Chris@0: Chris@0: 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@344: void AudioDial::setDefaultMappedValue(float 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@177: void AudioDial::setMappedValue(float 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@167: float 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@168: QString unit = ""; Chris@168: QString text; Chris@168: if (m_rangeMapper) unit = m_rangeMapper->getUnit(); Chris@168: if (name != "") { Chris@168: text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit); Chris@168: } else { Chris@168: text = tr("%2%3").arg(m_mappedValue).arg(unit); 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@0: 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@187: m_mousePressed = true; Chris@187: 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@34: 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@187: float min = m_rangeMapper->getValueForPosition(minimum()); Chris@187: float max = m_rangeMapper->getValueForPosition(maximum()); Chris@187: Chris@187: if (min > max) { Chris@187: float 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@187: float 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@0: QDial::mouseMoveEvent(mouseEvent); Chris@0: } else if (m_mousePressed) { Chris@0: const QPoint& posMouse = mouseEvent->pos(); Chris@0: int v = QDial::value() Chris@0: + (posMouse.x() - m_posMouse.x()) Chris@0: + (m_posMouse.y() - posMouse.y()); Chris@0: if (v > QDial::maximum()) Chris@0: v = QDial::maximum(); Chris@0: else Chris@0: if (v < QDial::minimum()) Chris@0: v = QDial::minimum(); Chris@0: m_posMouse = posMouse; Chris@0: 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@0: QDial::mouseReleaseEvent(mouseEvent); Chris@0: } else if (m_mousePressed) { Chris@0: 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: