changeset 25:dcdb21b62dbb

* Refactor sparse models. Previously the 1D and time-value models duplicated a lot of code; now there is a base class (SparseModel) templated on the stored point type, and the subclasses define point types with the necessary characteristics. * Add NoteModel, a new SparseModel subclass. * Reorganise local feature description display. Instead of asking the layer to draw its own, just query it for a textual description and draw that in Pane. Greatly simplifies this part of the layer code. * Add local feature descriptions to colour 3D plot and waveform layers. * Add pitch in MIDI-pitch-and-cents to spectrogram layer. * Give AudioGenerator its own mutex to shorten lock times in CallbackPlaySource. * Minor adjustments to layers menu &c
author Chris Cannam
date Thu, 02 Feb 2006 16:10:19 +0000
parents 6b794a2af3d9
children 94381052a6c9
files layer/Colour3DPlotLayer.cpp layer/Colour3DPlotLayer.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h layer/TimeInstantLayer.cpp layer/TimeInstantLayer.h layer/TimeValueLayer.cpp layer/TimeValueLayer.h layer/WaveformLayer.cpp layer/WaveformLayer.h widgets/Pane.cpp
diffstat 11 files changed, 322 insertions(+), 253 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/Colour3DPlotLayer.cpp	Thu Feb 02 16:10:19 2006 +0000
@@ -66,6 +66,96 @@
     cacheInvalid();
 }
 
+bool
+Colour3DPlotLayer::isLayerScrollable() const
+{
+    QPoint discard;
+    return !m_view->shouldIlluminateLocalFeatures(this, discard);
+}
+
+QString
+Colour3DPlotLayer::getFeatureDescription(QPoint &pos) const
+{
+    if (!m_model) return "";
+
+    int x = pos.x();
+    int y = pos.y();
+
+    size_t modelStart = m_model->getStartFrame();
+    size_t modelWindow = m_model->getWindowSize();
+
+    int sx0 = modelWindow *
+	int((getFrameForX(x) - long(modelStart)) / long(modelWindow));
+    int sx1 = sx0 + modelWindow;
+
+    float binHeight = float(m_view->height()) / m_model->getYBinCount();
+    int sy = (m_view->height() - y) / binHeight;
+
+    float value = m_model->getBinValue(sx0, sy);
+    
+    QString binName = m_model->getBinName(sy);
+    if (binName == "") binName = QString("[%1]").arg(sy + 1);
+    else binName = QString("%1 [%2]").arg(binName).arg(sy + 1);
+
+    QString text = tr("Time:\t%1 - %2\nBin:\t%3\nValue:\t%4")
+	.arg(RealTime::frame2RealTime(sx0, m_model->getSampleRate())
+	     .toText(true).c_str())
+	.arg(RealTime::frame2RealTime(sx1, m_model->getSampleRate())
+	     .toText(true).c_str())
+	.arg(binName)
+	.arg(value);
+
+    return text;
+}
+
+int
+Colour3DPlotLayer::getVerticalScaleWidth(QPainter &paint) const
+{
+    if (!m_model) return 0;
+
+    QString sampleText("123");
+    int tw = paint.fontMetrics().width(sampleText);
+
+    for (size_t i = 0; i < m_model->getYBinCount(); ++i) {
+	if (m_model->getBinName(i).length() > sampleText.length()) {
+	    sampleText = m_model->getBinName(i);
+	}
+    }
+    if (sampleText != "123") {
+	tw = std::max(tw, paint.fontMetrics().width(sampleText));
+    }
+
+    return tw + 13;
+}
+
+void
+Colour3DPlotLayer::paintVerticalScale(QPainter &paint, QRect rect) const
+{
+    if (!m_model) return;
+
+    int h = rect.height(), w = rect.width();
+    float binHeight = float(m_view->height()) / m_model->getYBinCount();
+
+//    int textHeight = paint.fontMetrics().height();
+//    int toff = -textHeight + paint.fontMetrics().ascent() + 2;
+
+    for (size_t i = 0; i < m_model->getYBinCount(); ++i) {
+
+	int y0 = m_view->height() - (i * binHeight) - 1;
+	
+	QString text = m_model->getBinName(i);
+	if (text == "") text = QString("[%1]").arg(i + 1);
+
+	paint.drawLine(0, y0, w, y0);
+
+	int cy = y0 - binHeight/2;
+	int ty = cy + paint.fontMetrics().ascent()/2;
+
+//	int tx = w - 10 - paint.fontMetrics().width(text);
+	paint.drawText(10, ty, text);
+    }
+}
+
 void
 Colour3DPlotLayer::paint(QPainter &paint, QRect rect) const
 {
@@ -193,6 +283,9 @@
     std::cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", window size " << m_model->getWindowSize() << std::endl;
 */
 
+    QPoint illuminatePos;
+    bool illuminate = m_view->shouldIlluminateLocalFeatures(this, illuminatePos);
+
     for (int sx = sx0 - 1; sx <= sx1; ++sx) {
 
 	int fx = sx * int(modelWindow);
@@ -223,7 +316,16 @@
 
 	    int w = rx1 - rx0;
 	    if (w < 1) w = 1;
-	    paint.drawRect(rx0, ry0 - h / sh - 1, w, h / sh + 1);
+
+	    QRect r(rx0, ry0 - h / sh - 1, w, h / sh + 1);
+
+	    if (illuminate) {
+		if (r.contains(illuminatePos)) {
+		    paint.setPen(Qt::black);//!!!
+		}
+	    }
+
+	    paint.drawRect(r);
 
 	    if (sx >= 0 && sx < m_cache->width() &&
 		sy >= 0 && sy < m_cache->height()) {
--- a/layer/Colour3DPlotLayer.h	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/Colour3DPlotLayer.h	Thu Feb 02 16:10:19 2006 +0000
@@ -43,10 +43,17 @@
     virtual const Model *getModel() const { return m_model; }
     virtual void paint(QPainter &paint, QRect rect) const;
 
+    virtual int getVerticalScaleWidth(QPainter &) const;
+    virtual void paintVerticalScale(QPainter &paint, QRect rect) const;
+
+    virtual QString getFeatureDescription(QPoint &) const;
+
     virtual int getNearestFeatureFrame(int frame, 
 				       size_t &resolution,
 				       bool snapRight = true) const;
 
+    virtual bool isLayerScrollable() const;
+
     void setModel(const DenseThreeDimensionalModel *model);
 
     virtual int getCompletion() const { return m_model->getCompletion(); }
--- a/layer/SpectrogramLayer.cpp	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/SpectrogramLayer.cpp	Thu Feb 02 16:10:19 2006 +0000
@@ -1472,8 +1472,6 @@
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     std::cerr << "SpectrogramLayer::paint() returning" << std::endl;
 #endif
-
-//!!!    drawLocalFeatureDescription(paint);
 }
 
 int
@@ -1485,141 +1483,6 @@
     return completion;
 }
 
-QRect
-SpectrogramLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const
-{
-    if (!m_model || !m_model->isOK()) return QRect();
-
-    QString timeLabel = tr("Time: ");
-    QString freqLabel = tr("Hz: ");
-    QString dBLabel = tr("dB: ");
-
-    // assume time is widest
-    RealTime rtMin, rtMax;
-    if (!getXBinSourceRange(pos.x(), rtMin, rtMax)) return QRect();
-    QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str());
-    QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str());
-
-    QFontMetrics metrics = paint.fontMetrics();
-
-    int labelwidth = 
-	std::max(std::max(metrics.width(timeLabel),
-			  metrics.width(freqLabel)),
-		 metrics.width(dBLabel));
-
-    int boxwidth = labelwidth + 
-	metrics.width(timeMinText) + metrics.width(timeMaxText);
-
-    int fontHeight = metrics.height();
-    int boxheight = fontHeight * 3 + 4;
-
-    return QRect(0, 0, boxwidth + 20, boxheight + 15);
-}
-
-void
-SpectrogramLayer::paintLocalFeatureDescription(QPainter &paint,
-					       QRect rect, QPoint pos) const
-{
-    int x = pos.x();
-    int y = pos.y();
-
-    if (!m_model || !m_model->isOK()) return;
-
-    float dbMin = 0, dbMax = 0;
-    float freqMin = 0, freqMax = 0;
-    RealTime rtMin, rtMax;
-
-    bool haveDb = false;
-
-    if (!getXBinSourceRange(x, rtMin, rtMax)) return;
-    if (!getYBinSourceRange(y, freqMin, freqMax)) return;
-    if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true;
-
-    QString timeLabel = tr("Time: ");
-    QString freqLabel = tr("Hz: ");
-    QString dBLabel = tr("dB: ");
-
-    QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str());
-    QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str());
-
-    QString freqMinText = QString("%1").arg(freqMin);
-    QString freqMaxText = "";
-    if (freqMax != freqMin) {
-	freqMaxText = QString(" - %1").arg(freqMax);
-    }
-
-    QString dBMinText = "";
-    QString dBMaxText = "";
-
-    if (haveDb) {
-	int dbmxi = int(dbMax - 0.001);
-	int dbmni = int(dbMin - 0.001);
-	dBMinText = QString("%1").arg(dbmni);
-	if (dbmxi != dbmni) dBMaxText = QString(" - %1").arg(dbmxi);
-    }
-
-    QFontMetrics metrics = paint.fontMetrics();
-
-    int labelwidth = 
-	std::max(std::max(metrics.width(timeLabel),
-			  metrics.width(freqLabel)),
-		 metrics.width(dBLabel));
-
-    int minwidth = 
-	std::max(std::max(metrics.width(timeMinText),
-			  metrics.width(freqMinText)),
-		 metrics.width(dBMinText));
-
-    int maxwidth = 
-	std::max(std::max(metrics.width(timeMaxText),
-			  metrics.width(freqMaxText)),
-		 metrics.width(dBMaxText));
-
-    int boxwidth = labelwidth + minwidth + maxwidth;
-
-    int fontAscent = metrics.ascent();
-    int fontHeight = metrics.height();
-
-    int boxheight = fontHeight * 3 + 4;
-
-//    paint.setPen(Qt::white);
-//    paint.setBrush(Qt::NoBrush);
-
-//!!!    int xbase = m_view->width() - boxwidth - 20;
-    int xbase = rect.x() + 5;
-    int ybase = rect.y() + 5;
-
-    paint.drawRect(xbase, ybase, boxwidth + 10,
-		   boxheight + 10 - metrics.descent() + 1);
-
-    paint.drawText(xbase + 5 + labelwidth - metrics.width(timeLabel),
-		   ybase + 5 + fontAscent, timeLabel);
-
-    paint.drawText(xbase + 5 + labelwidth - metrics.width(freqLabel),
-		   ybase + 7 + fontAscent + fontHeight, freqLabel);
-
-    paint.drawText(xbase + 5 + labelwidth - metrics.width(dBLabel),
-		   ybase + 9 + fontAscent + fontHeight * 2, dBLabel);
-    
-    paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(timeMinText),
-		   ybase + 5 + fontAscent, timeMinText);
-
-    paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(freqMinText),
-		   ybase + 7 + fontAscent + fontHeight, freqMinText);
-
-    paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(dBMinText),
-		   ybase + 9 + fontAscent + fontHeight * 2, dBMinText);
-    
-    paint.drawText(xbase + 5 + labelwidth + minwidth,
-		   ybase + 5 + fontAscent, timeMaxText);
-
-    paint.drawText(xbase + 5 + labelwidth + minwidth,
-		   ybase + 7 + fontAscent + fontHeight, freqMaxText);
-    
-    paint.drawText(xbase + 5 + labelwidth + minwidth,
-		   ybase + 9 + fontAscent + fontHeight * 2, dBMaxText);
-}
-
 int
 SpectrogramLayer::getNearestFeatureFrame(int frame, 
 					 size_t &resolution,
@@ -1631,48 +1494,61 @@
     return snapFrame;
 }
 
-/*!!!
+QString
+SpectrogramLayer::getFeatureDescription(QPoint &pos) const
+{
+    int x = pos.x();
+    int y = pos.y();
 
-bool
-SpectrogramLayer::identifyLocalFeatures(bool on, int x, int y)
-{
-    return true; //!!!
-
-    m_identify = on;
-    m_identifyX = x;
-    m_identifyY = y;
-
-    m_view->update();
-*/
-/*
-    if (!m_model || !m_model->isOK()) return false;
-
-    std::cerr << "SpectrogramLayer::identifyLocalFeatures(" << on << "," << x << "," << y << ")" << std::endl;
+    if (!m_model || !m_model->isOK()) return "";
 
     float dbMin = 0, dbMax = 0;
     float freqMin = 0, freqMax = 0;
+    QString pitchMin, pitchMax;
     RealTime rtMin, rtMax;
 
-    if (getXBinSourceRange(x, rtMin, rtMax)) {
-	std::cerr << "Times: " << rtMin << " -> " << rtMax << std::endl;
-    } else return false;
+    bool haveDb = false;
 
-    if (getYBinSourceRange(y, freqMin, freqMax)) {
-	std::cerr << "Frequencies: " << freqMin << " -> " << freqMax << std::endl;
-    } else return false;
+    if (!getXBinSourceRange(x, rtMin, rtMax)) return "";
+    if (!getYBinSourceRange(y, freqMin, freqMax)) return "";
+    if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true;
 
-    if (getXYBinSourceRange(x, y, dbMin, dbMax)) {
-	std::cerr << "dB: " << dbMin << " -> " << dbMax << std::endl;
+    //!!! want to actually do a one-off FFT to recalculate the dB value!
+
+    QString text;
+
+    if (rtMin != rtMax) {
+	text += tr("Time:\t%1 - %2\n")
+	    .arg(rtMin.toText(true).c_str())
+	    .arg(rtMax.toText(true).c_str());
+    } else {
+	text += tr("Time:\t%1\n")
+	    .arg(rtMin.toText(true).c_str());
     }
 
-    m_identifyX = x;
-    m_identifyY = y;
-    m_identify = true;
-*/
-    /*!!!
-    return true;
+    if (freqMin != freqMax) {
+	text += tr("Frequency:\t%1 - %2 Hz\nPitch:\t%3 - %4\n")
+	    .arg(freqMin)
+	    .arg(freqMax)
+	    .arg(Pitch::getPitchLabelForFrequency(freqMin))
+	    .arg(Pitch::getPitchLabelForFrequency(freqMax));
+    } else {
+	text += tr("Frequency:\t%1 Hz\nPitch:\t%2\n")
+	    .arg(freqMin)
+	    .arg(Pitch::getPitchLabelForFrequency(freqMin));
+    }	
+
+    if (haveDb) {
+	if (lrintf(dbMin) != lrintf(dbMax)) {
+	    text += tr("dB:\t%1 - %2").arg(lrintf(dbMin)).arg(lrintf(dbMax));
+	} else {
+	    text += tr("dB:\t%1").arg(lrintf(dbMin));
+	}
+    }
+
+    return text;
 }
-    */
+
 int
 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const
 {
--- a/layer/SpectrogramLayer.h	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/SpectrogramLayer.h	Thu Feb 02 16:10:19 2006 +0000
@@ -51,8 +51,7 @@
     virtual int getVerticalScaleWidth(QPainter &) const;
     virtual void paintVerticalScale(QPainter &paint, QRect rect) const;
 
-    virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const;
-    virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const;
+    virtual QString getFeatureDescription(QPoint &) const;
 
     virtual int getNearestFeatureFrame(int frame, 
 				       size_t &resolution,
--- a/layer/TimeInstantLayer.cpp	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/TimeInstantLayer.cpp	Thu Feb 02 16:10:19 2006 +0000
@@ -139,14 +139,6 @@
     return !m_view->shouldIlluminateLocalFeatures(this, discard);
 }
 
-QRect
-TimeInstantLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const
-{
-    return QRect(0, 0,
-		 std::max(100, paint.fontMetrics().width(tr("No local points"))),
-		 50); //!!! cruddy
-}
-
 SparseOneDimensionalModel::PointList
 TimeInstantLayer::getLocalPoints(int x) const
 {
@@ -181,48 +173,40 @@
     return usePoints;
 }
 
-void
-TimeInstantLayer::paintLocalFeatureDescription(QPainter &paint, QRect rect,
-					       QPoint pos) const
+QString
+TimeInstantLayer::getFeatureDescription(QPoint &pos) const
 {
-    //!!! bleagh
+    int x = pos.x();
 
-    int x = pos.x();
-    
-    if (!m_model || !m_model->getSampleRate()) return;
+    if (!m_model || !m_model->getSampleRate()) return "";
 
     SparseOneDimensionalModel::PointList points = getLocalPoints(x);
 
-    QFontMetrics metrics = paint.fontMetrics();
-    int xbase = rect.x() + 5;
-    int ybase = rect.y() + 5;
-
     if (points.empty()) {
-	QString label = tr("No local points");
 	if (!m_model->isReady()) {
-	    label = tr("In progress");
+	    return tr("In progress");
+	} else {
+	    return tr("No local points");
 	}
-	paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), label);
-	return;
     }
 
     long useFrame = points.begin()->frame;
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
-    QString timeText = QString(tr("Time %1")).arg(rt.toText(true).c_str());
+    
+    QString text;
 
-    int timewidth = metrics.width(timeText);
-    int labelwidth = metrics.width(points.begin()->label);
+    if (points.begin()->label == "") {
+	text = QString(tr("Time:\t%1\nNo label"))
+	    .arg(rt.toText(true).c_str());
+    } else {
+	text = QString(tr("Time:\t%1\nLabel:\t%2"))
+	    .arg(rt.toText(true).c_str())
+	    .arg(points.begin()->label);
+    }
 
-    int boxheight = metrics.height() * 2 + 3;
-    int boxwidth = std::max(timewidth, labelwidth);
-
-    paint.drawRect(xbase, ybase, boxwidth + 10,
-		   boxheight + 10 - metrics.descent() + 1);
-
-    paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), timeText);
-    paint.drawText(xbase + 5, ybase + 7 + metrics.ascent() + metrics.height(),
-		   points.begin()->label);
+    pos = QPoint(getXForFrame(useFrame), pos.y());
+    return text;
 }
 
 int
--- a/layer/TimeInstantLayer.h	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/TimeInstantLayer.h	Thu Feb 02 16:10:19 2006 +0000
@@ -28,8 +28,7 @@
 
     virtual void paint(QPainter &paint, QRect rect) const;
 
-    virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const;
-    virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const;
+    virtual QString getFeatureDescription(QPoint &) const;
 
     virtual int getNearestFeatureFrame(int frame, 
 				       size_t &resolution,
--- a/layer/TimeValueLayer.cpp	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/TimeValueLayer.cpp	Thu Feb 02 16:10:19 2006 +0000
@@ -178,16 +178,6 @@
     return !m_view->shouldIlluminateLocalFeatures(this, discard);
 }
 
-QRect
-TimeValueLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const
-{
-    return QRect(0, 0,
-		 std::max(100, paint.fontMetrics().width(tr("No local points"))),
-		 70); //!!!
-}
-
-//!!! too much in common with TimeInstantLayer
-
 SparseTimeValueModel::PointList
 TimeValueLayer::getLocalPoints(int x) const
 {
@@ -222,52 +212,42 @@
     return usePoints;
 }
 
-void
-TimeValueLayer::paintLocalFeatureDescription(QPainter &paint, QRect rect,
-					     QPoint pos) const
+QString
+TimeValueLayer::getFeatureDescription(QPoint &pos) const
 {
-    //!!! bleagh
+    int x = pos.x();
 
-    int x = pos.x();
-    
-    if (!m_model || !m_model->getSampleRate()) return;
+    if (!m_model || !m_model->getSampleRate()) return "";
 
     SparseTimeValueModel::PointList points = getLocalPoints(x);
 
-    QFontMetrics metrics = paint.fontMetrics();
-    int xbase = rect.x() + 5;
-    int ybase = rect.y() + 5;
-
     if (points.empty()) {
-	QString label = tr("No local points");
 	if (!m_model->isReady()) {
-	    label = tr("In progress");
+	    return tr("In progress");
+	} else {
+	    return tr("No local points");
 	}
-	paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), label);
-	return;
     }
 
     long useFrame = points.begin()->frame;
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
-    QString timeText = QString("%1").arg(rt.toText(true).c_str());
-    QString valueText = QString("%1").arg(points.begin()->value);
+    
+    QString text;
 
-    int timewidth = metrics.width(timeText);
-    int valuewidth = metrics.width(valueText);
-    int labelwidth = metrics.width(points.begin()->label);
+    if (points.begin()->label == "") {
+	text = QString(tr("Time:\t%1\nValue:\t%2\nNo label"))
+	    .arg(rt.toText(true).c_str())
+	    .arg(points.begin()->value);
+    } else {
+	text = QString(tr("Time:\t%1\nValue:\t%2\nLabel:\t%3"))
+	    .arg(rt.toText(true).c_str())
+	    .arg(points.begin()->value)
+	    .arg(points.begin()->label);
+    }
 
-    int boxheight = metrics.height() * 3 + 4;
-    int boxwidth = std::max(std::max(timewidth, labelwidth), valuewidth);
-    
-    paint.drawRect(xbase, ybase, boxwidth + 10,
-		   boxheight + 10 - metrics.descent() + 1);
-
-    paint.drawText(xbase + 5, ybase + 5 + metrics.ascent(), timeText);
-    paint.drawText(xbase + 5, ybase + 7 + metrics.ascent() + metrics.height(),
-		   valueText);
-    paint.drawText(xbase + 5, ybase + 9 + metrics.ascent() + 2*metrics.height(),
-		   points.begin()->label);
+    pos = QPoint(getXForFrame(useFrame), getYForValue(points.begin()->value));
+    return text;
 }
 
 int
--- a/layer/TimeValueLayer.h	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/TimeValueLayer.h	Thu Feb 02 16:10:19 2006 +0000
@@ -28,8 +28,7 @@
 
     virtual void paint(QPainter &paint, QRect rect) const;
 
-    virtual QRect getFeatureDescriptionRect(QPainter &, QPoint) const;
-    virtual void paintLocalFeatureDescription(QPainter &, QRect, QPoint) const;
+    virtual QString getFeatureDescription(QPoint &) const;
 
     virtual int getNearestFeatureFrame(int frame, 
 				       size_t &resolution,
--- a/layer/WaveformLayer.cpp	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/WaveformLayer.cpp	Thu Feb 02 16:10:19 2006 +0000
@@ -399,8 +399,8 @@
     y0 = rect.top();
     y1 = rect.bottom();
 
-    long frame0 = startFrame + x0 * zoomLevel;
-    long frame1 = startFrame + (x1 + 1) * zoomLevel;
+    long frame0 = getFrameForX(x0);
+    long frame1 = getFrameForX(x1 + 1);
      
 #ifdef DEBUG_WAVEFORM_PAINT
     std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << ")" <<  std::endl;
@@ -646,6 +646,74 @@
     }
 }
 
+QString
+WaveformLayer::getFeatureDescription(QPoint &pos) const
+{
+    int x = pos.x();
+
+    if (!m_model || !m_model->isOK()) return "";
+
+    long f0 = getFrameForX(x);
+    long f1 = getFrameForX(x + 1);
+
+    if (f0 < 0) f0 = 0;
+    if (f1 <= f0) return "";
+    
+    QString text;
+
+    RealTime rt0 = RealTime::frame2RealTime(f0, m_model->getSampleRate());
+    RealTime rt1 = RealTime::frame2RealTime(f1, m_model->getSampleRate());
+
+    if (f1 != f0 + 1 && (rt0.sec != rt1.sec || rt0.msec() != rt1.msec())) {
+	text += tr("Time:\t%1 - %2")
+	    .arg(rt0.toText(true).c_str())
+	    .arg(rt1.toText(true).c_str());
+    } else {
+	text += tr("Time:\t%1")
+	    .arg(rt0.toText(true).c_str());
+    }
+
+    size_t channels = 0, minChannel = 0, maxChannel = 0;
+    bool mergingChannels = false;
+
+    channels = getChannelArrangement(minChannel, maxChannel, mergingChannels);
+    if (channels == 0) return "";
+
+    for (size_t ch = minChannel; ch <= maxChannel; ++ch) {
+
+	size_t blockSize = m_view->getZoomLevel();
+	RangeSummarisableTimeValueModel::RangeBlock ranges =
+	    m_model->getRanges(ch, f0, f1, blockSize);
+
+	if (ranges.empty()) continue;
+	
+	RangeSummarisableTimeValueModel::Range range = ranges[0];
+	
+	QString label = tr("Level:");
+	if (minChannel != maxChannel) {
+	    if (ch == 0) label = tr("Left:");
+	    else if (ch == 1) label = tr("Right:");
+	    else label = tr("Channel %1").arg(ch + 1);
+	}
+
+	int min = int(range.min * 1000);
+	int max = int(range.max * 1000);
+	int db = int(AudioLevel::multiplier_to_dB(std::max(fabsf(range.min),
+							   fabsf(range.max)))
+		     * 100);
+
+	if (min != max) {
+	    text += tr("\n%1\t%2 - %3 (%4 dB peak)")
+		.arg(label).arg(float(min)/1000).arg(float(max)/1000).arg(float(db)/100);
+	} else {
+	    text += tr("\n%1\t%2 (%3 dB peak)")
+		.arg(label).arg(float(min)/1000).arg(float(db)/100);
+	}
+    }
+
+    return text;
+}
+
 int
 WaveformLayer::getVerticalScaleWidth(QPainter &paint) const
 {
--- a/layer/WaveformLayer.h	Wed Feb 01 14:49:49 2006 +0000
+++ b/layer/WaveformLayer.h	Thu Feb 02 16:10:19 2006 +0000
@@ -33,6 +33,8 @@
     virtual const Model *getModel() const { return m_model; }
     virtual void paint(QPainter &paint, QRect rect) const;
 
+    virtual QString getFeatureDescription(QPoint &) const;
+
     virtual int getVerticalScaleWidth(QPainter &) const;
     virtual void paintVerticalScale(QPainter &paint, QRect rect) const;
 
--- a/widgets/Pane.cpp	Wed Feb 01 14:49:49 2006 +0000
+++ b/widgets/Pane.cpp	Thu Feb 02 16:10:19 2006 +0000
@@ -113,6 +113,57 @@
 	}
 
 	if (m_identifyFeatures) {
+
+	    QPoint pos = m_identifyPoint;
+	    QString desc = (*vi)->getFeatureDescription(pos);
+	    
+	    if (desc != "") {
+
+		paint.save();
+
+		int tabStop =
+		    paint.fontMetrics().width(tr("Some lengthy prefix:"));
+
+		QRect boundingRect = 
+		    paint.fontMetrics().boundingRect
+		    (rect(),
+		     Qt::AlignRight | Qt::AlignTop | Qt::TextExpandTabs,
+		     desc, tabStop);
+
+		if (hasLightBackground()) {
+		    paint.setPen(Qt::NoPen);
+		    paint.setBrush(QColor(250, 250, 250, 200));
+		} else {
+		    paint.setPen(Qt::NoPen);
+		    paint.setBrush(QColor(50, 50, 50, 200));
+		}
+
+		int extra = paint.fontMetrics().descent();
+		paint.drawRect(width() - boundingRect.width() - 10 - extra,
+			       10 - extra,
+			       boundingRect.width() + 2 * extra,
+			       boundingRect.height() + extra);
+
+		if (hasLightBackground()) {
+		    paint.setPen(QColor(150, 20, 0));
+		} else {
+		    paint.setPen(QColor(255, 150, 100));
+		}
+		
+		QTextOption option;
+		option.setWrapMode(QTextOption::NoWrap);
+		option.setAlignment(Qt::AlignRight | Qt::AlignTop);
+		option.setTabStop(tabStop);
+		paint.drawText(QRectF(width() - boundingRect.width() - 10, 10,
+				      boundingRect.width(),
+				      boundingRect.height()),
+			       desc,
+			       option);
+
+		paint.restore();
+	    }
+
+/*!!!
 	    QRect descRect = (*vi)->getFeatureDescriptionRect(paint,
 							      m_identifyPoint);
 	    if (descRect.width() > 0 && descRect.height() > 0 &&
@@ -137,6 +188,8 @@
 		
 		paint.restore();
 	    }
+
+*/
 	}
 
 	break;