changeset 187:e7cf6044c2a0

* better icon * support range mappers in thumbwheel * supply range mapper for vertical zoom from spectrogram * fix bug in fftmodel for scaled ffts * make the various widgets all respond to double-click for edit, middle-click for reset, ctrl-left-click for reset
author Chris Cannam
date Fri, 12 Jan 2007 14:49:18 +0000
parents 8dd247c4c5f1
children dd573e090eed
files layer/Layer.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h view/Pane.cpp widgets/AudioDial.cpp widgets/Fader.cpp widgets/Fader.h widgets/Thumbwheel.cpp widgets/Thumbwheel.h
diffstat 9 files changed, 450 insertions(+), 176 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Layer.h	Fri Jan 05 15:49:10 2007 +0000
+++ b/layer/Layer.h	Fri Jan 12 14:49:18 2007 +0000
@@ -34,6 +34,7 @@
 class View;
 class QMouseEvent;
 class Clipboard;
+class RangeMapper;
 
 /**
  * The base class for visual representations of the data found in a
@@ -338,6 +339,14 @@
      */
     virtual void setVerticalZoomStep(int) { }
 
+    /**
+     * Create and return a range mapper for vertical zoom step values.
+     * See the RangeMapper documentation for more details.  The
+     * returned value is allocated on the heap and will be deleted by
+     * the caller.
+     */
+    virtual RangeMapper *getNewVerticalZoomRangeMapper() const { return 0; }
+
 public slots:
     void showLayer(View *, bool show);
 
--- a/layer/SpectrogramLayer.cpp	Fri Jan 05 15:49:10 2007 +0000
+++ b/layer/SpectrogramLayer.cpp	Fri Jan 12 14:49:18 2007 +0000
@@ -725,6 +725,8 @@
 {
     if (m_minFrequency == mf) return;
 
+    std::cerr << "SpectrogramLayer::setMinFrequency: " << mf << std::endl;
+
     invalidatePixmapCaches();
     invalidateMagnitudes();
     
@@ -744,6 +746,8 @@
 {
     if (m_maxFrequency == mf) return;
 
+    std::cerr << "SpectrogramLayer::setMaxFrequency: " << mf << std::endl;
+
     invalidatePixmapCaches();
     invalidateMagnitudes();
     
@@ -1595,7 +1599,7 @@
             m_fftModels.erase(v);
         } else {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model" << std::endl;
+            std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[v].first->getHeight() << std::endl;
 #endif
             return m_fftModels[v].first;
         }
@@ -2293,6 +2297,7 @@
 {
     min = getEffectiveMinFrequency();
     max = getEffectiveMaxFrequency();
+    std::cerr << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << std::endl;
     return true;
 }    
 
@@ -2300,6 +2305,9 @@
 SpectrogramLayer::setDisplayExtents(float min, float max)
 {
     if (!m_model) return false;
+
+    std::cerr << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << std::endl;
+
     if (min < 0) min = 0;
     if (max > m_model->getSampleRate()/2) max = m_model->getSampleRate()/2;
     
@@ -2806,38 +2814,73 @@
     }
 }
 
+class SpectrogramRangeMapper : public RangeMapper
+{
+public:
+    SpectrogramRangeMapper(int sr, int fftsize) :
+//        m_dist((float(sr) / 2) - (float(sr) / fftsize)),
+        m_dist(float(sr) / 2),
+        m_s2(sqrtf(sqrtf(2))) { }
+    ~SpectrogramRangeMapper() { }
+    
+    virtual int getPositionForValue(float value) const {
+
+        float dist = m_dist;
+    
+        int n = 0;
+        int discard = 0;
+
+        while (dist > (value + 0.00001) && dist > 0.1f) {
+            dist /= m_s2;
+            ++n;
+        }
+
+        return n;
+    }
+
+    virtual float getValueForPosition(int position) const {
+
+        // Vertical zoom step 0 shows the entire range from DC ->
+        // Nyquist frequency.  Step 1 shows 2^(1/4) of the range of
+        // step 0, and so on until the visible range is smaller than
+        // the frequency step between bins at the current fft size.
+
+        float dist = m_dist;
+    
+        int n = 0;
+        while (n < position) {
+            dist /= m_s2;
+            ++n;
+        }
+
+        return dist;
+    }
+    
+    virtual QString getUnit() const { return "Hz"; }
+
+protected:
+    float m_dist;
+    float m_s2;
+};
+
 int
 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    // Vertical zoom step 0 shows the entire range from DC -> Nyquist
-    // frequency.  Step 1 shows 2^(1/4) of the range of step 0, and so
-    // on until the visible range is smaller than the frequency step
-    // between bins at the current fft size.
-
     if (!m_model) return 0;
+
+    int sr = m_model->getSampleRate();
+
+    SpectrogramRangeMapper mapper(sr, m_fftSize);
+
+//    int maxStep = mapper.getPositionForValue((float(sr) / m_fftSize) + 0.001);
+    int maxStep = mapper.getPositionForValue(0);
+    int minStep = mapper.getPositionForValue(float(sr) / 2);
     
-    float min, max;
-    int sr = m_model->getSampleRate();
-    min = float(sr) / m_fftSize;
-    max = float(sr) / 2;
-    
-    float dist = max - min;
-
-    int n = 0;
-    defaultStep = 0;
-    bool haveDefault = false;
-    float s2 = sqrtf(sqrtf(2));
-    while (dist > min) {
-        if (!haveDefault && max <= m_initialMaxFrequency) {
-            defaultStep = n;
-            haveDefault = true;
-        }
-        ++n;
-        dist /= s2;
-        max = min + dist;
-    }
-
-    return n;
+    defaultStep = mapper.getPositionForValue(m_initialMaxFrequency) - minStep;
+
+    std::cerr << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << m_initialMaxFrequency << ")" << std::endl;
+
+    return maxStep - minStep;
 }
 
 int
@@ -2848,23 +2891,9 @@
     float dmin, dmax;
     getDisplayExtents(dmin, dmax);
     
-    float mmin, mmax;
-    int sr = m_model->getSampleRate();
-    mmin = float(sr) / m_fftSize;
-    mmax = float(sr) / 2;
-    
-    float mdist = mmax - mmin;
-    float ddist = dmax - dmin;
-    
-    int n = 0;
-    int discard = 0;
-    int m = getVerticalZoomSteps(discard);
-    float s2 = sqrtf(sqrtf(2));
-    while (mdist > ddist) {
-        if (++n > m) break;
-        mdist /= s2;
-    }
-
+    SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize);
+    int n = mapper.getPositionForValue(dmax - dmin);
+    std::cerr << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << std::endl;
     return n;
 }
 
@@ -2873,32 +2902,42 @@
 {
     //!!! does not do the right thing for log scale
 
+    if (!m_model) return;
+
     float dmin, dmax;
     getDisplayExtents(dmin, dmax);
     
-    float mmin, mmax;
     int sr = m_model->getSampleRate();
-    mmin = float(sr) / m_fftSize;
-    mmax = float(sr) / 2;
-    
-    float ddist = mmax - mmin;
-    
-    int n = 0;
-    float s2 = sqrtf(sqrtf(2));
-    while (n < step) {
-        ddist /= s2;
-        ++n;
-    }
+    SpectrogramRangeMapper mapper(sr, m_fftSize);
+    float ddist = mapper.getValueForPosition(step);
 
     float dmid = (dmax + dmin) / 2;
     float newmin = dmid - ddist / 2;
     float newmax = dmid + ddist / 2;
+
+    float mmin, mmax;
+    mmin = 0;
+    mmax = float(sr) / 2;
     
-    if (newmin < mmin) newmin = mmin;
-    if (newmax > mmax) newmax = mmax;
+    if (newmin < mmin) {
+        newmax += (mmin - newmin);
+        newmin = mmin;
+    }
+    if (newmax > mmax) {
+        newmax = mmax;
+    }
     
-    setMinFrequency(newmin);
-    setMaxFrequency(newmax);
+    std::cerr << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << ddist << ")" << std::endl;
+
+    setMinFrequency(int(newmin));
+    setMaxFrequency(int(newmax));
+}
+
+RangeMapper *
+SpectrogramLayer::getNewVerticalZoomRangeMapper() const
+{
+    if (!m_model) return 0;
+    return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize);
 }
 
 QString
@@ -2971,10 +3010,16 @@
     if (ok) setThreshold(threshold);
 
     size_t minFrequency = attributes.value("minFrequency").toUInt(&ok);
-    if (ok) setMinFrequency(minFrequency);
+    if (ok) {
+        std::cerr << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << std::endl;
+        setMinFrequency(minFrequency);
+    }
 
     size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
-    if (ok) setMaxFrequency(maxFrequency);
+    if (ok) {
+        std::cerr << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << std::endl;
+        setMaxFrequency(maxFrequency);
+    }
 
     ColourScale colourScale = (ColourScale)
 	attributes.value("colourScale").toInt(&ok);
--- a/layer/SpectrogramLayer.h	Fri Jan 05 15:49:10 2007 +0000
+++ b/layer/SpectrogramLayer.h	Fri Jan 12 14:49:18 2007 +0000
@@ -213,6 +213,7 @@
     virtual int getVerticalZoomSteps(int &defaultStep) const;
     virtual int getCurrentVerticalZoomStep() const;
     virtual void setVerticalZoomStep(int);
+    virtual RangeMapper *getNewVerticalZoomRangeMapper() const;
 
 protected slots:
     void cacheInvalid();
--- a/view/Pane.cpp	Fri Jan 05 15:49:10 2007 +0000
+++ b/view/Pane.cpp	Fri Jan 12 14:49:18 2007 +0000
@@ -58,6 +58,8 @@
 void
 Pane::updateHeadsUpDisplay()
 {
+    Profiler profiler("Pane::updateHeadsUpDisplay", true);
+
 /*
     int count = 0;
     int currentLevel = 1;
@@ -85,6 +87,7 @@
         m_headsUpDisplay->setLayout(layout);
         
         m_hthumb = new Thumbwheel(Qt::Horizontal);
+        m_hthumb->setObjectName(tr("Horizontal Zoom"));
         layout->addWidget(m_hthumb, 1, 0, 1, 2);
         m_hthumb->setFixedWidth(70);
         m_hthumb->setFixedHeight(16);
@@ -102,6 +105,7 @@
                 this, SLOT(verticalPannerMoved(float, float, float, float)));
 
         m_vthumb = new Thumbwheel(Qt::Vertical);
+        m_vthumb->setObjectName(tr("Vertical Zoom"));
         layout->addWidget(m_vthumb, 0, 2);
         m_vthumb->setFixedWidth(16);
         m_vthumb->setFixedHeight(70);
@@ -184,10 +188,12 @@
             m_vthumb->hide();
         } else {
             m_vthumb->show();
+            m_vthumb->blockSignals(true);
             m_vthumb->setMinimumValue(0);
             m_vthumb->setMaximumValue(max);
             m_vthumb->setDefaultValue(defaultStep);
             m_vthumb->setValue(layer->getCurrentVerticalZoomStep());
+            m_vthumb->blockSignals(false);
 
             std::cerr << "Vertical thumbwheel: min 0, max " << max
                       << ", default " << defaultStep << ", value "
@@ -1548,6 +1554,12 @@
     View::propertyContainerSelected(v, pc);
     updateHeadsUpDisplay();
 
+    if (m_vthumb) {
+        RangeMapper *rm = 0;
+        if (layer) rm = layer->getNewVerticalZoomRangeMapper();
+        if (rm) m_vthumb->setRangeMapper(rm);
+    }
+
     if (getLayerCount() > 0) {
         layer = getLayer(getLayerCount() - 1);
         connect(layer, SIGNAL(verticalZoomChanged()),
--- a/widgets/AudioDial.cpp	Fri Jan 05 15:49:10 2007 +0000
+++ b/widgets/AudioDial.cpp	Fri Jan 12 14:49:18 2007 +0000
@@ -74,7 +74,7 @@
     m_defaultValue(0),
     m_mappedValue(0),
     m_noMappedUpdate(false),
-    m_showTooltip(false),
+    m_showTooltip(true),
     m_rangeMapper(0)
 {
     m_mouseDial = false;
@@ -91,6 +91,8 @@
 
 void AudioDial::setRangeMapper(RangeMapper *mapper)
 {
+    if (m_rangeMapper == mapper) return;
+
     if (!m_rangeMapper && mapper) {
         connect(this, SIGNAL(valueChanged(int)),
                 this, SLOT(updateMappedValue(int)));
@@ -99,11 +101,7 @@
     delete m_rangeMapper;
     m_rangeMapper = mapper;
 
-    if (m_rangeMapper) {
-        m_mappedValue = m_rangeMapper->getValueForPosition(value());
-    } else {
-        m_mappedValue = value();
-    }
+    updateMappedValue(value());
 }
 
 
@@ -346,7 +344,7 @@
         }
         m_noMappedUpdate = false;
     } else {
-        setValue(mappedValue);
+        setValue(int(mappedValue));
     }
 }
 
@@ -400,78 +398,89 @@
 {
     if (m_mouseDial) {
 	QDial::mousePressEvent(mouseEvent);
-    } else if (mouseEvent->button() == Qt::LeftButton) {
-	m_mousePressed = true;
-	m_posMouse = mouseEvent->pos();
-    } else if (mouseEvent->button() == Qt::MidButton) {
+    } else if (mouseEvent->button() == Qt::MidButton ||
+               ((mouseEvent->button() == Qt::LeftButton) &&
+                (mouseEvent->modifiers() & Qt::ControlModifier))) {
 	int dv = m_defaultValue;
 	if (dv < minimum()) dv = minimum();
 	if (dv > maximum()) dv = maximum();
 	setValue(m_defaultValue);
+    } else if (mouseEvent->button() == Qt::LeftButton) {
+	m_mousePressed = true;
+	m_posMouse = mouseEvent->pos();
     }
 }
 
 
 void AudioDial::mouseDoubleClickEvent(QMouseEvent *mouseEvent)
 {
+    //!!! needs a common base class with Thumbwheel
+
     if (m_mouseDial) {
 	QDial::mouseDoubleClickEvent(mouseEvent);
-    } else if (mouseEvent->button() == Qt::LeftButton) {
+    } else if (mouseEvent->button() != Qt::LeftButton) {
+        return;
+    }
 
-	bool ok = false;
+    bool ok = false;
 
-        if (m_rangeMapper) {
+    if (m_rangeMapper) {
+        
+        float min = m_rangeMapper->getValueForPosition(minimum());
+        float max = m_rangeMapper->getValueForPosition(maximum());
+        
+        if (min > max) { 
+            float tmp = min;
+            min = max;
+            max = tmp;
+        }
 
-            float min = m_rangeMapper->getValueForPosition(minimum());
-            float max = m_rangeMapper->getValueForPosition(maximum());
-
-            QString unit = m_rangeMapper->getUnit();
-
-            QString text;
-            if (objectName() != "") {
-                if (unit != "") {
-                    text = tr("New value for %1, from %2 to %3 %4:")
-                        .arg(objectName()).arg(min).arg(max).arg(unit);
-                } else {
-                    text = tr("New value for %1, from %2 to %3:")
-                        .arg(objectName()).arg(min).arg(max);
-                }
+        QString unit = m_rangeMapper->getUnit();
+        
+        QString text;
+        if (objectName() != "") {
+            if (unit != "") {
+                text = tr("New value for %1, from %2 to %3 %4:")
+                    .arg(objectName()).arg(min).arg(max).arg(unit);
             } else {
-                if (unit != "") {
-                    text = tr("Enter a new value from %1 to %2 %3:")
-                        .arg(min).arg(max).arg(unit);
-                } else {
-                    text = tr("Enter a new value from %1 to %2:")
-                        .arg(min).arg(max);
-                }
+                text = tr("New value for %1, from %2 to %3:")
+                    .arg(objectName()).arg(min).arg(max);
             }
-
-            float newValue = QInputDialog::getDouble
-                (this,
-                 tr("Enter new value"),
-                 text,
-                 m_mappedValue,
-                 min,
-                 max,
-                 4, 
-                 &ok);
-
-            if (ok) {
-                setMappedValue(newValue);
+        } else {
+            if (unit != "") {
+                text = tr("Enter a new value from %1 to %2 %3:")
+                    .arg(min).arg(max).arg(unit);
+            } else {
+                text = tr("Enter a new value from %1 to %2:")
+                    .arg(min).arg(max);
             }
-
-        } else {
-
-            int newPosition = QInputDialog::getInteger
-                (this,
-                 tr("Enter new value"),
-                 tr("Enter a new value from %1 to %2:")
-                 .arg(minimum()).arg(maximum()),
-                 value(), minimum(), maximum(), pageStep(), &ok);
-
-            if (ok) {
-                setValue(newPosition);
-            }
+        }
+        
+        float newValue = QInputDialog::getDouble
+            (this,
+             tr("Enter new value"),
+             text,
+             m_mappedValue,
+             min,
+             max,
+             4, 
+             &ok);
+        
+        if (ok) {
+            setMappedValue(newValue);
+        }
+        
+    } else {
+        
+        int newPosition = QInputDialog::getInteger
+            (this,
+             tr("Enter new value"),
+             tr("Enter a new value from %1 to %2:")
+             .arg(minimum()).arg(maximum()),
+             value(), minimum(), maximum(), pageStep(), &ok);
+        
+        if (ok) {
+            setValue(newPosition);
         }
     }
 }
--- a/widgets/Fader.cpp	Fri Jan 05 15:49:10 2007 +0000
+++ b/widgets/Fader.cpp	Fri Jan 12 14:49:18 2007 +0000
@@ -53,13 +53,17 @@
 #include <QWheelEvent>
 #include <QPaintEvent>
 #include <QPainter>
+#include <QInputDialog>
+
+#include <iostream>
 
 Fader::Fader(QWidget *parent, bool withoutKnob) :
     QWidget(parent),
     m_withoutKnob(withoutKnob),
     m_value(1.0),
     m_peakLeft(0.0),
-    m_peakRight(0.0)
+    m_peakRight(0.0),
+    m_mousePressed(false)
 {
     setMinimumSize(116, 23);
     setMaximumSize(116, 23);
@@ -99,75 +103,84 @@
 Fader::mouseMoveEvent(QMouseEvent *ev)
 {
     if (ev->button() == Qt::MidButton) {
+        setValue(1.0);
+        emit valueChanged(1.0);
+        update();
         ev->accept();
         return;
     }
+    if (!m_mousePressed) return;
 
-    int x = ev->x() - 6;
-    const int max_x = 116 - 12;
+    int x = ev->x();
+    int diff = x - m_mousePressX;
+    if (diff == 0) return;
 
-    int value = x;
+    int vx = AudioLevel::multiplier_to_fader
+        (m_mousePressValue, getMaxX(), AudioLevel::LongFader);
 
-    if (value > max_x) {
-	value = max_x;
-    } else if (value < 0) {
-	value = 0;
-    }
+    vx += diff;
 
-//    float fval = float(value) / float(max_x);
+    if (vx > getMaxX()) vx = getMaxX();
+    if (vx < 0) vx = 0;
+
     float fval = AudioLevel::fader_to_multiplier
-	(value, max_x, AudioLevel::LongFader);
+	(vx, getMaxX(), AudioLevel::LongFader);
 
     setValue(fval);
     emit valueChanged(fval);
-
-    update();
+    ev->accept();
 }
 
 
 void
 Fader::mouseReleaseEvent(QMouseEvent *ev)
 {
-    mouseMoveEvent(ev);
+    if (m_mousePressed) {
+        mouseMoveEvent(ev);
+        m_mousePressed = false;
+    }
 }
 
-
 void
 Fader::mouseDoubleClickEvent(QMouseEvent *)
 {
-    setValue(1.0);
-    emit valueChanged(1.0);
-    update();
+    bool ok = false;
+    float min = AudioLevel::fader_to_dB
+        (0, getMaxX(), AudioLevel::LongFader);
+    float max = AudioLevel::fader_to_dB
+        (getMaxX(), getMaxX(), AudioLevel::LongFader);
+    float deft = AudioLevel::multiplier_to_dB(m_value);
+
+    float dB = QInputDialog::getDouble
+        (this,
+         tr("Enter new fader level"),
+         tr("New fader level, from %1 to %2 dBFS:").arg(min).arg(max),
+         deft, min, max, 3, &ok);
+
+    if (ok) {
+        float value = AudioLevel::dB_to_multiplier(dB);
+        setValue(value);
+        emit valueChanged(value);
+        update();
+    }
 }
 
 void
 Fader::mousePressEvent(QMouseEvent *ev)
 {
-    if (ev->button() == Qt::MidButton) {
+    if (ev->button() == Qt::MidButton ||
+        ((ev->button() == Qt::LeftButton) &&
+         (ev->modifiers() & Qt::ControlModifier))) {
         setValue(1.0);
         emit valueChanged(1.0);
         update();
         return;
     }
 
-    int x = ev->x() - 6;
-    const int max_x = 116 - 12;
-
-    int value = x;
-
-    if (value > max_x) {
-	value = max_x;
-    } else if (value < 0) {
-	value = 0;
-    }
-
-    float fval = AudioLevel::fader_to_multiplier
-	(value, max_x, AudioLevel::LongFader);
-
-    setValue(fval);
-    emit valueChanged(fval);
-
-    update();
+    if (ev->button() != Qt::LeftButton) return;
+    m_mousePressed = true;
+    m_mousePressX = ev->x();
+    m_mousePressValue = getValue();
 }
 
 
@@ -203,15 +216,18 @@
     if (m_value != v) {
 	m_value = v;
 	float db = AudioLevel::multiplier_to_dB(m_value);
+        QString text;
 	if (db <= AudioLevel::DB_FLOOR) {
-	    setToolTip(tr("Level: Off"));
+            text = tr("Level: Off");
 	} else {
-	    setToolTip(tr("Level: %1%2.%3%4 dB")
-		       .arg(db < 0.0 ? "-" : "")
-		       .arg(abs(int(db)))
-		       .arg(abs(int(db * 10.0) % 10))
-		       .arg(abs(int(db * 100.0) % 10)));
+            text = tr("Level: %1%2.%3%4 dB")
+                .arg(db < 0.0 ? "-" : "")
+                .arg(abs(int(db)))
+                .arg(abs(int(db * 10.0) % 10))
+                .arg(abs(int(db * 100.0) % 10));
 	}
+        std::cerr << "Fader: setting tooltip to \"" << text.toStdString() << "\"" << std::endl;
+        QWidget::setToolTip(text);
 	update();
     }
 }
@@ -281,4 +297,8 @@
     }
 }
 
-
+int
+Fader::getMaxX() const
+{
+    return 116 - 12;
+}
--- a/widgets/Fader.h	Fri Jan 05 15:49:10 2007 +0000
+++ b/widgets/Fader.h	Fri Jan 12 14:49:18 2007 +0000
@@ -79,11 +79,17 @@
     void valueChanged(float); // 0.0 -> 1.0
 
 private:
+    int getMaxX() const;
+
     bool m_withoutKnob;
     float m_value;
     float m_peakLeft;
     float m_peakRight;
 
+    bool m_mousePressed;
+    int m_mousePressX;
+    float m_mousePressValue;
+
     QPixmap m_back;
     QPixmap m_leds;
     QPixmap m_knob;
--- a/widgets/Thumbwheel.cpp	Fri Jan 05 15:49:10 2007 +0000
+++ b/widgets/Thumbwheel.cpp	Fri Jan 12 14:49:18 2007 +0000
@@ -15,9 +15,12 @@
 
 #include "Thumbwheel.h"
 
+#include "base/RangeMapper.h"
+
 #include <QMouseEvent>
 #include <QPaintEvent>
 #include <QWheelEvent>
+#include <QInputDialog>
 #include <QPainter>
 
 #include <cmath>
@@ -30,6 +33,8 @@
     m_max(100),
     m_default(50),
     m_value(50),
+    m_mappedValue(50),
+    m_noMappedUpdate(false),
     m_rotation(0.5),
     m_orientation(orientation),
     m_speed(1.0),
@@ -37,12 +42,40 @@
     m_showScale(true),
     m_clicked(false),
     m_atDefault(true),
-    m_clickRotation(m_rotation)
+    m_clickRotation(m_rotation),
+    m_showTooltip(true),
+    m_rangeMapper(0)
 {
 }
 
 Thumbwheel::~Thumbwheel()
 {
+    delete m_rangeMapper;
+}
+
+void
+Thumbwheel::setRangeMapper(RangeMapper *mapper)
+{
+    if (m_rangeMapper == mapper) return;
+
+    if (!m_rangeMapper && mapper) {
+        connect(this, SIGNAL(valueChanged(int)),
+                this, SLOT(updateMappedValue(int)));
+    }
+
+    delete m_rangeMapper;
+    m_rangeMapper = mapper;
+
+    updateMappedValue(getValue());
+}
+
+void
+Thumbwheel::setShowToolTip(bool show)
+{
+    m_showTooltip = show;
+    m_noMappedUpdate = true;
+    updateMappedValue(getValue());
+    m_noMappedUpdate = false;
 }
 
 void
@@ -98,6 +131,25 @@
     }
 }
 
+void
+Thumbwheel::setMappedValue(float mappedValue)
+{
+    if (m_rangeMapper) {
+        int newValue = m_rangeMapper->getPositionForValue(mappedValue);
+        m_mappedValue = mappedValue;
+        m_noMappedUpdate = true;
+        std::cerr << "Thumbwheel::setMappedValue(" << mappedValue << "): new value is " << newValue << std::endl;
+        if (newValue != getValue()) {
+            setValue(newValue);
+        }
+        emit valueChanged(newValue);
+        m_noMappedUpdate = false;
+    } else {
+        setValue(int(mappedValue));
+        emit valueChanged(int(mappedValue));
+    }
+}
+
 int
 Thumbwheel::getDefaultValue() const
 {
@@ -107,8 +159,8 @@
 void
 Thumbwheel::setValue(int value)
 {
-//    std::cerr << "Thumbwheel::setValue(" << value << ") (from " << m_value
-//              << ", rotation " << m_rotation << ")" << std::endl;
+    std::cerr << "Thumbwheel::setValue(" << value << ") (from " << m_value
+              << ", rotation " << m_rotation << ")" << std::endl;
 
     if (m_value != value) {
 
@@ -138,6 +190,41 @@
     return m_value;
 }
 
+float
+Thumbwheel::getMappedValue() const
+{
+    if (m_rangeMapper) {
+        std::cerr << "Thumbwheel::getMappedValue(): value = " << getValue() << ", mappedValue = " << m_mappedValue << std::endl;
+        return m_mappedValue;
+    }
+    return getValue();
+}
+
+void
+Thumbwheel::updateMappedValue(int value)
+{
+    if (!m_noMappedUpdate) {
+        if (m_rangeMapper) {
+            m_mappedValue = m_rangeMapper->getValueForPosition(value);
+        } else {
+            m_mappedValue = value;
+        }
+    }
+
+    if (m_showTooltip) {
+        QString name = objectName();
+        QString unit = "";
+        QString text;
+        if (m_rangeMapper) unit = m_rangeMapper->getUnit();
+        if (name != "") {
+            text = tr("%1: %2%3").arg(name).arg(m_mappedValue).arg(unit);
+        } else {
+            text = tr("%2%3").arg(m_mappedValue).arg(unit);
+        }
+        setToolTip(text);
+    }
+}
+
 void
 Thumbwheel::setSpeed(float speed)
 {
@@ -177,21 +264,90 @@
 void
 Thumbwheel::mousePressEvent(QMouseEvent *e)
 {
-    if (e->button() == Qt::LeftButton) {
+    if (e->button() == Qt::MidButton ||
+        ((e->button() == Qt::LeftButton) &&
+         (e->modifiers() & Qt::ControlModifier))) {
+        resetToDefault();
+    } else if (e->button() == Qt::LeftButton) {
         m_clicked = true;
         m_clickPos = e->pos();
         m_clickRotation = m_rotation;
-    } else if (e->button() == Qt::MidButton) {
-        resetToDefault();
     }
 }
 
 void
-Thumbwheel::mouseDoubleClickEvent(QMouseEvent *)
+Thumbwheel::mouseDoubleClickEvent(QMouseEvent *mouseEvent)
 {
-    resetToDefault();
+    //!!! needs a common base class with AudioDial
+
+    if (mouseEvent->button() != Qt::LeftButton) {
+        return;
+    }
+
+    bool ok = false;
+
+    if (m_rangeMapper) {
+        
+        float min = m_rangeMapper->getValueForPosition(m_min);
+        float max = m_rangeMapper->getValueForPosition(m_max);
+                
+        if (min > max) { 
+            float tmp = min;
+            min = max;
+            max = tmp;
+        }
+
+        QString unit = m_rangeMapper->getUnit();
+        
+        QString text;
+        if (objectName() != "") {
+            if (unit != "") {
+                text = tr("New value for %1, from %2 to %3 %4:")
+                    .arg(objectName()).arg(min).arg(max).arg(unit);
+            } else {
+                text = tr("New value for %1, from %2 to %3:")
+                    .arg(objectName()).arg(min).arg(max);
+            }
+        } else {
+            if (unit != "") {
+                text = tr("Enter a new value from %1 to %2 %3:")
+                    .arg(min).arg(max).arg(unit);
+            } else {
+                text = tr("Enter a new value from %1 to %2:")
+                    .arg(min).arg(max);
+            }
+        }
+        
+        float newValue = QInputDialog::getDouble
+            (this,
+             tr("Enter new value"),
+             text,
+             m_mappedValue,
+             min,
+             max,
+             4, 
+             &ok);
+        
+        if (ok) {
+            setMappedValue(newValue);
+        }
+        
+    } else {
+        
+        int newValue = QInputDialog::getInteger
+            (this,
+             tr("Enter new value"),
+             tr("Enter a new value from %1 to %2:")
+             .arg(m_min).arg(m_max),
+             getValue(), m_min, m_max, 1, &ok);
+        
+        if (ok) {
+            setValue(newValue);
+        }
+    }
 }
 
+
 void
 Thumbwheel::mouseMoveEvent(QMouseEvent *e)
 {
--- a/widgets/Thumbwheel.h	Fri Jan 05 15:49:10 2007 +0000
+++ b/widgets/Thumbwheel.h	Fri Jan 12 14:49:18 2007 +0000
@@ -18,6 +18,8 @@
 
 #include <QWidget>
 
+class RangeMapper;
+
 class Thumbwheel : public QWidget
 {
     Q_OBJECT
@@ -41,6 +43,12 @@
     virtual void wheelEvent(QWheelEvent *e);
     virtual void paintEvent(QPaintEvent *e);
 
+    void setRangeMapper(RangeMapper *mapper); // I take ownership, will delete
+    const RangeMapper *getRangeMapper() const { return m_rangeMapper; }
+    float getMappedValue() const;
+
+    void setShowToolTip(bool show);
+
     QSize sizeHint() const;
 
 signals:
@@ -54,13 +62,19 @@
     void setTracking(bool tracking);
     void setShowScale(bool show);
     void setValue(int value);
+    void setMappedValue(float mappedValue);
     void resetToDefault();
 
+protected slots:
+    void updateMappedValue(int value);
+
 private:
     int m_min;
     int m_max;
     int m_default;
     int m_value;
+    float m_mappedValue;
+    bool m_noMappedUpdate;
     float m_rotation;
     Qt::Orientation m_orientation;
     float m_speed;
@@ -70,6 +84,8 @@
     bool m_atDefault;
     QPoint m_clickPos;
     float m_clickRotation;
+    bool m_showTooltip;
+    RangeMapper *m_rangeMapper;
 };
 
 #endif