svgui  1.9
AudioDial.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     
00008     This program is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU General Public License as
00010     published by the Free Software Foundation; either version 2 of the
00011     License, or (at your option) any later version.  See the file
00012     COPYING included with this distribution for more information.
00013 */
00014 
00038 #include "AudioDial.h"
00039 
00040 #include "base/RangeMapper.h"
00041 
00042 #include <cmath>
00043 #include <iostream>
00044 
00045 #include <QTimer>
00046 #include <QPainter>
00047 #include <QPixmap>
00048 #include <QColormap>
00049 #include <QMouseEvent>
00050 #include <QPaintEvent>
00051 #include <QInputDialog>
00052 
00053 #include "base/Profiler.h"
00054 
00055 
00056 
00057 
00058 
00060 
00061 
00062 //-------------------------------------------------------------------------
00063 // AudioDial - Instance knob widget class.
00064 //
00065 
00066 #define AUDIO_DIAL_MIN (0.25 * M_PI)
00067 #define AUDIO_DIAL_MAX (1.75 * M_PI)
00068 #define AUDIO_DIAL_RANGE (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN)
00069 
00070 
00071 //static int dialsExtant = 0;
00072 
00073 
00074 // Constructor.
00075 AudioDial::AudioDial(QWidget *parent) :
00076     QDial(parent),
00077     m_knobColor(Qt::black),
00078     m_meterColor(Qt::white),
00079     m_defaultValue(0),
00080     m_defaultMappedValue(0),
00081     m_mappedValue(0),
00082     m_noMappedUpdate(false),
00083     m_showTooltip(true),
00084     m_rangeMapper(0)
00085 {
00086     m_mouseDial = false;
00087     m_mousePressed = false;
00088 //    ++dialsExtant;
00089 }
00090 
00091 
00092 // Destructor.
00093 AudioDial::~AudioDial (void)
00094 {
00095     delete m_rangeMapper;
00096 //    --dialsExtant;
00097 }
00098 
00099 
00100 void AudioDial::setRangeMapper(RangeMapper *mapper)
00101 {
00102 //    cerr << "AudioDial[" << this << "][\"" << objectName() << "\"::setRangeMapper(" << mapper << ") [current is " << m_rangeMapper << "] (have " << dialsExtant << " dials extant)" << endl;
00103 
00104     if (m_rangeMapper == mapper) return;
00105 
00106     if (!m_rangeMapper && mapper) {
00107         connect(this, SIGNAL(valueChanged(int)),
00108                 this, SLOT(updateMappedValue(int)));
00109     }
00110 
00111     delete m_rangeMapper;
00112     m_rangeMapper = mapper;
00113 
00114     updateMappedValue(value());
00115 }
00116 
00117 
00118 void AudioDial::paintEvent(QPaintEvent *)
00119 {
00120     Profiler profiler("AudioDial::paintEvent");
00121 
00122     QPainter paint;
00123 
00124     float angle = AUDIO_DIAL_MIN // offset
00125         + (AUDIO_DIAL_RANGE *
00126            (float(QDial::value() - QDial::minimum()) /
00127             (float(QDial::maximum() - QDial::minimum()))));
00128     int degrees = int(angle * 180.0 / M_PI);
00129 
00130     int ns = notchSize();
00131     int numTicks = 1 + (maximum() + ns - minimum()) / ns;
00132         
00133     QColor knobColor(m_knobColor);
00134     if (knobColor == Qt::black)
00135         knobColor = palette().window().color();
00136 
00137     QColor meterColor(m_meterColor);
00138     if (!isEnabled())
00139         meterColor = palette().mid().color();
00140     else if (m_meterColor == Qt::white)
00141         meterColor = palette().highlight().color();
00142 
00143     int m_size = width() < height() ? width() : height();
00144     int scale = 1;
00145     int width = m_size - 2*scale;
00146 
00147     paint.begin(this);
00148     paint.setRenderHint(QPainter::Antialiasing, true);
00149     paint.translate(1, 1);
00150 
00151     QPen pen;
00152     QColor c;
00153 
00154     // Knob body and face...
00155 
00156     c = knobColor;
00157     pen.setColor(knobColor);
00158     pen.setWidth(scale * 2);
00159     pen.setCapStyle(Qt::FlatCap);
00160         
00161     paint.setPen(pen);
00162     paint.setBrush(c);
00163 
00164     int indent = (int)(width * 0.15 + 1);
00165 
00166     paint.drawEllipse(indent-1, indent-1, width-2*indent, width-2*indent);
00167 
00168     pen.setWidth(3 * scale);
00169     int pos = indent-1 + (width-2*indent) / 20;
00170     int darkWidth = (width-2*indent) * 3 / 4;
00171     while (darkWidth) {
00172         c = c.light(102);
00173         pen.setColor(c);
00174         paint.setPen(pen);
00175         paint.drawEllipse(pos, pos, darkWidth, darkWidth);
00176         if (!--darkWidth) break;
00177         paint.drawEllipse(pos, pos, darkWidth, darkWidth);
00178         if (!--darkWidth) break;
00179         paint.drawEllipse(pos, pos, darkWidth, darkWidth);
00180         ++pos; --darkWidth;
00181     }
00182 
00183     // Tick notches...
00184 
00185     if ( notchesVisible() ) {
00186 //      cerr << "Notches visible" << endl;
00187         pen.setColor(palette().dark().color());
00188         pen.setWidth(scale);
00189         paint.setPen(pen);
00190         for (int i = 0; i < numTicks; ++i) {
00191             int div = numTicks;
00192             if (div > 1) --div;
00193             drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div,
00194                      width, true);
00195         }
00196     }
00197 
00198     // The bright metering bit...
00199 
00200     c = meterColor;
00201     pen.setColor(c);
00202     pen.setWidth(indent);
00203     paint.setPen(pen);
00204 
00205 //    cerr << "degrees " << degrees << ", gives us " << -(degrees - 45) * 16 << endl;
00206 
00207     int arcLen = -(degrees - 45) * 16;
00208     if (arcLen == 0) arcLen = -16;
00209 
00210     paint.drawArc(indent/2, indent/2,
00211                   width-indent, width-indent, (180 + 45) * 16, arcLen);
00212 
00213     paint.setBrush(Qt::NoBrush);
00214 
00215     // Shadowing...
00216 
00217     pen.setWidth(scale);
00218     paint.setPen(pen);
00219 
00220     // Knob shadow...
00221 
00222     int shadowAngle = -720;
00223     c = knobColor.dark();
00224     for (int arc = 120; arc < 2880; arc += 240) {
00225         pen.setColor(c);
00226         paint.setPen(pen);
00227         paint.drawArc(indent, indent,
00228                       width-2*indent, width-2*indent, shadowAngle + arc, 240);
00229         paint.drawArc(indent, indent,
00230                       width-2*indent, width-2*indent, shadowAngle - arc, 240);
00231         c = c.light(110);
00232     }
00233 
00234     // Scale shadow, omitting the bottom part...
00235 
00236     shadowAngle = 2160;
00237     c = palette().shadow().color();
00238     for (int i = 0; i < 5; ++i) {
00239         pen.setColor(c);
00240         paint.setPen(pen);
00241         int arc = i * 240 + 120;
00242         paint.drawArc(scale/2, scale/2,
00243                       width-scale, width-scale, shadowAngle + arc, 240);
00244         c = c.light(110);
00245     }
00246     c = palette().shadow().color();
00247     for (int i = 0; i < 12; ++i) {
00248         pen.setColor(c);
00249         paint.setPen(pen);
00250         int arc = i * 240 + 120;
00251         paint.drawArc(scale/2, scale/2,
00252                       width-scale, width-scale, shadowAngle - arc, 240);
00253         c = c.light(110);
00254     }
00255 
00256     // Scale ends...
00257 
00258     pen.setColor(palette().shadow().color());
00259     pen.setWidth(scale);
00260     paint.setPen(pen);
00261     for (int i = 0; i < numTicks; ++i) {
00262         if (i != 0 && i != numTicks - 1) continue;
00263         int div = numTicks;
00264         if (div > 1) --div;
00265         drawTick(paint, AUDIO_DIAL_MIN + (AUDIO_DIAL_MAX - AUDIO_DIAL_MIN) * i / div,
00266                  width, false);
00267     }
00268 
00269     // Pointer notch...
00270 
00271     float hyp = float(width) / 2.0;
00272     float len = hyp - indent;
00273     --len;
00274 
00275     float x0 = hyp;
00276     float y0 = hyp;
00277 
00278     float x = hyp - len * sin(angle);
00279     float y = hyp + len * cos(angle);
00280 
00281     c = palette().dark().color();
00282     pen.setColor(isEnabled() ? c.dark(130) : c);
00283     pen.setWidth(scale * 2);
00284     paint.setPen(pen);
00285     paint.drawLine(int(x0), int(y0), int(x), int(y));
00286 
00287     paint.end();
00288 }
00289 
00290 
00291 void AudioDial::drawTick(QPainter &paint,
00292                          float angle, int size, bool internal)
00293 {
00294     float hyp = float(size) / 2.0;
00295     float x0 = hyp - (hyp - 1) * sin(angle);
00296     float y0 = hyp + (hyp - 1) * cos(angle);
00297 
00298 //    cerr << "drawTick: angle " << angle << ", size " << size << ", internal " << internal << endl;
00299     
00300     if (internal) {
00301 
00302         float len = hyp / 4;
00303         float x1 = hyp - (hyp - len) * sin(angle);
00304         float y1 = hyp + (hyp - len) * cos(angle);
00305                 
00306         paint.drawLine(int(x0), int(y0), int(x1), int(y1));
00307 
00308     } else {
00309 
00310         float len = hyp / 4;
00311         float x1 = hyp - (hyp + len) * sin(angle);
00312         float y1 = hyp + (hyp + len) * cos(angle);
00313 
00314         paint.drawLine(int(x0), int(y0), int(x1), int(y1));
00315     }
00316 }
00317 
00318 
00319 void AudioDial::setKnobColor(const QColor& color)
00320 {
00321     m_knobColor = color;
00322     update();
00323 }
00324 
00325 
00326 void AudioDial::setMeterColor(const QColor& color)
00327 {
00328     m_meterColor = color;
00329     update();
00330 }
00331 
00332 
00333 void AudioDial::setMouseDial(bool mouseDial)
00334 {
00335     m_mouseDial = mouseDial;
00336 }
00337 
00338 
00339 void AudioDial::setDefaultValue(int defaultValue)
00340 {
00341     m_defaultValue = defaultValue;
00342     if (m_rangeMapper) {
00343         m_defaultMappedValue = m_rangeMapper->getValueForPosition(defaultValue);
00344     }
00345 }
00346 
00347 void AudioDial::setValue(int value)
00348 {
00349     QDial::setValue(value);
00350     updateMappedValue(value);
00351 }
00352 
00353 void AudioDial::setDefaultMappedValue(float value)
00354 {
00355     m_defaultMappedValue = value;
00356     if (m_rangeMapper) {
00357         m_defaultValue = m_rangeMapper->getPositionForValue(value);
00358     }
00359 }
00360 
00361 void AudioDial::setMappedValue(float mappedValue)
00362 {
00363     if (m_rangeMapper) {
00364         int newPosition = m_rangeMapper->getPositionForValue(mappedValue);
00365         bool changed = (m_mappedValue != mappedValue);
00366         m_mappedValue = mappedValue;
00367         m_noMappedUpdate = true;
00368         SVDEBUG << "AudioDial::setMappedValue(" << mappedValue << "): new position is " << newPosition << endl;
00369         if (newPosition != value()) {
00370             setValue(newPosition);
00371         } else if (changed) {
00372             emit valueChanged(newPosition);
00373         }
00374         m_noMappedUpdate = false;
00375     } else {
00376         setValue(int(mappedValue));
00377     }
00378 }
00379 
00380 
00381 void AudioDial::setShowToolTip(bool show)
00382 {
00383     m_showTooltip = show;
00384     m_noMappedUpdate = true;
00385     updateMappedValue(value());
00386     m_noMappedUpdate = false;
00387 }
00388 
00389 
00390 float AudioDial::mappedValue() const
00391 {
00392     if (m_rangeMapper) {
00393 //        SVDEBUG << "AudioDial::mappedValue(): value = " << value() << ", mappedValue = " << m_mappedValue << endl;
00394         return m_mappedValue;
00395     }
00396     return value();
00397 }
00398 
00399 
00400 void AudioDial::updateMappedValue(int value)
00401 {
00402     if (!m_noMappedUpdate) {
00403         if (m_rangeMapper) {
00404             m_mappedValue = m_rangeMapper->getValueForPosition(value);
00405         } else {
00406             m_mappedValue = value;
00407         }
00408     }
00409 
00410     if (m_showTooltip) {
00411         QString name = objectName();
00412         QString unit = "";
00413         QString text;
00414         if (m_rangeMapper) unit = m_rangeMapper->getUnit();
00415         if (name != "") {
00416             text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit);
00417         } else {
00418             text = tr("%2%3").arg(m_mappedValue).arg(unit);
00419         }
00420         setToolTip(text);
00421     }
00422 }
00423 
00424 void
00425 AudioDial::setToDefault()
00426 {
00427     if (m_rangeMapper) {
00428         setMappedValue(m_defaultMappedValue);
00429         return;
00430     }
00431     int dv = m_defaultValue;
00432     if (dv < minimum()) dv = minimum();
00433     if (dv > maximum()) dv = maximum();
00434     setValue(m_defaultValue);
00435 }
00436 
00437 // Alternate mouse behavior event handlers.
00438 void AudioDial::mousePressEvent(QMouseEvent *mouseEvent)
00439 {
00440     if (m_mouseDial) {
00441         QDial::mousePressEvent(mouseEvent);
00442     } else if (mouseEvent->button() == Qt::MidButton ||
00443                ((mouseEvent->button() == Qt::LeftButton) &&
00444                 (mouseEvent->modifiers() & Qt::ControlModifier))) {
00445         setToDefault();
00446     } else if (mouseEvent->button() == Qt::LeftButton) {
00447         m_mousePressed = true;
00448         m_posMouse = mouseEvent->pos();
00449     }
00450 }
00451 
00452 
00453 void AudioDial::mouseDoubleClickEvent(QMouseEvent *mouseEvent)
00454 {
00456 
00457     if (m_mouseDial) {
00458         QDial::mouseDoubleClickEvent(mouseEvent);
00459     } else if (mouseEvent->button() != Qt::LeftButton) {
00460         return;
00461     }
00462 
00463     bool ok = false;
00464 
00465     if (m_rangeMapper) {
00466         
00467         float min = m_rangeMapper->getValueForPosition(minimum());
00468         float max = m_rangeMapper->getValueForPosition(maximum());
00469         
00470         if (min > max) { 
00471             float tmp = min;
00472             min = max;
00473             max = tmp;
00474         }
00475 
00476         QString unit = m_rangeMapper->getUnit();
00477         
00478         QString text;
00479         if (objectName() != "") {
00480             if (unit != "") {
00481                 text = tr("New value for %1, from %2 to %3 %4:")
00482                     .arg(objectName()).arg(min).arg(max).arg(unit);
00483             } else {
00484                 text = tr("New value for %1, from %2 to %3:")
00485                     .arg(objectName()).arg(min).arg(max);
00486             }
00487         } else {
00488             if (unit != "") {
00489                 text = tr("Enter a new value from %1 to %2 %3:")
00490                     .arg(min).arg(max).arg(unit);
00491             } else {
00492                 text = tr("Enter a new value from %1 to %2:")
00493                     .arg(min).arg(max);
00494             }
00495         }
00496         
00497         float newValue = QInputDialog::getDouble
00498             (this,
00499              tr("Enter new value"),
00500              text,
00501              m_mappedValue,
00502              min,
00503              max,
00504              4, 
00505              &ok);
00506         
00507         if (ok) {
00508             setMappedValue(newValue);
00509         }
00510         
00511     } else {
00512         
00513         int newPosition = QInputDialog::getInt
00514             (this,
00515              tr("Enter new value"),
00516              tr("Enter a new value from %1 to %2:")
00517              .arg(minimum()).arg(maximum()),
00518              value(), minimum(), maximum(), singleStep(), &ok);
00519         
00520         if (ok) {
00521             setValue(newPosition);
00522         }
00523     }
00524 }
00525 
00526 
00527 void AudioDial::mouseMoveEvent(QMouseEvent *mouseEvent)
00528 {
00529     if (m_mouseDial) {
00530         QDial::mouseMoveEvent(mouseEvent);
00531     } else if (m_mousePressed) {
00532         const QPoint& posMouse = mouseEvent->pos();
00533         int v = QDial::value()
00534             + (posMouse.x() - m_posMouse.x())
00535             + (m_posMouse.y() - posMouse.y());
00536         if (v > QDial::maximum())
00537             v = QDial::maximum();
00538         else
00539             if (v < QDial::minimum())
00540                 v = QDial::minimum();
00541         m_posMouse = posMouse;
00542         QDial::setValue(v);
00543     }
00544 }
00545 
00546 
00547 void AudioDial::mouseReleaseEvent(QMouseEvent *mouseEvent)
00548 {
00549     if (m_mouseDial) {
00550         QDial::mouseReleaseEvent(mouseEvent);
00551     } else if (m_mousePressed) {
00552         m_mousePressed = false;
00553     }
00554 }
00555 
00556 void
00557 AudioDial::enterEvent(QEvent *e)
00558 {
00559     QDial::enterEvent(e);
00560     emit mouseEntered();
00561 }
00562 
00563 void
00564 AudioDial::leaveEvent(QEvent *e)
00565 {
00566     QDial::enterEvent(e);
00567     emit mouseLeft();
00568 }
00569