changeset 374:64e84e5efb76 spectrogram-cache-rejig

* Merge from trunk
author Chris Cannam
date Wed, 27 Feb 2008 11:59:42 +0000
parents 6440e280122e
children 44670ce11a0c
files layer/Colour3DPlotLayer.cpp layer/Colour3DPlotLayer.h layer/ImageLayer.cpp layer/ImageLayer.h layer/Layer.cpp layer/Layer.h layer/LayerFactory.cpp layer/LayerFactory.h layer/NoteLayer.cpp layer/NoteLayer.h layer/SingleColourLayer.cpp layer/SingleColourLayer.h layer/SliceLayer.cpp layer/SliceLayer.h layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h layer/SpectrumLayer.cpp layer/SpectrumLayer.h layer/TextLayer.cpp layer/TextLayer.h layer/TimeInstantLayer.cpp layer/TimeInstantLayer.h layer/TimeRulerLayer.cpp layer/TimeRulerLayer.h layer/TimeValueLayer.cpp layer/TimeValueLayer.h layer/WaveformLayer.cpp layer/WaveformLayer.h view/Overview.cpp view/Pane.cpp view/Pane.h view/PaneStack.cpp view/PaneStack.h view/View.cpp view/View.h view/ViewManager.cpp view/ViewManager.h widgets/AudioDial.cpp widgets/AudioDial.h widgets/ImageDialog.cpp widgets/LabelCounterInputDialog.cpp widgets/LayerTree.cpp widgets/LayerTree.h widgets/LayerTreeDialog.cpp widgets/LayerTreeDialog.h widgets/PluginParameterBox.cpp widgets/PluginParameterDialog.cpp widgets/PluginParameterDialog.h widgets/PropertyBox.cpp widgets/PropertyBox.h widgets/PropertyStack.cpp widgets/widgets.pro
diffstat 52 files changed, 2059 insertions(+), 565 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/Colour3DPlotLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -39,7 +39,8 @@
     m_colourScale(LinearScale),
     m_colourMap(0),
     m_normalizeColumns(false),
-    m_normalizeVisibleArea(false)
+    m_normalizeVisibleArea(false),
+    m_invertVertical(false)
 {
     
 }
@@ -87,6 +88,7 @@
     list.push_back("Colour Scale");
     list.push_back("Normalize Columns");
     list.push_back("Normalize Visible Area");
+    list.push_back("Invert Vertical Scale");
     return list;
 }
 
@@ -97,6 +99,16 @@
     if (name == "Colour Scale") return tr("Scale");
     if (name == "Normalize Columns") return tr("Normalize Columns");
     if (name == "Normalize Visible Area") return tr("Normalize Visible Area");
+    if (name == "Invert Vertical Scale") return tr("Invert Vertical Scale");
+    return "";
+}
+
+QString
+Colour3DPlotLayer::getPropertyIconName(const PropertyName &name) const
+{
+    if (name == "Normalize Columns") return "normalise-columns";
+    if (name == "Normalize Visible Area") return "normalise";
+    if (name == "Invert Vertical Scale") return "invert-vertical";
     return "";
 }
 
@@ -105,6 +117,7 @@
 {
     if (name == "Normalize Columns") return ToggleProperty;
     if (name == "Normalize Visible Area") return ToggleProperty;
+    if (name == "Invert Vertical Scale") return ToggleProperty;
     return ValueProperty;
 }
 
@@ -113,6 +126,7 @@
 {
     if (name == "Normalize Columns" ||
         name == "Normalize Visible Area" ||
+        name == "Invert Vertical Scale" ||
 	name == "Colour Scale") return tr("Scale");
     return QString();
 }
@@ -154,6 +168,11 @@
         *deflt = 0;
 	val = (m_normalizeVisibleArea ? 1 : 0);
 
+    } else if (name == "Invert Vertical Scale") {
+	
+        *deflt = 0;
+	val = (m_invertVertical ? 1 : 0);
+
     } else {
 	val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
     }
@@ -195,6 +214,8 @@
 	setNormalizeColumns(value ? true : false);
     } else if (name == "Normalize Visible Area") {
 	setNormalizeVisibleArea(value ? true : false);
+    } else if (name == "Invert Vertical Scale") {
+	setInvertVertical(value ? true : false);
     }
 }
 
@@ -246,6 +267,21 @@
     return m_normalizeVisibleArea;
 }
 
+void
+Colour3DPlotLayer::setInvertVertical(bool n)
+{
+    if (m_invertVertical == n) return;
+    m_invertVertical = n;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+bool
+Colour3DPlotLayer::getInvertVertical() const
+{
+    return m_invertVertical;
+}
+
 bool
 Colour3DPlotLayer::isLayerScrollable(const View *v) const
 {
@@ -278,6 +314,8 @@
     float binHeight = float(v->height()) / m_model->getHeight();
     int sy = int((v->height() - y) / binHeight);
 
+    if (m_invertVertical) sy = m_model->getHeight() - sy - 1;
+
     float value = m_model->getValueAt(sx0, sy);
 
 //    std::cerr << "bin value (" << sx0 << "," << sy << ") is " << value << std::endl;
@@ -358,12 +396,15 @@
 
     for (size_t i = 0; i < m_model->getHeight(); ++i) {
 
-        if ((i % step) != 0) continue;
+        size_t idx = i;
+        if (m_invertVertical) idx = m_model->getHeight() - idx - 1;
+
+        if ((idx % step) != 0) continue;
 
 	int y0 = int(v->height() - (i * binHeight) - 1);
 	
-	QString text = m_model->getBinName(i);
-	if (text == "") text = QString("[%1]").arg(i + 1);
+	QString text = m_model->getBinName(idx);
+	if (text == "") text = QString("[%1]").arg(idx + 1);
 
 	paint.drawLine(cw, y0, w, y0);
 
@@ -518,7 +559,12 @@
             if (pixel < 0) pixel = 0;
             if (pixel > 255) pixel = 255;
 
-            m_cache->setPixel(c - firstBin, y, pixel);
+            if (m_invertVertical) {
+                m_cache->setPixel(c - firstBin, m_model->getHeight() - y - 1,
+                                  pixel);
+            } else {
+                m_cache->setPixel(c - firstBin, y, pixel);
+            }
         }
     }
 }
@@ -574,14 +620,21 @@
     fillCache(sx0 < 0 ? 0 : sx0,
               sx1 < 0 ? 0 : sx1);
 
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+    std::cerr << "Colour3DPlotLayer::paint: height = "<< m_model->getHeight() << ", modelStart = " << modelStart << ", resolution = " << modelResolution << ", model rate = " << m_model->getSampleRate() << std::endl;
+#endif
+
     if (int(m_model->getHeight()) >= v->height() ||
-        int(modelResolution) < v->getZoomLevel() / 2) {
+        int(modelResolution * m_model->getSampleRate()) < v->getZoomLevel() / 2) {
+#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
+        std::cerr << "calling paintDense" << std::endl;
+#endif
         paintDense(v, paint, rect);
         return;
     }
 
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
-    std::cerr << "Colour3DPlotLayer::paint: w " << w << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sw << ", sh " << sh << std::endl;
+    std::cerr << "Colour3DPlotLayer::paint: w " << x1-x0 << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sx1-sx0 << ", sh " << sh << std::endl;
     std::cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", resolution " << m_model->getResolution() << std::endl;
 #endif
 
@@ -596,7 +649,7 @@
         
 	int fx = sx * int(modelResolution);
 
-	if (fx + int(modelResolution) < int(modelStart) ||
+	if (fx + int(modelResolution) <= int(modelStart) ||
 	    fx > int(modelEnd)) continue;
 
         int rx0 = v->getXForFrame(int((fx + int(modelStart)) * srRatio));
@@ -687,7 +740,7 @@
 
     for (int x = x0; x < x1; ++x) {
 
-        long xf = long(v->getFrameForX(x) / srRatio);
+        long xf = long(v->getFrameForX(x));
         if (xf < 0) {
             for (int y = 0; y < h; ++y) {
                 img.setPixel(x - x0, y, m_cache->color(0));
@@ -695,6 +748,8 @@
             continue;
         }
 
+        xf /= srRatio;
+
         float sx0 = (float(xf) - modelStart) / modelResolution;
         float sx1 = (float(v->getFrameForX(x+1) / srRatio) - modelStart) / modelResolution;
             
--- a/layer/Colour3DPlotLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/Colour3DPlotLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -75,6 +75,7 @@
     virtual PropertyList getProperties() const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
     virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual QString getPropertyIconName(const PropertyName &) const;
     virtual QString getPropertyGroupName(const PropertyName &) const;
     virtual int getPropertyRangeAndValue(const PropertyName &,
                                          int *min, int *max, int *deflt) const;
@@ -97,6 +98,9 @@
     void setNormalizeVisibleArea(bool n);
     bool getNormalizeVisibleArea() const;
 
+    void setInvertVertical(bool i);
+    bool getInvertVertical() const;
+
     virtual const Model *getSliceableModel() const { return m_model; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
@@ -116,6 +120,7 @@
     int         m_colourMap;
     bool        m_normalizeColumns;
     bool        m_normalizeVisibleArea;
+    bool        m_invertVertical;
 
     void getColumn(size_t col, DenseThreeDimensionalModel::Column &) const;
 
--- a/layer/ImageLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/ImageLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -30,6 +30,7 @@
 #include <QInputDialog>
 #include <QMutexLocker>
 #include <QTextStream>
+#include <QMessageBox>
 
 #include <iostream>
 #include <cmath>
@@ -802,7 +803,7 @@
 }
 
 void
-ImageLayer::copy(Selection s, Clipboard &to)
+ImageLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -812,20 +813,39 @@
     for (ImageModel::PointList::iterator i = points.begin();
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
-            //!!! inadequate
             Clipboard::Point point(i->frame, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
             to.addPoint(point);
         }
     }
 }
 
 bool
-ImageLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */)
+ImageLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
 {
     if (!m_model) return false;
 
     const Clipboard::PointList &points = from.getPoints();
 
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v, tr("Re-align pasted items?"),
+                                  tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
     ImageModel::EditCommand *command =
 	new ImageModel::EditCommand(m_model, tr("Paste"));
 
@@ -833,10 +853,23 @@
          i != points.end(); ++i) {
         
         if (!i->haveFrame()) continue;
+
         size_t frame = 0;
-        if (frameOffset > 0 || -frameOffset < i->getFrame()) {
-            frame = i->getFrame() + frameOffset;
+
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
         }
+
         ImageModel::Point newPoint(frame);
 
         //!!! inadequate
@@ -879,7 +912,7 @@
             return;
         }
 
-        FileSource *rf = new FileSource(img, true);
+        FileSource *rf = new FileSource(img, FileSource::ProgressDialog);
         if (rf->isOK()) {
             std::cerr << "ok, adding it (local filename = " << rf->getLocalFilename().toStdString() << ")" << std::endl;
             m_remoteFiles[img] = rf;
--- a/layer/ImageLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/ImageLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -58,8 +58,8 @@
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(Selection s, Clipboard &to);
-    virtual bool paste(const Clipboard &from, int frameOffset,
+    virtual void copy(View *v, Selection s, Clipboard &to);
+    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
                        bool interactive);
 
     virtual bool editOpen(View *, QMouseEvent *); // on double-click
--- a/layer/Layer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/Layer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -68,10 +68,16 @@
 	(LayerFactory::getInstance()->getLayerType(this));
 }
 
+void
+Layer::setPresentationName(QString name)
+{
+    m_presentationName = name;
+}
+
 QString
 Layer::getLayerPresentationName() const
 {
-//    QString layerName = objectName();
+    if (m_presentationName != "") return m_presentationName;
 
     LayerFactory *factory = LayerFactory::getInstance();
     QString layerName = factory->getLayerPresentationName
@@ -159,6 +165,106 @@
     return true;
 }
 
+size_t
+Layer::alignToReference(View *v, size_t frame) const
+{
+    const Model *m = getModel();
+    std::cerr << "Layer::alignToReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << std::endl;
+    if (m && m->getAlignmentReference()) {
+        return m->alignToReference(frame);
+    } else {
+        return v->alignToReference(frame);
+    }
+}
+
+size_t
+Layer::alignFromReference(View *v, size_t frame) const
+{
+    const Model *m = getModel();
+    std::cerr << "Layer::alignFromReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << std::endl;
+    if (m && m->getAlignmentReference()) {
+        return m->alignFromReference(frame);
+    } else {
+        return v->alignFromReference(frame);
+    }
+}
+
+bool
+Layer::clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const
+{
+    // Notes on pasting to an aligned layer:
+    // 
+    // Each point may have a reference frame that may differ from the
+    // point's given frame (in its source model).  If it has no
+    // reference frame, we have to assume the source model was not
+    // aligned or was the reference model: when cutting or copying
+    // points from a layer, we must always set their reference frame
+    // correctly if we are aligned.
+    // 
+    // When pasting:
+    // - if point's reference and aligned frames differ:
+    //   - if this layer is aligned:
+    //     - if point's aligned frame matches this layer's aligned version
+    //       of point's reference frame:
+    //       - we can paste at reference frame or our frame
+    //     - else
+    //       - we can paste at reference frame, result of aligning reference
+    //         frame in our model, or literal source frame
+    //   - else
+    //     - we can paste at reference (our) frame, or literal source frame
+    // - else
+    //   - if this layer is aligned:
+    //     - we can paste at reference (point's only available) frame,
+    //       or result of aligning reference frame in our model
+    //   - else
+    //     - we can only paste at reference frame
+    // 
+    // Which of these alternatives are useful?
+    //
+    // Example: we paste between two tracks that are aligned to the
+    // same reference, and the points are at 10s and 20s in the source
+    // track, corresponding to 5s and 10s in the reference but 20s and
+    // 30s in the target track.
+    // 
+    // The obvious default is to paste at 20s and 30s; if we aren't
+    // doing that, would it be better to paste at 5s and 10s or at 10s
+    // and 20s?  We probably don't ever want to do the former, do we?
+    // We either want to be literal all the way through, or aligned
+    // all the way through.
+
+    for (Clipboard::PointList::const_iterator i = clip.getPoints().begin();
+         i != clip.getPoints().end(); ++i) {
+
+        // In principle, we want to know whether the aligned version
+        // of the reference frame in our layer is the same as the
+        // source frame contained in the clipboard point.  However,
+        // because of rounding during alignment, that won't
+        // necessarily be the case even if the clipboard point came
+        // from our layer!  What we need to check is whether, if we
+        // aligned the clipboard point's frame back to the reference
+        // using this layer's alignment, we would obtain the same
+        // reference frame as that for the clipboard point.
+
+        // What if the clipboard point has no reference frame?  Then
+        // we have to treat it as having its own frame as the
+        // reference (i.e. having been copied from the reference
+        // model).
+        
+        long sourceFrame = i->getFrame();
+        long referenceFrame = sourceFrame;
+        if (i->haveReferenceFrame()) {
+            referenceFrame = i->getReferenceFrame();
+        }
+        long myMappedFrame = alignToReference(v, sourceFrame);
+
+//        std::cerr << "sourceFrame = " << sourceFrame << ", referenceFrame = " << referenceFrame << " (have = " << i->haveReferenceFrame() << "), myMappedFrame = " << myMappedFrame << std::endl;
+
+        if (myMappedFrame != referenceFrame) return true;
+    }
+
+    return false;
+}
+
 bool
 Layer::MeasureRect::operator<(const MeasureRect &mr) const
 {
@@ -488,6 +594,11 @@
 {
     stream << indent;
 
+    if (m_presentationName != "") {
+        extraAttributes = QString("%1 presentationName=\"%2\"")
+            .arg(extraAttributes).arg(encodeEntities(m_presentationName));
+    }
+
     stream << QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5")
 	.arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
                             (LayerFactory::getInstance()->getLayerType(this))))
@@ -517,6 +628,11 @@
 {
     stream << indent;
 
+    if (m_presentationName != "") {
+        extraAttributes = QString("%1 presentationName=\"%2\"")
+            .arg(extraAttributes).arg(encodeEntities(m_presentationName));
+    }
+
     stream << QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5/>\n")
 	.arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
                             (LayerFactory::getInstance()->getLayerType(this))))
--- a/layer/Layer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/Layer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -92,9 +92,12 @@
     virtual QString getPropertyContainerIconName() const;
 
     virtual QString getPropertyContainerName() const {
-	return objectName();
+        if (m_presentationName != "") return m_presentationName;
+	else return objectName();
     }
 
+    virtual void setPresentationName(QString name);
+
     virtual QString getLayerPresentationName() const;
     virtual QPixmap getLayerPresentationPixmap(QSize) const { return QPixmap(); }
 
@@ -151,7 +154,7 @@
 	return false;
     }
 
-    // Draw and edit modes:
+    // Draw, erase, and edit modes:
     //
     // Layer needs to get actual mouse events, I guess.  Draw mode is
     // probably the easier.
@@ -160,6 +163,10 @@
     virtual void drawDrag(View *, QMouseEvent *) { }
     virtual void drawEnd(View *, QMouseEvent *) { }
 
+    virtual void eraseStart(View *, QMouseEvent *) { }
+    virtual void eraseDrag(View *, QMouseEvent *) { }
+    virtual void eraseEnd(View *, QMouseEvent *) { }
+
     virtual void editStart(View *, QMouseEvent *) { }
     virtual void editDrag(View *, QMouseEvent *) { }
     virtual void editEnd(View *, QMouseEvent *) { }
@@ -189,7 +196,7 @@
     virtual void resizeSelection(Selection, Selection /* newSize */) { }
     virtual void deleteSelection(Selection) { }
 
-    virtual void copy(Selection, Clipboard & /* to */) { }
+    virtual void copy(View *, Selection, Clipboard & /* to */) { }
 
     /**
      * Paste from the given clipboard onto the layer at the given
@@ -198,7 +205,8 @@
      * return false if the user cancelled the paste operation.  This
      * function should return true if a paste actually occurred.
      */
-    virtual bool paste(const Clipboard & /* from */,
+    virtual bool paste(View *,
+                       const Clipboard & /* from */,
                        int /* frameOffset */,
                        bool /* interactive */) { return false; }
 
@@ -461,6 +469,10 @@
 protected:
     void connectSignals(const Model *);
 
+    virtual size_t alignToReference(View *v, size_t frame) const;
+    virtual size_t alignFromReference(View *v, size_t frame) const;
+    bool clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const;
+
     struct MeasureRect {
 
         mutable QRect pixrect;
@@ -536,6 +548,8 @@
     void paintMeasurementRect(View *v, QPainter &paint,
                               const MeasureRect &r, bool focus) const;
 
+    QString m_presentationName;
+
 private:
     mutable QMutex m_dormancyMutex;
     mutable std::map<const void *, bool> m_dormancy;
--- a/layer/LayerFactory.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/LayerFactory.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -28,6 +28,8 @@
 #include "SliceLayer.h"
 #include "SliceableLayer.h"
 
+#include "base/Clipboard.h"
+
 #include "data/model/RangeSummarisableTimeValueModel.h"
 #include "data/model/DenseTimeValueModel.h"
 #include "data/model/SparseOneDimensionalModel.h"
@@ -350,6 +352,10 @@
 	dynamic_cast<SpectrogramLayer *>(layer)->setChannel(channel);
 	return;
     }
+    if (dynamic_cast<SpectrumLayer *>(layer)) {
+	dynamic_cast<SpectrumLayer *>(layer)->setChannel(channel);
+	return;
+    }
 }
 
 Layer *
@@ -418,8 +424,8 @@
 	std::cerr << "LayerFactory::createLayer: Unknown layer type " 
 		  << type << std::endl;
     } else {
-	std::cerr << "LayerFactory::createLayer: Setting object name "
-		  << getLayerPresentationName(type).toStdString() << " on " << layer << std::endl;
+//	std::cerr << "LayerFactory::createLayer: Setting object name "
+//		  << getLayerPresentationName(type).toStdString() << " on " << layer << std::endl;
 	layer->setObjectName(getLayerPresentationName(type));
         setLayerDefaultProperties(type, layer);
     }
@@ -430,14 +436,14 @@
 void
 LayerFactory::setLayerDefaultProperties(LayerType type, Layer *layer)
 {
-    std::cerr << "LayerFactory::setLayerDefaultProperties: type " << type << " (name \"" << getLayerTypeName(type).toStdString() << "\"" << std::endl;
+//    std::cerr << "LayerFactory::setLayerDefaultProperties: type " << type << " (name \"" << getLayerTypeName(type).toStdString() << "\")" << std::endl;
 
     QSettings settings;
     settings.beginGroup("LayerDefaults");
     QString defaults = settings.value(getLayerTypeName(type), "").toString();
     if (defaults == "") return;
 
-    std::cerr << "defaults=\"" << defaults.toStdString() << "\"" << std::endl;
+//    std::cerr << "defaults=\"" << defaults.toStdString() << "\"" << std::endl;
 
     QString xml = layer->toXmlString();
     QDomDocument docOld, docNew;
@@ -453,9 +459,9 @@
         for (unsigned int i = 0; i < attrNodes.length(); ++i) {
             QDomAttr attr = attrNodes.item(i).toAttr();
             if (attr.isNull()) continue;
-            std::cerr << "append \"" << attr.name().toStdString()
-                      << "\" -> \"" << attr.value().toStdString() << "\""
-                      << std::endl;
+//            std::cerr << "append \"" << attr.name().toStdString()
+//                      << "\" -> \"" << attr.value().toStdString() << "\""
+//                      << std::endl;
             attrs.append(attr.name(), "", "", attr.value());
         }
         
@@ -465,9 +471,9 @@
             QDomAttr attr = attrNodes.item(i).toAttr();
             if (attr.isNull()) continue;
             if (attrs.value(attr.name()) == "") {
-                std::cerr << "append \"" << attr.name().toStdString()
-                          << "\" -> \"" << attr.value().toStdString() << "\""
-                          << std::endl;
+//                std::cerr << "append \"" << attr.name().toStdString()
+//                          << "\" -> \"" << attr.value().toStdString() << "\""
+//                          << std::endl;
                 attrs.append(attr.name(), "", "", attr.value());
             }
         }
@@ -478,3 +484,24 @@
     settings.endGroup();
 }
 
+LayerFactory::LayerType
+LayerFactory::getLayerTypeForClipboardContents(const Clipboard &clip)
+{
+    const Clipboard::PointList &contents = clip.getPoints();
+
+    bool haveFrame = false;
+    bool haveValue = false;
+    bool haveDuration = false;
+
+    for (Clipboard::PointList::const_iterator i = contents.begin();
+         i != contents.end(); ++i) {
+        if (i->haveFrame()) haveFrame = true;
+        if (i->haveValue()) haveValue = true;
+        if (i->haveDuration()) haveDuration = true;
+    }
+
+    if (haveFrame && haveValue && haveDuration) return Notes;
+    if (haveFrame && haveValue) return TimeValues;
+    return TimeInstants;
+}
+    
--- a/layer/LayerFactory.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/LayerFactory.h	Wed Feb 27 11:59:42 2008 +0000
@@ -21,6 +21,7 @@
 
 class Layer;
 class Model;
+class Clipboard;
 
 class LayerFactory
 {
@@ -76,6 +77,8 @@
     QString getLayerTypeName(LayerType);
     LayerType getLayerTypeForName(QString);
 
+    LayerType getLayerTypeForClipboardContents(const Clipboard &);
+
 protected:
     template <typename LayerClass, typename ModelClass>
     bool trySetModel(Layer *layerBase, Model *modelBase) {
--- a/layer/NoteLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/NoteLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -33,6 +33,7 @@
 #include <QPainterPath>
 #include <QMouseEvent>
 #include <QTextStream>
+#include <QMessageBox>
 
 #include <iostream>
 #include <cmath>
@@ -41,8 +42,8 @@
     SingleColourLayer(),
     m_model(0),
     m_editing(false),
-    m_originalPoint(0, 0.0, 0, tr("New Point")),
-    m_editingPoint(0, 0.0, 0, tr("New Point")),
+    m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
     m_editingCommand(0),
     m_verticalScale(AutoAlignScale)
 {
@@ -619,7 +620,7 @@
 
     float value = getValueForY(v, e->y());
 
-    m_editingPoint = NoteModel::Point(frame, value, 0, tr("New Point"));
+    m_editingPoint = NoteModel::Point(frame, value, 0, 0.8, tr("New Point"));
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) m_editingCommand->finish();
@@ -670,6 +671,51 @@
 }
 
 void
+NoteLayer::eraseStart(View *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    NoteModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+
+    m_editingPoint = *points.begin();
+
+    if (m_editingCommand) {
+	m_editingCommand->finish();
+	m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+NoteLayer::eraseDrag(View *v, QMouseEvent *e)
+{
+}
+
+void
+NoteLayer::eraseEnd(View *v, QMouseEvent *e)
+{
+    if (!m_model || !m_editing) return;
+
+    m_editing = false;
+
+    NoteModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+    if (points.begin()->frame != m_editingPoint.frame ||
+        points.begin()->value != m_editingPoint.value) return;
+
+    m_editingCommand = new NoteModel::EditCommand
+        (m_model, tr("Erase Point"));
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    m_editingCommand->finish();
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
 NoteLayer::editStart(View *v, QMouseEvent *e)
 {
 //    std::cerr << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
@@ -871,7 +917,7 @@
 }    
 
 void
-NoteLayer::copy(Selection s, Clipboard &to)
+NoteLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -881,19 +927,39 @@
     for (NoteModel::PointList::iterator i = points.begin();
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
-            Clipboard::Point point(i->frame, i->value, i->duration, i->label);
+            Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
             to.addPoint(point);
         }
     }
 }
 
 bool
-NoteLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */)
+NoteLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
 {
     if (!m_model) return false;
 
     const Clipboard::PointList &points = from.getPoints();
 
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v, tr("Re-align pasted items?"),
+                                  tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
     NoteModel::EditCommand *command =
 	new NoteModel::EditCommand(m_model, tr("Paste"));
 
@@ -902,15 +968,28 @@
         
         if (!i->haveFrame()) continue;
         size_t frame = 0;
-        if (frameOffset > 0 || -frameOffset < i->getFrame()) {
-            frame = i->getFrame() + frameOffset;
+
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
         }
+
         NoteModel::Point newPoint(frame);
   
         if (i->haveLabel()) newPoint.label = i->getLabel();
         if (i->haveValue()) newPoint.value = i->getValue();
         else newPoint.value = (m_model->getValueMinimum() +
                                m_model->getValueMaximum()) / 2;
+        if (i->haveLevel()) newPoint.level = i->getLevel();
         if (i->haveDuration()) newPoint.duration = i->getDuration();
         else {
             size_t nextFrame = frame;
--- a/layer/NoteLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/NoteLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -44,6 +44,10 @@
     virtual void drawDrag(View *v, QMouseEvent *);
     virtual void drawEnd(View *v, QMouseEvent *);
 
+    virtual void eraseStart(View *v, QMouseEvent *);
+    virtual void eraseDrag(View *v, QMouseEvent *);
+    virtual void eraseEnd(View *v, QMouseEvent *);
+
     virtual void editStart(View *v, QMouseEvent *);
     virtual void editDrag(View *v, QMouseEvent *);
     virtual void editEnd(View *v, QMouseEvent *);
@@ -54,8 +58,8 @@
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(Selection s, Clipboard &to);
-    virtual bool paste(const Clipboard &from, int frameOffset,
+    virtual void copy(View *v, Selection s, Clipboard &to);
+    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
--- a/layer/SingleColourLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SingleColourLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -22,12 +22,15 @@
 #include <QTextStream>
 #include <QApplication>
 
+//#define DEBUG_COLOUR_SELECTION 1
+
 SingleColourLayer::ColourRefCount 
 SingleColourLayer::m_colourRefCount;
 
 SingleColourLayer::SingleColourLayer() :
     m_colour(0),
-    m_colourExplicitlySet(false)
+    m_colourExplicitlySet(false),
+    m_defaultColourSet(false)
 {
     setDefaultColourFor(0);
 }
@@ -125,7 +128,13 @@
 void
 SingleColourLayer::setDefaultColourFor(View *v)
 {
-    if (m_colourExplicitlySet) return;
+#ifdef DEBUG_COLOUR_SELECTION
+    std::cerr << "SingleColourLayer::setDefaultColourFor: m_colourExplicitlySet = " << m_colourExplicitlySet << ", m_defaultColourSet " << m_defaultColourSet << std::endl;
+#endif
+
+    if (m_colourExplicitlySet || m_defaultColourSet) return;
+
+    if (v) m_defaultColourSet = true; // v==0 case doesn't really count
 
     bool dark = false;
     if (v) {
@@ -148,9 +157,13 @@
         // means we're being called from the constructor, and this is
         // a virtual function
         hint = getDefaultColourHint(dark, impose);
-//        std::cerr << "hint = " << hint << ", impose = " << impose << std::endl;
+#ifdef DEBUG_COLOUR_SELECTION
+        std::cerr << "hint = " << hint << ", impose = " << impose << std::endl;
+#endif
     } else {
-//        std::cerr << "(from ctor)" << std::endl;
+#ifdef DEBUG_COLOUR_SELECTION
+        std::cerr << "(from ctor)" << std::endl;
+#endif
     }
 
     if (hint >= 0 && impose) {
@@ -171,15 +184,21 @@
             count = m_colourRefCount[index];
         }
 
-//        std::cerr << "index = " << index << ", count = " << count;
+#ifdef DEBUG_COLOUR_SELECTION
+        std::cerr << "index = " << index << ", count = " << count;
+#endif
 
         if (bestColour < 0 || count < bestCount) {
             bestColour = index;
             bestCount = count;
-//            std::cerr << " *";
+#ifdef DEBUG_COLOUR_SELECTION
+            std::cerr << " *";
+#endif
         }
 
-//        std::cerr << std::endl;
+#ifdef DEBUG_COLOUR_SELECTION
+        std::cerr << std::endl;
+#endif
     }
     
     if (bestColour < 0) m_colour = 0;
@@ -289,7 +308,9 @@
 
     if (m_colour != colour) {
 
+#ifdef DEBUG_COLOUR_SELECTION
         std::cerr << "SingleColourLayer::setProperties: changing colour from " << m_colour << " to " << colour << std::endl;
+#endif
 
         if (m_colourRefCount.find(m_colour) != m_colourRefCount.end() &&
             m_colourRefCount[m_colour] > 0) {
--- a/layer/SingleColourLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SingleColourLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -70,6 +70,7 @@
 
     int m_colour;
     bool m_colourExplicitlySet;
+    bool m_defaultColourSet;
 };
 
 #endif
--- a/layer/SliceLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SliceLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -569,6 +569,13 @@
     return SingleColourLayer::getPropertyLabel(name);
 }
 
+QString
+SliceLayer::getPropertyIconName(const PropertyName &name) const
+{
+    if (name == "Normalize") return "normalise";
+    return "";
+}
+
 Layer::PropertyType
 SliceLayer::getPropertyType(const PropertyName &name) const
 {
--- a/layer/SliceLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SliceLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -52,6 +52,7 @@
 
     virtual PropertyList getProperties() const;
     virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual QString getPropertyIconName(const PropertyName &) const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
     virtual QString getPropertyGroupName(const PropertyName &) const;
     virtual int getPropertyRangeAndValue(const PropertyName &,
--- a/layer/SpectrogramLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SpectrogramLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -184,6 +184,14 @@
 }
 
 QString
+SpectrogramLayer::getPropertyIconName(const PropertyName &name) const
+{
+    if (name == "Normalize Columns") return "normalise-columns";
+    if (name == "Normalize Visible Area") return "normalise";
+    return "";
+}
+
+QString
 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const
 {
     if (name == "Colour") return tr("Colour");
--- a/layer/SpectrogramLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SpectrogramLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -79,6 +79,7 @@
 
     virtual PropertyList getProperties() const;
     virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual QString getPropertyIconName(const PropertyName &) const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
     virtual QString getPropertyGroupName(const PropertyName &) const;
     virtual int getPropertyRangeAndValue(const PropertyName &,
--- a/layer/SpectrumLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SpectrumLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -47,35 +47,64 @@
 
 SpectrumLayer::~SpectrumLayer()
 {
-    //!!! delete parent's model
-//    for (size_t i = 0; i < m_fft.size(); ++i) delete m_fft[i];
+    Model *m = const_cast<Model *>
+        (static_cast<const Model *>(m_sliceableModel));
+    m->aboutToDelete();
+    m_sliceableModel = 0;
+    delete m;
 }
 
 void
 SpectrumLayer::setModel(DenseTimeValueModel *model)
 {
+    std::cerr << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << std::endl;
+    
     if (m_originModel == model) return;
+
     m_originModel = model;
 
     if (m_sliceableModel) {
-        const Model *oldModel = m_sliceableModel;
+        Model *m = const_cast<Model *>
+            (static_cast<const Model *>(m_sliceableModel));
+        m->aboutToDelete();
         setSliceableModel(0);
-        // surprised I'm allowed to delete a const pointer -- may be a
-        // source of future compiler rejection?
-        delete oldModel;
+        delete m;
     }
-//!!!    setupFFT();
+
+    m_newFFTNeeded = true;
+
+    emit layerParametersChanged();
+}
+
+void
+SpectrumLayer::setChannel(int channel)
+{
+    std::cerr << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << std::endl;
+    
+    m_channelSet = true;
+    
+    if (m_channel == channel) return;
+
+    m_channel = channel;
+
+    m_newFFTNeeded = true;
+
+    emit layerParametersChanged();
 }
 
 void
 SpectrumLayer::setupFFT()
 {
-    FFTModel *oldFFT = dynamic_cast<FFTModel *>
-        (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
-    
-    if (oldFFT) {
+    if (m_sliceableModel) {
+        Model *m = const_cast<Model *>
+            (static_cast<const Model *>(m_sliceableModel));
+        m->aboutToDelete();
         setSliceableModel(0);
-        delete oldFFT;
+        delete m;
+    }
+
+    if (!m_originModel) {
+        return;
     }
 
     FFTModel *newFFT = new FFTModel(m_originModel,
@@ -97,24 +126,8 @@
     }
 
     newFFT->resume();
-}
 
-void
-SpectrumLayer::setChannel(int channel)
-{
-    m_channelSet = true;
-
-    FFTModel *fft = dynamic_cast<FFTModel *>
-        (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
-
-    if (m_channel == channel) {
-        if (fft) fft->resume();
-        return;
-    }
-
-    m_channel = channel;
-
-    emit layerParametersChanged();
+    m_newFFTNeeded = false;
 }
 
 Layer::PropertyList
@@ -136,6 +149,13 @@
     return SliceLayer::getPropertyLabel(name);
 }
 
+QString
+SpectrumLayer::getPropertyIconName(const PropertyName &name) const
+{
+    if (name == "Show Peak Frequencies") return "show-peaks";
+    return SliceLayer::getPropertyIconName(name);
+}
+
 Layer::PropertyType
 SpectrumLayer::getPropertyType(const PropertyName &name) const
 {
@@ -633,11 +653,14 @@
 SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
 {
     if (!m_originModel || !m_originModel->isOK() ||
-        !m_originModel->isReady()) return;
+        !m_originModel->isReady()) {
+        std::cerr << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << std::endl;
+        return;
+    }
 
     if (m_newFFTNeeded) {
+        std::cerr << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << std::endl;
         const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
-        m_newFFTNeeded = false;
     }
 
     FFTModel *fft = dynamic_cast<FFTModel *>
@@ -653,6 +676,8 @@
         pkh = 10;
 //!!!    }
 
+    paint.save();
+
     if (fft && m_showPeaks) {
 
         // draw peak lines
@@ -792,6 +817,8 @@
 	    px = x;
 	}
 //    }
+
+    paint.restore();
 }
 
 void
--- a/layer/SpectrumLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/SpectrumLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -24,6 +24,7 @@
 #include "data/model/DenseTimeValueModel.h"
 
 #include <QColor>
+#include <QMutex>
 
 class FFTModel;
 
@@ -52,6 +53,7 @@
 
     virtual PropertyList getProperties() const;
     virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual QString getPropertyIconName(const PropertyName &) const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
     virtual QString getPropertyGroupName(const PropertyName &) const;
     virtual int getPropertyRangeAndValue(const PropertyName &,
@@ -112,6 +114,8 @@
     bool                    m_showPeaks;
     mutable bool            m_newFFTNeeded;
 
+    mutable QMutex m_fftMutex;
+
     void setupFFT();
 
     virtual void getBiasCurve(BiasCurve &) const;
--- a/layer/TextLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TextLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -27,6 +27,7 @@
 #include <QMouseEvent>
 #include <QInputDialog>
 #include <QTextStream>
+#include <QMessageBox>
 
 #include <iostream>
 #include <cmath>
@@ -448,6 +449,51 @@
 }
 
 void
+TextLayer::eraseStart(View *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    TextModel::PointList points = getLocalPoints(v, e->x(), e->y());
+    if (points.empty()) return;
+
+    m_editingPoint = *points.begin();
+
+    if (m_editingCommand) {
+	m_editingCommand->finish();
+	m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+TextLayer::eraseDrag(View *v, QMouseEvent *e)
+{
+}
+
+void
+TextLayer::eraseEnd(View *v, QMouseEvent *e)
+{
+    if (!m_model || !m_editing) return;
+
+    m_editing = false;
+
+    TextModel::PointList points = getLocalPoints(v, e->x(), e->y());
+    if (points.empty()) return;
+    if (points.begin()->frame != m_editingPoint.frame ||
+        points.begin()->height != m_editingPoint.height) return;
+
+    m_editingCommand = new TextModel::EditCommand
+        (m_model, tr("Erase Point"));
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    m_editingCommand->finish();
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
 TextLayer::editStart(View *v, QMouseEvent *e)
 {
 //    std::cerr << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
@@ -626,7 +672,7 @@
 }
 
 void
-TextLayer::copy(Selection s, Clipboard &to)
+TextLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -637,18 +683,38 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->height, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
             to.addPoint(point);
         }
     }
 }
 
 bool
-TextLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */)
+TextLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
 {
     if (!m_model) return false;
 
     const Clipboard::PointList &points = from.getPoints();
 
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v, tr("Re-align pasted items?"),
+                                  tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
     TextModel::EditCommand *command =
 	new TextModel::EditCommand(m_model, tr("Paste"));
 
@@ -667,9 +733,21 @@
         
         if (!i->haveFrame()) continue;
         size_t frame = 0;
-        if (frameOffset > 0 || -frameOffset < i->getFrame()) {
-            frame = i->getFrame() + frameOffset;
+        
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
         }
+
         TextModel::Point newPoint(frame);
 
         if (i->haveValue()) {
--- a/layer/TextLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TextLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -44,6 +44,10 @@
     virtual void drawDrag(View *v, QMouseEvent *);
     virtual void drawEnd(View *v, QMouseEvent *);
 
+    virtual void eraseStart(View *v, QMouseEvent *);
+    virtual void eraseDrag(View *v, QMouseEvent *);
+    virtual void eraseEnd(View *v, QMouseEvent *);
+
     virtual void editStart(View *v, QMouseEvent *);
     virtual void editDrag(View *v, QMouseEvent *);
     virtual void editEnd(View *v, QMouseEvent *);
@@ -52,8 +56,8 @@
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(Selection s, Clipboard &to);
-    virtual bool paste(const Clipboard &from, int frameOffset,
+    virtual void copy(View *v, Selection s, Clipboard &to);
+    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
                        bool interactive);
 
     virtual bool editOpen(View *, QMouseEvent *); // on double-click
--- a/layer/TimeInstantLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TimeInstantLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -25,10 +25,12 @@
 #include "data/model/SparseOneDimensionalModel.h"
 
 #include "widgets/ItemEditDialog.h"
+#include "widgets/ListInputDialog.h"
 
 #include <QPainter>
 #include <QMouseEvent>
 #include <QTextStream>
+#include <QMessageBox>
 
 #include <iostream>
 #include <cmath>
@@ -493,6 +495,50 @@
 }
 
 void
+TimeInstantLayer::eraseStart(View *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+
+    m_editingPoint = *points.begin();
+
+    if (m_editingCommand) {
+	m_editingCommand->finish();
+	m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+TimeInstantLayer::eraseDrag(View *v, QMouseEvent *e)
+{
+}
+
+void
+TimeInstantLayer::eraseEnd(View *v, QMouseEvent *e)
+{
+    if (!m_model || !m_editing) return;
+
+    m_editing = false;
+
+    SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+    if (points.begin()->frame != m_editingPoint.frame) return;
+
+    m_editingCommand = new SparseOneDimensionalModel::EditCommand
+        (m_model, tr("Erase Point"));
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    m_editingCommand->finish();
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
 TimeInstantLayer::editStart(View *v, QMouseEvent *e)
 {
     std::cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << std::endl;
@@ -667,7 +713,7 @@
 }
 
 void
-TimeInstantLayer::copy(Selection s, Clipboard &to)
+TimeInstantLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -678,18 +724,38 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
             to.addPoint(point);
         }
     }
 }
 
 bool
-TimeInstantLayer::paste(const Clipboard &from, int frameOffset, bool)
+TimeInstantLayer::paste(View *v, const Clipboard &from, int frameOffset, bool)
 {
     if (!m_model) return false;
 
     const Clipboard::PointList &points = from.getPoints();
 
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v, tr("Re-align pasted instants?"),
+                                  tr("The instants you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
     SparseOneDimensionalModel::EditCommand *command =
 	new SparseOneDimensionalModel::EditCommand(m_model, tr("Paste"));
 
@@ -697,10 +763,29 @@
          i != points.end(); ++i) {
         
         if (!i->haveFrame()) continue;
+
         size_t frame = 0;
-        if (frameOffset > 0 || -frameOffset < i->getFrame()) {
-            frame = i->getFrame() + frameOffset;
+
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
         }
+
+        if (frameOffset > 0) frame += frameOffset;
+        else if (frameOffset < 0) {
+            if (frame > -frameOffset) frame += frameOffset;
+            else frame = 0;
+        }
+
         SparseOneDimensionalModel::Point newPoint(frame);
         if (i->haveLabel()) {
             newPoint.label = i->getLabel();
--- a/layer/TimeInstantLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TimeInstantLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -45,6 +45,10 @@
     virtual void drawDrag(View *v, QMouseEvent *);
     virtual void drawEnd(View *v, QMouseEvent *);
 
+    virtual void eraseStart(View *v, QMouseEvent *);
+    virtual void eraseDrag(View *v, QMouseEvent *);
+    virtual void eraseEnd(View *v, QMouseEvent *);
+
     virtual void editStart(View *v, QMouseEvent *);
     virtual void editDrag(View *v, QMouseEvent *);
     virtual void editEnd(View *v, QMouseEvent *);
@@ -55,8 +59,8 @@
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(Selection s, Clipboard &to);
-    virtual bool paste(const Clipboard &from, int frameOffset,
+    virtual void copy(View *v, Selection s, Clipboard &to);
+    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -101,6 +105,8 @@
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
+    bool clipboardAlignmentDiffers(View *v, const Clipboard &) const;
+
     SparseOneDimensionalModel *m_model;
     bool m_editing;
     SparseOneDimensionalModel::Point m_editingPoint;
--- a/layer/TimeRulerLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TimeRulerLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -15,6 +15,8 @@
 
 #include "TimeRulerLayer.h"
 
+#include "LayerFactory.h"
+
 #include "data/model/Model.h"
 #include "base/RealTime.h"
 #include "base/ColourDatabase.h"
@@ -25,6 +27,8 @@
 #include <iostream>
 #include <cmath>
 
+//#define DEBUG_TIME_RULER_LAYER 1
+
 using std::cerr;
 using std::endl;
 
@@ -190,104 +194,150 @@
 void
 TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const
 {
-//    std::cerr << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
-//	      << ") [" << rect.width() << "x" << rect.height() << "]" << std::endl;
+#ifdef DEBUG_TIME_RULER_LAYER
+    std::cerr << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
+	      << ") [" << rect.width() << "x" << rect.height() << "]" << std::endl;
+#endif
     
     if (!m_model || !m_model->isOK()) return;
 
     int sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
-    long startFrame = v->getStartFrame();
-    long endFrame = v->getEndFrame();
+    long startFrame = v->getFrameForX(rect.x() - 50);
 
-    int zoomLevel = v->getZoomLevel();
-
-    long rectStart = startFrame + (rect.x() - 100) * zoomLevel;
-    long rectEnd = startFrame + (rect.x() + rect.width() + 100) * zoomLevel;
-
-//    std::cerr << "TimeRulerLayer::paint: calling paint.save()" << std::endl;
-    paint.save();
-//!!!    paint.setClipRect(v->rect());
-
-    int minPixelSpacing = 50;
+#ifdef DEBUG_TIME_RULER_LAYER
+    std::cerr << "start frame = " << startFrame << std::endl;
+#endif
 
     bool quarter = false;
     int incms = getMajorTickSpacing(v, quarter);
 
-    RealTime rt = RealTime::frame2RealTime(rectStart, sampleRate);
-    long ms = rt.sec * 1000 + rt.msec();
+    int ms = lrint(1000.0 * (double(startFrame) / double(sampleRate)));
     ms = (ms / incms) * incms - incms;
 
-    RealTime incRt = RealTime::fromMilliseconds(incms);
-    long incFrame = RealTime::realTime2Frame(incRt, sampleRate);
-    int incX = incFrame / zoomLevel;
+#ifdef DEBUG_TIME_RULER_LAYER
+    std::cerr << "start ms = " << ms << " at step " << incms << std::endl;
+#endif
+
+    // Calculate the number of ticks per increment -- approximate
+    // values for x and frame counts here will do, no rounding issue.
+    // We always use the exact incms in our calculations for where to
+    // draw the actual ticks or lines.
+
+    int minPixelSpacing = 50;
+    long incFrame = (incms * sampleRate) / 1000;
+    int incX = incFrame / v->getZoomLevel();
     int ticks = 10;
     if (incX < minPixelSpacing * 2) {
 	ticks = quarter ? 4 : 5;
     }
 
-    QRect oldClipRect = rect;
-    QRect newClipRect(oldClipRect.x() - 25, oldClipRect.y(),
-		      oldClipRect.width() + 50, oldClipRect.height());
-    paint.setClipRect(newClipRect);
-    paint.setClipRect(rect);
+    QColor greyColour = getPartialShades(v)[1];
 
-    QColor greyColour = getPartialShades(v)[1];
+    paint.save();
 
     while (1) {
 
-	rt = RealTime::fromMilliseconds(ms);
+        // frame is used to determine where to draw the lines, so it
+        // needs to correspond to an exact pixel (so that we don't get
+        // a different pixel when scrolling a small amount and
+        // re-drawing with a different start frame).
+
+        double dms = ms;
+        long frame = lrint((dms * sampleRate) / 1000.0);
+        frame /= v->getZoomLevel();
+        frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel
+
 	ms += incms;
 
-	long frame = RealTime::realTime2Frame(rt, sampleRate);
-	if (frame >= rectEnd) break;
+        int x = v->getXForFrame(frame);
 
-	int x = (frame - startFrame) / zoomLevel;
-	if (x < rect.x() || x >= rect.x() + rect.width()) continue;
+#ifdef DEBUG_TIME_RULER_LAYER
+        std::cerr << "Considering frame = " << frame << ", x = " << x << std::endl;
+#endif
 
-	paint.setPen(greyColour);
-	paint.drawLine(x, 0, x, v->height());
+        if (x >= rect.x() + rect.width() + 50) {
+#ifdef DEBUG_TIME_RULER_LAYER
+            std::cerr << "X well out of range, ending here" << std::endl;
+#endif
+            break;
+        }
 
-	paint.setPen(getBaseQColor());
-	paint.drawLine(x, 0, x, 5);
-	paint.drawLine(x, v->height() - 6, x, v->height() - 1);
+	if (x >= rect.x() - 50) {
 
-	QString text(QString::fromStdString(rt.toText()));
+#ifdef DEBUG_TIME_RULER_LAYER
+            std::cerr << "X in range, drawing line here" << std::endl;
+#endif
 
-	int y;
-	QFontMetrics metrics = paint.fontMetrics();
-	switch (m_labelHeight) {
-	default:
-	case LabelTop:
-	    y = 6 + metrics.ascent();
-	    break;
-	case LabelMiddle:
-	    y = v->height() / 2 - metrics.height() / 2 + metrics.ascent();
-	    break;
-	case LabelBottom:
-	    y = v->height() - metrics.height() + metrics.ascent() - 6;
-	}
+            RealTime rt = RealTime::fromMilliseconds(ms);
 
-	int tw = metrics.width(text);
+            QString text(QString::fromStdString(rt.toText()));
+            QFontMetrics metrics = paint.fontMetrics();
+            int tw = metrics.width(text);
 
-        if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
-            ViewManager::NoOverlays) {
+            if (tw < 50 &&
+                (x < rect.x() - tw/2 ||
+                 x >= rect.x() + rect.width() + tw/2)) {
+#ifdef DEBUG_TIME_RULER_LAYER
+                std::cerr << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << std::endl;
+#endif
+            }
 
-            if (v->getLayer(0) == this) {
-                // backmost layer, don't worry about outlining the text
-                paint.drawText(x+2 - tw/2, y, text);
-            } else {
-                v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText);
+            paint.setPen(greyColour);
+            paint.drawLine(x, 0, x, v->height());
+
+            paint.setPen(getBaseQColor());
+            paint.drawLine(x, 0, x, 5);
+            paint.drawLine(x, v->height() - 6, x, v->height() - 1);
+
+            int y;
+            switch (m_labelHeight) {
+            default:
+            case LabelTop:
+                y = 6 + metrics.ascent();
+                break;
+            case LabelMiddle:
+                y = v->height() / 2 - metrics.height() / 2 + metrics.ascent();
+                break;
+            case LabelBottom:
+                y = v->height() - metrics.height() + metrics.ascent() - 6;
+            }
+
+            if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
+                ViewManager::NoOverlays) {
+
+                if (v->getLayer(0) == this) {
+                    // backmost layer, don't worry about outlining the text
+                    paint.drawText(x+2 - tw/2, y, text);
+                } else {
+                    v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText);
+                }
             }
         }
 
 	paint.setPen(greyColour);
 
 	for (int i = 1; i < ticks; ++i) {
-	    rt = rt + (incRt / ticks);
-	    frame = RealTime::realTime2Frame(rt, sampleRate);
-	    x = (frame - startFrame) / zoomLevel;
+
+            dms = ms - incms + (i * double(incms)) / ticks;
+            frame = lrint((dms * sampleRate) / 1000.0);
+            frame /= v->getZoomLevel();
+            frame *= v->getZoomLevel(); // exact pixel as above
+
+            x = v->getXForFrame(frame);
+
+            if (x < rect.x() || x >= rect.x() + rect.width()) {
+#ifdef DEBUG_TIME_RULER_LAYER
+//                std::cerr << "tick " << i << ": X out of range, going on to next tick" << std::endl;
+#endif
+                continue;
+            }
+
+#ifdef DEBUG_TIME_RULER_LAYER
+            std::cerr << "tick " << i << " in range, drawing at " << x << std::endl;
+#endif
+
 	    int sz = 5;
 	    if (ticks == 10) {
 		if ((i % 2) == 1) {
@@ -314,6 +364,14 @@
         (QString(darkbg ? "White" : "Black"));
 }
 
+QString TimeRulerLayer::getLayerPresentationName() const
+{
+    LayerFactory *factory = LayerFactory::getInstance();
+    QString layerName = factory->getLayerPresentationName
+        (factory->getLayerType(this));
+    return layerName;
+}
+
 void
 TimeRulerLayer::toXml(QTextStream &stream,
                       QString indent, QString extraAttributes) const
--- a/layer/TimeRulerLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TimeRulerLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -51,6 +51,8 @@
         return false;
     }
 
+    virtual QString getLayerPresentationName() const;
+
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
--- a/layer/TimeValueLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TimeValueLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -23,6 +23,7 @@
 #include "view/View.h"
 
 #include "data/model/SparseTimeValueModel.h"
+#include "data/model/Labeller.h"
 
 #include "widgets/ItemEditDialog.h"
 #include "widgets/ListInputDialog.h"
@@ -35,6 +36,8 @@
 #include <QMouseEvent>
 #include <QRegExp>
 #include <QTextStream>
+#include <QMessageBox>
+#include <QInputDialog>
 
 #include <iostream>
 #include <cmath>
@@ -538,6 +541,8 @@
     int sampleRate = m_model->getSampleRate();
     if (!sampleRate) return;
 
+    paint.setRenderHint(QPainter::Antialiasing, false);
+
 //    Profiler profiler("TimeValueLayer::paint", true);
 
     int x0 = rect.left(), x1 = rect.right();
@@ -599,6 +604,9 @@
         if (m_plotStyle != PlotSegmentation) {
             textY = y - paint.fontMetrics().height()
                       + paint.fontMetrics().ascent();
+            if (textY < paint.fontMetrics().ascent() + 1) {
+                textY = paint.fontMetrics().ascent() + 1;
+            }
         }
 
 	bool haveNext = false;
@@ -940,6 +948,51 @@
 }
 
 void
+TimeValueLayer::eraseStart(View *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+
+    m_editingPoint = *points.begin();
+
+    if (m_editingCommand) {
+	m_editingCommand->finish();
+	m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+TimeValueLayer::eraseDrag(View *v, QMouseEvent *e)
+{
+}
+
+void
+TimeValueLayer::eraseEnd(View *v, QMouseEvent *e)
+{
+    if (!m_model || !m_editing) return;
+
+    m_editing = false;
+
+    SparseTimeValueModel::PointList points = getLocalPoints(v, e->x());
+    if (points.empty()) return;
+    if (points.begin()->frame != m_editingPoint.frame ||
+        points.begin()->value != m_editingPoint.value) return;
+
+    m_editingCommand = new SparseTimeValueModel::EditCommand
+        (m_model, tr("Erase Point"));
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    m_editingCommand->finish();
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
 TimeValueLayer::editStart(View *v, QMouseEvent *e)
 {
 //    std::cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
@@ -1136,7 +1189,7 @@
 }    
 
 void
-TimeValueLayer::copy(Selection s, Clipboard &to)
+TimeValueLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1147,45 +1200,55 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->value, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
             to.addPoint(point);
         }
     }
 }
 
 bool
-TimeValueLayer::paste(const Clipboard &from, int frameOffset,
+TimeValueLayer::paste(View *v, const Clipboard &from, int frameOffset,
                       bool interactive)
 {
     if (!m_model) return false;
 
     const Clipboard::PointList &points = from.getPoints();
 
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v, tr("Re-align pasted items?"),
+                                  tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
     SparseTimeValueModel::EditCommand *command =
 	new SparseTimeValueModel::EditCommand(m_model, tr("Paste"));
 
-    //!!! Replace all this with a use of Labeller
-
     enum ValueAvailability {
         UnknownAvailability,
         NoValues,
         SomeValues,
         AllValues
     };
-    enum ValueGeneration {
-        GenerateNone,
-        GenerateFromCounter,
-        GenerateFromFrameNumber,
-        GenerateFromRealTime,
-        GenerateFromRealTimeDifference,
-        GenerateFromTempo,
-        GenerateFromExistingNeighbour,
-        GenerateFromLabels
-    };
 
-    ValueGeneration generation = GenerateNone;
+    Labeller::ValueType generation = Labeller::ValueNone;
 
     bool haveUsableLabels = false;
     bool haveExistingItems = !(m_model->isEmpty());
+    Labeller labeller;
+    labeller.setSampleRate(m_model->getSampleRate());
 
     if (interactive) {
 
@@ -1232,38 +1295,18 @@
                 text = tr("Some of the items you are pasting do not have values.\nWhat values do you want to use for these items?");
             }
 
+            Labeller::TypeNameMap names = labeller.getTypeNames();
+
             QStringList options;
-            std::vector<int> genopts;
+            std::vector<Labeller::ValueType> genopts;
 
-            options << tr("Zero for all items");
-            genopts.push_back(int(GenerateNone));
-
-            options << tr("Whole numbers counting from 1");
-            genopts.push_back(int(GenerateFromCounter));
-
-            options << tr("Item's audio sample frame number");
-            genopts.push_back(int(GenerateFromFrameNumber));
-
-            options << tr("Item's time in seconds");
-            genopts.push_back(int(GenerateFromRealTime));
-
-            options << tr("Duration from the item to the following item");
-            genopts.push_back(int(GenerateFromRealTimeDifference));
-
-            options << tr("Tempo in bpm derived from the duration");
-            genopts.push_back(int(GenerateFromTempo));
-
-            if (haveExistingItems) {
-                options << tr("Value of the nearest existing item");
-                genopts.push_back(int(GenerateFromExistingNeighbour));
+            for (Labeller::TypeNameMap::const_iterator i = names.begin();
+                 i != names.end(); ++i) {
+                if (i->first == Labeller::ValueNone) options << tr("Zero for all items");
+                else options << i->second;
+                genopts.push_back(i->first);
             }
 
-            if (haveUsableLabels) {
-                options << tr("Value extracted from the item's label (where possible)");
-                genopts.push_back(int(GenerateFromLabels));
-            }
-
-
             static int prevSelection = 0;
 
             bool ok = false;
@@ -1273,32 +1316,54 @@
 
             if (!ok) return false;
             int selection = 0;
-            generation = GenerateNone;
+            generation = Labeller::ValueNone;
 
             for (QStringList::const_iterator i = options.begin();
                  i != options.end(); ++i) {
                 if (selected == *i) {
-                    generation = ValueGeneration(genopts[selection]);
+                    generation = genopts[selection];
                     break;
                 }
                 ++selection;
             }
+            
+            labeller.setType(generation);
+
+            if (generation == Labeller::ValueFromCyclicalCounter ||
+                generation == Labeller::ValueFromTwoLevelCounter) {
+                int cycleSize = QInputDialog::getInteger
+                    (0, tr("Select cycle size"),
+                     tr("Cycle size:"), 4, 2, 16, 1);
+                labeller.setCounterCycleSize(cycleSize);
+            }
 
             prevSelection = selection;
         }
     }
 
-    int counter = 1;
-    float prevBpm = 120.f;
+    SparseTimeValueModel::Point prevPoint(0);
 
     for (Clipboard::PointList::const_iterator i = points.begin();
          i != points.end(); ++i) {
         
         if (!i->haveFrame()) continue;
+
         size_t frame = 0;
-        if (frameOffset > 0 || -frameOffset < i->getFrame()) {
-            frame = i->getFrame() + frameOffset;
+
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
         }
+
         SparseTimeValueModel::Point newPoint(frame);
   
         if (i->haveLabel()) {
@@ -1307,81 +1372,33 @@
             newPoint.label = QString("%1").arg(i->getValue());
         }
 
+        bool usePrev = false;
+        SparseTimeValueModel::Point formerPrevPoint = prevPoint;
+
         if (i->haveValue()) {
             newPoint.value = i->getValue();
         } else {
-            
-            switch (generation) {
-
-            case GenerateNone:
-                newPoint.value = 0;
-                break;
-
-            case GenerateFromCounter:
-                newPoint.value = counter;
-                break;
-
-            case GenerateFromFrameNumber:
-                newPoint.value = frame;
-                break;
-
-            case GenerateFromRealTime: 
-                newPoint.value = float(frame) / float(m_model->getSampleRate());
-                break;
-
-            case GenerateFromRealTimeDifference:
-            case GenerateFromTempo:
-            {
-                size_t nextFrame = frame;
-                Clipboard::PointList::const_iterator j = i;
-                for (; j != points.end(); ++j) {
-                    if (!j->haveFrame()) continue;
-                    if (j != i) break;
-                }
-                if (j != points.end()) {
-                    nextFrame = j->getFrame();
-                }
-                if (generation == GenerateFromRealTimeDifference) {
-                    newPoint.value = float(nextFrame - frame) /
-                        float(m_model->getSampleRate());
-                } else {
-                    float bpm = prevBpm;
-                    if (nextFrame > frame) {
-                        bpm = (60.f * m_model->getSampleRate()) /
-                            (nextFrame - frame);
-                    }
-                    newPoint.value = bpm;
-                    prevBpm = bpm;
-                }
-                break;
-            }
-
-            case GenerateFromExistingNeighbour:
-            {
-                SparseTimeValueModel::PointList points = 
-                    m_model->getPoints(frame);
-                if (points.empty()) points = m_model->getPreviousPoints(frame);
-                if (points.empty()) points = m_model->getNextPoints(frame);
-                if (points.empty()) {
-                    newPoint.value = 0.f;
-                } else {
-                    newPoint.value = points.begin()->value;
-                }
-            }
-
-            case GenerateFromLabels:
-                if (i->haveLabel()) {
-                    // more forgiving than QString::toFloat()
-                    newPoint.value = atof(i->getLabel().toLocal8Bit());
-                } else {
-                    newPoint.value = 0.f;
-                }
+//            std::cerr << "Setting value on point at " << newPoint.frame << " from labeller";
+//            if (i == points.begin()) {
+//                std::cerr << ", no prev point" << std::endl;
+//            } else {
+//                std::cerr << ", prev point is at " << prevPoint.frame << std::endl;
+//            }
+            labeller.setValue<SparseTimeValueModel::Point>
+                (newPoint, (i == points.begin()) ? 0 : &prevPoint);
+//            std::cerr << "New point value = " << newPoint.value << std::endl;
+            if (labeller.actingOnPrevPoint() && i != points.begin()) {
+                usePrev = true;
             }
         }
-        
+
+        if (usePrev) {
+            command->deletePoint(formerPrevPoint);
+            command->addPoint(prevPoint);
+        }
+
+        prevPoint = newPoint;
         command->addPoint(newPoint);
-        
-        ++counter;
     }
 
     command->finish();
--- a/layer/TimeValueLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/TimeValueLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -47,6 +47,10 @@
     virtual void drawDrag(View *v, QMouseEvent *);
     virtual void drawEnd(View *v, QMouseEvent *);
 
+    virtual void eraseStart(View *v, QMouseEvent *);
+    virtual void eraseDrag(View *v, QMouseEvent *);
+    virtual void eraseEnd(View *v, QMouseEvent *);
+
     virtual void editStart(View *v, QMouseEvent *);
     virtual void editDrag(View *v, QMouseEvent *);
     virtual void editEnd(View *v, QMouseEvent *);
@@ -57,8 +61,8 @@
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(Selection s, Clipboard &to);
-    virtual bool paste(const Clipboard &from, int frameOffset,
+    virtual void copy(View *v, Selection s, Clipboard &to);
+    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
--- a/layer/WaveformLayer.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/WaveformLayer.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -108,6 +108,13 @@
     return SingleColourLayer::getPropertyLabel(name);
 }
 
+QString
+WaveformLayer::getPropertyIconName(const PropertyName &name) const
+{
+    if (name == "Normalize Visible Area") return "normalise";
+    return "";
+}
+
 Layer::PropertyType
 WaveformLayer::getPropertyType(const PropertyName &name) const
 {
@@ -389,6 +396,73 @@
 static float meterdbs[] = { -40, -30, -20, -15, -10,
                             -5, -3, -2, -1, -0.5, 0 };
 
+bool
+WaveformLayer::getSourceFramesForX(View *v, int x, size_t modelZoomLevel,
+                                   size_t &f0, size_t &f1) const
+{
+    long viewFrame = v->getFrameForX(x);
+    if (viewFrame < 0) {
+        f0 = 0;
+        f1 = 0;
+        return false;
+    }
+
+    f0 = viewFrame;
+    
+    f0 = f0 / modelZoomLevel;
+    f0 = f0 * modelZoomLevel;
+
+    viewFrame = v->getFrameForX(x + 1);
+    
+    f1 = viewFrame;
+    f1 = f1 / modelZoomLevel;
+    f1 = f1 * modelZoomLevel;
+    
+    return (f0 < m_model->getEndFrame());
+}
+
+float
+WaveformLayer::getNormalizeGain(View *v, int channel) const
+{
+    long startFrame = v->getStartFrame();
+    long endFrame = v->getEndFrame();
+
+    // Although a long for purposes of comparison against the view
+    // start and end frames, these are known to be non-negative
+    long modelStart = long(m_model->getStartFrame());
+    long modelEnd = long(m_model->getEndFrame());
+    
+    size_t rangeStart, rangeEnd;
+            
+    if (startFrame < modelStart) rangeStart = modelStart;
+    else rangeStart = startFrame;
+
+    if (endFrame < 0) rangeEnd = 0;
+    else if (endFrame > modelEnd) rangeEnd = modelEnd;
+    else rangeEnd = endFrame;
+
+    if (rangeEnd < rangeStart) rangeEnd = rangeStart;
+
+    RangeSummarisableTimeValueModel::Range range =
+        m_model->getSummary(channel, rangeStart, rangeEnd - rangeStart);
+
+    size_t minChannel = 0, maxChannel = 0;
+    bool mergingChannels = false, mixingChannels = false;
+
+    getChannelArrangement(minChannel, maxChannel,
+                          mergingChannels, mixingChannels);
+
+    if (mergingChannels || mixingChannels) {
+        RangeSummarisableTimeValueModel::Range otherRange =
+            m_model->getSummary(1, rangeStart, rangeEnd - rangeStart);
+        range.max = std::max(range.max, otherRange.max);
+        range.min = std::min(range.min, otherRange.min);
+        range.absmean = std::min(range.absmean, otherRange.absmean);
+    }
+
+    return 1.0 / std::max(fabsf(range.max), fabsf(range.min));
+}
+
 void
 WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const
 {
@@ -396,8 +470,6 @@
 	return;
     }
   
-    long startFrame = v->getStartFrame();
-    long endFrame = v->getEndFrame();
     int zoomLevel = v->getZoomLevel();
 
 #ifdef DEBUG_WAVEFORM_PAINT
@@ -471,11 +543,26 @@
     if (x0 > 0) --x0;
     if (x1 < v->width()) ++x1;
 
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1 + 1);
+    // Our zoom level may differ from that at which the underlying
+    // model has its blocks.
 
+    // Each pixel within our visible range must always draw from
+    // exactly the same set of underlying audio frames, no matter what
+    // the range being drawn is.  And that set of underlying frames
+    // must remain the same when we scroll one or more pixels left or
+    // right.
+            
+    size_t modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
+
+    size_t frame0;
+    size_t frame1;
+    size_t spare;
+
+    getSourceFramesForX(v, x0, modelZoomLevel, frame0, spare);
+    getSourceFramesForX(v, x1, modelZoomLevel, spare, frame1);
+    
 #ifdef DEBUG_WAVEFORM_PAINT
-    std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << ")" <<  std::endl;
+    std::cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << " and model zoom " << modelZoomLevel << ")" <<  std::endl;
 #endif
 
     RangeSummarisableTimeValueModel::RangeBlock *ranges = 
@@ -500,45 +587,15 @@
         m_effectiveGains.push_back(m_gain);
     }
 
-    // Although a long for purposes of comparison against the view
-    // start and end frames, these are known to be non-negative
-    long modelStart = long(m_model->getStartFrame());
-    long modelEnd = long(m_model->getEndFrame());
-
-#ifdef DEBUG_WAVEFORM_PAINT
-    std::cerr << "Model start = " << modelStart << ", end = " << modelEnd << std::endl;
-#endif
-
     for (size_t ch = minChannel; ch <= maxChannel; ++ch) {
 
 	int prevRangeBottom = -1, prevRangeTop = -1;
 	QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour;
-        size_t rangeStart, rangeEnd;
 
         m_effectiveGains[ch] = m_gain;
 
         if (m_autoNormalize) {
-
-            if (startFrame < modelStart) rangeStart = modelStart;
-            else rangeStart = startFrame;
-
-            if (endFrame < 0) rangeEnd = 0;
-            else if (endFrame > modelEnd) rangeEnd = modelEnd;
-            else rangeEnd = endFrame;
-
-            if (rangeEnd < rangeStart) rangeEnd = rangeStart;
-
-            RangeSummarisableTimeValueModel::Range range =
-                m_model->getSummary(ch, rangeStart, rangeEnd - rangeStart);
-            if (mergingChannels || mixingChannels) {
-                RangeSummarisableTimeValueModel::Range otherRange =
-                    m_model->getSummary(1, rangeStart, rangeEnd - rangeStart);
-                range.max = std::max(range.max, otherRange.max);
-                range.min = std::min(range.min, otherRange.min);
-                range.absmean = std::min(range.absmean, otherRange.absmean);
-            }
-            m_effectiveGains[ch] = 1.0 / std::max(fabsf(range.max),
-                                                  fabsf(range.min));
+            m_effectiveGains[ch] = getNormalizeGain(v, ch);
         }
 
         float gain = m_effectiveGains[ch];
@@ -606,25 +663,14 @@
                 }
             }
         }
+  
+        m_model->getSummaries(ch, frame0, frame1 - frame0,
+                              *ranges, modelZoomLevel);
 
-	if (frame1 < modelStart) continue;
+#ifdef DEBUG_WAVEFORM_PAINT
+        std::cerr << ranges->size() << " ranges from " << frame0 << " to " << frame1 << std::endl;
+#endif
 
-	size_t modelZoomLevel = zoomLevel;
-
-        if (frame0 < modelStart) rangeStart = modelStart;
-        else rangeStart = frame0;
-
-        if (frame1 < 0) rangeEnd = 0;
-        else if (frame1 > modelEnd) rangeEnd = modelEnd;
-        else rangeEnd = frame1;
-        
-        if (rangeEnd < rangeStart) rangeEnd = rangeStart;
-
-	m_model->getSummaries
-	    (ch, rangeStart, rangeEnd - rangeStart, *ranges, modelZoomLevel);
-
-//        std::cerr << ranges->size() << " ranges" << std::endl;
-        
 	if (mergingChannels || mixingChannels) {
             if (m_model->getChannelCount() > 1) {
                 if (!otherChannelRanges) {
@@ -632,7 +678,7 @@
                         new RangeSummarisableTimeValueModel::RangeBlock;
                 }
                 m_model->getSummaries
-                    (1, rangeStart, rangeEnd - rangeStart, *otherChannelRanges,
+                    (1, frame0, frame1 - frame0, *otherChannelRanges,
                      modelZoomLevel);
             } else {
                 if (otherChannelRanges != ranges) delete otherChannelRanges;
@@ -643,42 +689,35 @@
 	for (int x = x0; x <= x1; ++x) {
 
 	    range = RangeSummarisableTimeValueModel::Range();
-	    size_t index = x - x0;
-	    size_t maxIndex = index;
 
-            if (frame0 < modelStart) {
-                if (index < size_t((modelStart - frame0) / zoomLevel)) {
-                    continue;
-                } else {
-                    index -= ((modelStart - frame0) / zoomLevel);
-                    maxIndex = index;
-                }
+            size_t f0, f1;
+            if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) continue;
+            f1 = f1 - 1;
+
+            if (f0 < frame0) {
+                std::cerr << "ERROR: WaveformLayer::paint: pixel " << x << " has f0 = " << f0 << " which is less than range frame0 " << frame0 << " for x0 = " << x0 << std::endl;
+                continue;
             }
-            
-	    if (int(modelZoomLevel) != zoomLevel) {
 
-		index = size_t((double(index) * zoomLevel) / modelZoomLevel);
+            size_t i0 = (f0 - frame0) / modelZoomLevel;
+            size_t i1 = (f1 - frame0) / modelZoomLevel;
 
-		if (int(modelZoomLevel) < zoomLevel) {
-		    // Peaks may be missed!  The model should avoid
-		    // this by rounding zoom levels up rather than
-		    // down, but we'd better cope in case it doesn't
-		    maxIndex = index;
-		} else {
-		    maxIndex = size_t((double(index + 1) * zoomLevel)
-				      / modelZoomLevel) - 1;
-		}
-	    }
+#ifdef DEBUG_WAVEFORM_PAINT
+            std::cerr << "WaveformLayer::paint: pixel " << x << ": i0 " << i0 << " (f " << f0 << "), i1 " << i1 << " (f " << f1 << ")" << std::endl;
+#endif
 
-	    if (ranges && index < ranges->size()) {
+            if (i1 > i0 + 1) {
+                std::cerr << "WaveformLayer::paint: ERROR: i1 " << i1 << " > i0 " << i0 << " plus one (zoom = " << zoomLevel << ", model zoom = " << modelZoomLevel << ")" << std::endl;
+            }
 
-		range = (*ranges)[index];
+	    if (ranges && i0 < ranges->size()) {
 
-		if (maxIndex > index && maxIndex < ranges->size()) {
-		    range.max = std::max(range.max, (*ranges)[maxIndex].max);
-		    range.min = std::min(range.min, (*ranges)[maxIndex].min);
-		    range.absmean = (range.absmean +
-				     (*ranges)[maxIndex].absmean) / 2;
+		range = (*ranges)[i0];
+
+		if (i1 > i0 && i1 < ranges->size()) {
+		    range.max = std::max(range.max, (*ranges)[i1].max);
+		    range.min = std::min(range.min, (*ranges)[i1].min);
+		    range.absmean = (range.absmean + (*ranges)[i1].absmean) / 2;
 		}
 
 	    } else {
@@ -689,28 +728,28 @@
 
 	    if (mergingChannels) {
 
-		if (otherChannelRanges && index < otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < otherChannelRanges->size()) {
 
 		    range.max = fabsf(range.max);
-		    range.min = -fabsf((*otherChannelRanges)[index].max);
+		    range.min = -fabsf((*otherChannelRanges)[i0].max);
 		    range.absmean = (range.absmean +
-				     (*otherChannelRanges)[index].absmean) / 2;
+				     (*otherChannelRanges)[i0].absmean) / 2;
 
-		    if (maxIndex > index && maxIndex < otherChannelRanges->size()) {
+		    if (i1 > i0 && i1 < otherChannelRanges->size()) {
 			// let's not concern ourselves about the mean
 			range.min = std::min
 			    (range.min,
-			     -fabsf((*otherChannelRanges)[maxIndex].max));
+			     -fabsf((*otherChannelRanges)[i1].max));
 		    }
 		}
 
 	    } else if (mixingChannels) {
 
-		if (otherChannelRanges && index < otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < otherChannelRanges->size()) {
 
-                    range.max = (range.max + (*otherChannelRanges)[index].max) / 2;
-                    range.min = (range.min + (*otherChannelRanges)[index].min) / 2;
-                    range.absmean = (range.absmean + (*otherChannelRanges)[index].absmean) / 2;
+                    range.max = (range.max + (*otherChannelRanges)[i0].max) / 2;
+                    range.min = (range.min + (*otherChannelRanges)[i0].min) / 2;
+                    range.absmean = (range.absmean + (*otherChannelRanges)[i0].absmean) / 2;
                 }
             }
 
@@ -883,11 +922,12 @@
 
     if (!m_model || !m_model->isOK()) return "";
 
-    long f0 = v->getFrameForX(x);
-    long f1 = v->getFrameForX(x + 1);
+    int zoomLevel = v->getZoomLevel();
 
-    if (f0 < 0) f0 = 0;
-    if (f1 <= f0) return "";
+    size_t modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel);
+
+    size_t f0, f1;
+    if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) return "";
     
     QString text;
 
--- a/layer/WaveformLayer.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/layer/WaveformLayer.h	Wed Feb 27 11:59:42 2008 +0000
@@ -53,6 +53,7 @@
 
     virtual PropertyList getProperties() const;
     virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual QString getPropertyIconName(const PropertyName &) const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
     virtual QString getPropertyGroupName(const PropertyName &) const;
     virtual int getPropertyRangeAndValue(const PropertyName &,
@@ -201,6 +202,11 @@
 
     float getValueForY(const View *v, int y, size_t &channel) const;
 
+    bool getSourceFramesForX(View *v, int x, size_t modelZoomLevel,
+                             size_t &f0, size_t &f1) const;
+
+    float getNormalizeGain(View *v, int channel) const;
+
     virtual void flagBaseColourChanged() { m_cacheValid = false; }
 
     float        m_gain;
--- a/view/Overview.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/Overview.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -71,6 +71,7 @@
 void
 Overview::modelReplaced()
 {
+    m_playPointerFrame = getAlignedPlaybackFrame();
     View::modelReplaced();
 }
 
@@ -116,6 +117,8 @@
 {
     bool changed = false;
 
+    f = getAlignedPlaybackFrame();
+
     if (getXForFrame(m_playPointerFrame) != getXForFrame(f)) changed = true;
     m_playPointerFrame = f;
 
@@ -179,6 +182,15 @@
 	long f0 = w->getFrameForX(0);
 	long f1 = w->getFrameForX(w->width());
 
+        if (f0 >= 0) {
+            size_t rf0 = w->alignToReference(f0);
+            f0 = alignFromReference(rf0);
+        }
+        if (f1 >= 0) {
+            size_t rf1 = w->alignToReference(f1);
+            f1 = alignFromReference(rf1);
+        }
+
 	int x0 = getXForFrame(f0);
 	int x1 = getXForFrame(f1);
 
@@ -200,12 +212,16 @@
 Overview::mousePressEvent(QMouseEvent *e)
 {
     m_clickPos = e->pos();
+    long clickFrame = getFrameForX(m_clickPos.x());
+    if (clickFrame > 0) m_dragCentreFrame = clickFrame;
+    else m_dragCentreFrame = 0;
+    m_clickedInRange = true;
+
     for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
-	if (*i) {
-	    m_clickedInRange = true;
-	    m_dragCentreFrame = ((View *)*i)->getCentreFrame();
-	    break;
-	}
+	if (*i && (*i)->getAligningModel() == getAligningModel()) {
+            m_dragCentreFrame = (*i)->getCentreFrame();
+            break;
+        }
     }
 }
 
@@ -242,7 +258,8 @@
     
     if (std::max(m_centreFrame, newCentreFrame) -
 	std::min(m_centreFrame, newCentreFrame) > size_t(m_zoomLevel)) {
-	emit centreFrameChanged(newCentreFrame, true, PlaybackScrollContinuous);
+        size_t rf = alignToReference(newCentreFrame);
+	emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
     }
 }
 
@@ -250,7 +267,9 @@
 Overview::mouseDoubleClickEvent(QMouseEvent *e)
 {
     long frame = getFrameForX(e->x());
-    emit centreFrameChanged(frame, true, PlaybackScrollContinuous);
+    size_t rf = 0;
+    if (frame > 0) rf = alignToReference(frame);
+    emit centreFrameChanged(rf, true, PlaybackScrollContinuous);
 }
 
 void
--- a/view/Pane.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/Pane.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -22,6 +22,7 @@
 #include "ViewManager.h"
 #include "base/CommandHistory.h"
 #include "base/TextAbbrev.h"
+#include "base/Preferences.h"
 #include "layer/WaveformLayer.h"
 
 //!!! ugh
@@ -49,6 +50,8 @@
 
 #include "widgets/KeyReference.h" //!!! should probably split KeyReference into a data class in base and another that shows the widget
 
+//#define DEBUG_PANE
+
 using std::cerr;
 using std::endl;
 
@@ -63,6 +66,8 @@
     m_ctrlPressed(false),
     m_navigating(false),
     m_resizing(false),
+    m_editing(false),
+    m_releasing(false),
     m_centreLineVisible(true),
     m_scaleWidth(0),
     m_headsUpDisplay(0),
@@ -388,6 +393,7 @@
     View::paintEvent(e);
 
     paint.begin(this);
+    setPaintFont(paint);
 
     if (e) paint.setClipRect(r);
 
@@ -1209,6 +1215,8 @@
         return;
     }
 
+//    std::cerr << "mousePressEvent" << std::endl;
+
     m_clickPos = e->pos();
     m_mousePos = m_clickPos;
     m_clickedInRange = true;
@@ -1222,6 +1230,9 @@
     if (m_manager) mode = m_manager->getToolMode();
 
     m_navigating = false;
+    m_resizing = false;
+    m_editing = false;
+    m_releasing = false;
 
     if (mode == ViewManager::NavigateMode ||
         (e->buttons() & Qt::MidButton) ||
@@ -1276,9 +1287,10 @@
 	    if (snapFrame < 0) snapFrame = 0;
 	    m_selectionStartFrame = snapFrame;
 	    if (m_manager) {
-		m_manager->setInProgressSelection(Selection(snapFrame,
-							    snapFrame + resolution),
-						  !m_ctrlPressed);
+		m_manager->setInProgressSelection
+                    (Selection(alignToReference(snapFrame),
+                               alignToReference(snapFrame + resolution)),
+                     !m_ctrlPressed);
 	    }
 
 	    m_resizing = false;
@@ -1293,14 +1305,17 @@
 	    layer->drawStart(this, e);
 	}
 
+    } else if (mode == ViewManager::EraseMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->eraseStart(this, e);
+	}
+
     } else if (mode == ViewManager::EditMode) {
 
-	if (!editSelectionStart(e)) {
-	    Layer *layer = getSelectedLayer();
-	    if (layer && layer->isLayerEditable()) {
-		layer->editStart(this, e);
-	    }
-	}
+        // Do nothing here -- we'll do it in mouseMoveEvent when the
+        // drag threshold has been passed
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -1319,9 +1334,13 @@
         return;
     }
 
+//    std::cerr << "mouseReleaseEvent" << std::endl;
+
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
     if (m_manager) mode = m_manager->getToolMode();
 
+    m_releasing = true;
+
     if (m_clickedInRange) {
 	mouseMoveEvent(e);
     }
@@ -1348,7 +1367,10 @@
 
     } else if (mode == ViewManager::SelectMode) {
 
-        if (!hasTopLayerTimeXAxis()) return;
+        if (!hasTopLayerTimeXAxis()) {
+            m_releasing = false;
+            return;
+        }
 
 	if (m_manager && m_manager->haveInProgressSelection()) {
 
@@ -1378,15 +1400,25 @@
 	    update();
 	}
 
+    } else if (mode == ViewManager::EraseMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->eraseEnd(this, e);
+	    update();
+	}
+
     } else if (mode == ViewManager::EditMode) {
 
-	if (!editSelectionEnd(e)) {
-	    Layer *layer = getSelectedLayer();
-	    if (layer && layer->isLayerEditable()) {
-		layer->editEnd(this, e);
-		update();
-	    }
-	}
+        if (m_editing) {
+            if (!editSelectionEnd(e)) {
+                Layer *layer = getSelectedLayer();
+                if (layer && layer->isLayerEditable()) {
+                    layer->editEnd(this, e);
+                    update();
+                }
+            }
+        }
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -1397,6 +1429,7 @@
     }
 
     m_clickedInRange = false;
+    m_releasing = false;
 
     emit paneInteractedWith();
 }
@@ -1408,8 +1441,24 @@
         return;
     }
 
+//    std::cerr << "mouseMoveEvent" << std::endl;
+
     updateContextHelp(&e->pos());
 
+    if (m_navigating && m_clickedInRange && !m_releasing) {
+
+        // if no buttons pressed, and not called from
+        // mouseReleaseEvent, we want to reset clicked-ness (to avoid
+        // annoying continual drags when we moved the mouse outside
+        // the window after pressing button first time).
+
+        if (!(e->buttons() & Qt::LeftButton) &&
+            !(e->buttons() & Qt::MidButton)) {
+            m_clickedInRange = false;
+            return;
+        }
+    }
+
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
     if (m_manager) mode = m_manager->getToolMode();
 
@@ -1484,14 +1533,53 @@
 	    layer->drawDrag(this, e);
 	}
 
+    } else if (mode == ViewManager::EraseMode) {
+
+	Layer *layer = getSelectedLayer();
+	if (layer && layer->isLayerEditable()) {
+	    layer->eraseDrag(this, e);
+	}
+
     } else if (mode == ViewManager::EditMode) {
 
-	if (!editSelectionDrag(e)) {
-	    Layer *layer = getSelectedLayer();
-	    if (layer && layer->isLayerEditable()) {
-		layer->editDrag(this, e);
-	    }
-	}
+        if (m_editing) {
+            if (!editSelectionDrag(e)) {
+                Layer *layer = getSelectedLayer();
+                if (layer && layer->isLayerEditable()) {
+                    layer->editDrag(this, e);
+                }
+            }
+        }
+
+        if (!m_editing) {
+
+            DragMode newDragMode = updateDragMode
+                (m_dragMode,
+                 m_clickPos,
+                 e->pos(),
+                 true,  // can move horiz
+                 true,  // can move vert
+                 true,  // resist horiz
+                 true); // resist vert
+
+            if (newDragMode != UnresolvedDrag) {
+
+                m_editing = true;
+
+                QMouseEvent clickEvent(QEvent::MouseButtonPress,
+                                       m_clickPos,
+                                       Qt::NoButton,
+                                       e->buttons(),
+                                       e->modifiers());
+
+                if (!editSelectionStart(&clickEvent)) {
+                    Layer *layer = getSelectedLayer();
+                    if (layer && layer->isLayerEditable()) {
+                        layer->editStart(this, &clickEvent);
+                    }
+                }
+            }
+        }
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -1592,51 +1680,17 @@
     // If the top layer is incapable of being dragged
     // vertically, the logic is short circuited.
 
-    int xdiff = e->x() - m_clickPos.x();
-    int ydiff = e->y() - m_clickPos.y();
-    int smallThreshold = 10, bigThreshold = 50;
+    m_dragMode = updateDragMode
+        (m_dragMode,
+         m_clickPos,
+         e->pos(),
+         true, // can move horiz
+         canTopLayerMoveVertical(), // can move vert
+         canTopLayerMoveVertical() || (m_manager && m_manager->isPlaying()), // resist horiz
+         !(m_manager && m_manager->isPlaying())); // resist vert
 
-    bool canMoveVertical = canTopLayerMoveVertical();
-    bool canMoveHorizontal = true;
-
-    if (!canMoveHorizontal) {
-        m_dragMode = HorizontalDrag;
-    }
-
-    if (m_dragMode == UnresolvedDrag) {
-
-        if (abs(ydiff) > smallThreshold &&
-            abs(ydiff) > abs(xdiff) * 2) {
-            m_dragMode = VerticalDrag;
-        } else if (abs(xdiff) > smallThreshold &&
-                   abs(xdiff) > abs(ydiff) * 2) {
-            m_dragMode = HorizontalDrag;
-        } else if (abs(xdiff) > smallThreshold &&
-                   abs(ydiff) > smallThreshold) {
-            m_dragMode = FreeDrag;
-        } else {
-            // When playing, we don't want to disturb the play
-            // position too easily; when not playing, we don't
-            // want to move up/down too easily
-            if (m_manager && m_manager->isPlaying()) {
-                canMoveHorizontal = false;
-            } else {
-                canMoveVertical = false;
-            }
-        }
-    }
-
-    if (m_dragMode == VerticalDrag) {
-        if (abs(xdiff) > bigThreshold) m_dragMode = FreeDrag;
-        else canMoveHorizontal = false;
-    }
-
-    if (m_dragMode == HorizontalDrag && canMoveVertical) {
-        if (abs(ydiff) > bigThreshold) m_dragMode = FreeDrag;
-        else canMoveVertical = false;
-    }
-
-    if (canMoveHorizontal) {
+    if (m_dragMode == HorizontalDrag ||
+        m_dragMode == FreeDrag) {
 
         long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x());
 
@@ -1649,7 +1703,12 @@
         } else {
             newCentreFrame = 0;
         }
-	    
+
+#ifdef DEBUG_PANE	    
+        std::cerr << "Pane::dragTopLayer: newCentreFrame = " << newCentreFrame <<
+            ", models end frame = " << getModelsEndFrame() << std::endl;
+#endif
+
         if (newCentreFrame >= getModelsEndFrame()) {
             newCentreFrame = getModelsEndFrame();
             if (newCentreFrame > 0) --newCentreFrame;
@@ -1660,7 +1719,8 @@
         }
     }
 
-    if (canMoveVertical) {
+    if (m_dragMode == VerticalDrag ||
+        m_dragMode == FreeDrag) {
 
         float vmin = 0.f, vmax = 0.f;
         float dmin = 0.f, dmax = 0.f;
@@ -1669,10 +1729,15 @@
 
 //            std::cerr << "ydiff = " << ydiff << std::endl;
 
+            int ydiff = e->y() - m_clickPos.y();
             float perpix = (dmax - dmin) / height();
             float valdiff = ydiff * perpix;
 //            std::cerr << "valdiff = " << valdiff << std::endl;
 
+            if (m_dragMode == UnresolvedDrag && ydiff != 0) {
+                m_dragMode = VerticalDrag;
+            }
+
             float newmin = m_dragStartMinValue + valdiff;
             float newmax = m_dragStartMinValue + (dmax - dmin) + valdiff;
             if (newmin < vmin) {
@@ -1692,6 +1757,65 @@
     }
 }
 
+Pane::DragMode
+Pane::updateDragMode(DragMode dragMode,
+                     QPoint origin,
+                     QPoint point,
+                     bool canMoveHorizontal,
+                     bool canMoveVertical,
+                     bool resistHorizontal,
+                     bool resistVertical)
+{
+    int xdiff = point.x() - origin.x();
+    int ydiff = point.y() - origin.y();
+
+    int smallThreshold = 10, bigThreshold = 80;
+
+//    std::cerr << "Pane::updateDragMode: xdiff = " << xdiff << ", ydiff = "
+//              << ydiff << ", canMoveVertical = " << canMoveVertical << ", drag mode = " << m_dragMode << std::endl;
+
+    if (dragMode == UnresolvedDrag) {
+
+        if (abs(ydiff) > smallThreshold &&
+            abs(ydiff) > abs(xdiff) * 2 &&
+            canMoveVertical) {
+//            std::cerr << "Pane::updateDragMode: passed vertical threshold" << std::endl;
+            dragMode = VerticalDrag;
+        } else if (abs(xdiff) > smallThreshold &&
+                   abs(xdiff) > abs(ydiff) * 2 &&
+                   canMoveHorizontal) {
+//            std::cerr << "Pane::updateDragMode: passed horizontal threshold" << std::endl;
+            dragMode = HorizontalDrag;
+        } else if (abs(xdiff) > smallThreshold &&
+                   abs(ydiff) > smallThreshold &&
+                   canMoveVertical &&
+                   canMoveHorizontal) {
+//            std::cerr << "Pane::updateDragMode: passed both thresholds" << std::endl;
+            dragMode = FreeDrag;
+        }
+    }
+
+    if (dragMode == VerticalDrag && canMoveHorizontal) {
+        if (abs(xdiff) > bigThreshold) dragMode = FreeDrag;
+    }
+
+    if (dragMode == HorizontalDrag && canMoveVertical) {
+        if (abs(ydiff) > bigThreshold) dragMode = FreeDrag;
+    }
+
+    if (dragMode == UnresolvedDrag) {
+        if (!resistHorizontal && xdiff != 0) {
+            dragMode = HorizontalDrag;
+        }
+        if (!resistVertical && ydiff != 0) {
+            if (dragMode == HorizontalDrag) dragMode = FreeDrag;
+            else dragMode = VerticalDrag;
+        }
+    }
+    
+    return dragMode;
+}
+
 void
 Pane::dragExtendSelection(QMouseEvent *e)
 {
@@ -1727,7 +1851,8 @@
     }
 
     if (m_manager) {
-        m_manager->setInProgressSelection(Selection(min, max),
+        m_manager->setInProgressSelection(Selection(alignToReference(min),
+                                                    alignToReference(max)),
                                           !m_resizing && !m_ctrlPressed);
     }
 
@@ -2187,6 +2312,10 @@
     case ViewManager::DrawMode:
 	setCursor(Qt::CrossCursor);
 	break;
+	
+    case ViewManager::EraseMode:
+	setCursor(Qt::CrossCursor);
+	break;
 
     case ViewManager::MeasureMode:
         if (m_measureCursor1) setCursor(*m_measureCursor1);
@@ -2326,6 +2455,13 @@
 	if (editable) {
             help = tr("Click to add a new item in the active layer");
         }
+
+    } else if (mode == ViewManager::EraseMode) {
+        
+        //!!! could call through to a layer function to find out exact meaning
+	if (editable) {
+            help = tr("Click to erase an item from the active layer");
+        }
         
     } else if (mode == ViewManager::EditMode) {
         
--- a/view/Pane.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/Pane.h	Wed Feb 27 11:59:42 2008 +0000
@@ -142,6 +142,8 @@
 
     bool m_navigating;
     bool m_resizing;
+    bool m_editing;
+    bool m_releasing;
     size_t m_dragCentreFrame;
     float m_dragStartMinValue;
     bool m_centreLineVisible;
@@ -158,6 +160,14 @@
     };
     DragMode m_dragMode;
 
+    DragMode updateDragMode(DragMode currentMode,
+                            QPoint origin,
+                            QPoint currentPoint,
+                            bool canMoveHorizontal,
+                            bool canMoveVertical,
+                            bool resistHorizontal,
+                            bool resistVertical);
+
     QWidget *m_headsUpDisplay;
     Panner *m_vpan;
     Thumbwheel *m_hthumb;
--- a/view/PaneStack.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/PaneStack.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -495,14 +495,7 @@
     std::cerr << "PaneStack::sizePropertyStacks: max min width " << maxMinWidth << std::endl;
 #endif
 
-//#ifdef Q_WS_MAC
-    // This is necessary to compensate for cb->setMinimumSize(10, 10)
-    // in PropertyBox in the Mac version (to avoid a mysterious crash)
-    // ... no longer necessary with qt4.2
-//    int setWidth = maxMinWidth * 3 / 2;
-//#else
     int setWidth = maxMinWidth;
-//#endif
 
     m_propertyStackStack->setMaximumWidth(setWidth + 10);
 
@@ -511,6 +504,7 @@
 	m_panes[i].propertyStack->setMinimumWidth(setWidth);
     }
 
+    emit propertyStacksResized(setWidth);
     emit propertyStacksResized();
 }
     
--- a/view/PaneStack.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/PaneStack.h	Wed Feb 27 11:59:42 2008 +0000
@@ -74,6 +74,7 @@
     void currentPaneChanged(Pane *pane);
     void currentLayerChanged(Pane *pane, Layer *layer);
     void rightButtonMenuRequested(Pane *pane, QPoint position);
+    void propertyStacksResized(int width);
     void propertyStacksResized();
     void contextHelpChanged(const QString &);
 
--- a/view/View.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/View.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -19,6 +19,7 @@
 #include "base/ZoomConstraint.h"
 #include "base/Profiler.h"
 #include "base/Pitch.h"
+#include "base/Preferences.h"
 
 #include "layer/TimeRulerLayer.h"
 #include "layer/SingleColourLayer.h"
@@ -31,6 +32,7 @@
 #include <QApplication>
 #include <QProgressDialog>
 #include <QTextStream>
+#include <QFont>
 
 #include <iostream>
 #include <cassert>
@@ -326,8 +328,15 @@
 	    changeVisible = true;
 	}
 
-	if (e) emit centreFrameChanged(alignFromReference(f),
-                                       m_followPan, m_followPlay);
+	if (e) {
+            size_t rf = alignToReference(f);
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+            std::cerr << "View[" << this << "]::setCentreFrame(" << f
+                      << "): emitting centreFrameChanged("
+                      << rf << ")" << std::endl;
+#endif
+            emit centreFrameChanged(rf, m_followPan, m_followPlay);
+        }
     }
 
     return changeVisible;
@@ -344,6 +353,11 @@
 {
     long z = (long)m_zoomLevel;
     long frame = m_centreFrame - (width()/2) * z;
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    std::cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << std::endl;
+#endif
+
     frame = (frame / z) * z; // this is start frame
     return frame + x * z;
 }
@@ -498,9 +512,6 @@
 View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
     QProgressBar(parent)
 {
-    QFont f(font());
-    f.setPointSize(f.pointSize() * 8 / 10);
-    setFont(f);
 }
 
 void
@@ -518,6 +529,12 @@
     m_progressBars[layer]->setMinimum(0);
     m_progressBars[layer]->setMaximum(100);
     m_progressBars[layer]->setMinimumWidth(80);
+
+    QFont f(m_progressBars[layer]->font());
+    int fs = Preferences::getInstance()->getViewFontSize();
+    f.setPointSize(std::min(fs, int(ceil(fs * 0.85))));
+
+    m_progressBars[layer]->setFont(f);
     m_progressBars[layer]->hide();
     
     connect(layer, SIGNAL(layerParametersChanged()),
@@ -650,7 +667,10 @@
     connect(this, SIGNAL(zoomLevelChanged(unsigned long, bool)),
 	    m_manager, SLOT(viewZoomLevelChanged(unsigned long, bool)));
 
-    if (m_followPlay != PlaybackIgnore) {
+    if (m_followPlay == PlaybackScrollPage) {
+//        std::cerr << "View::setViewManager: setting centre frame to global centre frame: " << m_manager->getGlobalCentreFrame() << std::endl;
+        setCentreFrame(m_manager->getGlobalCentreFrame(), false);
+    } else if (m_followPlay == PlaybackScrollContinuous) {
 //        std::cerr << "View::setViewManager: setting centre frame to playback frame: " << m_manager->getPlaybackFrame() << std::endl;
         setCentreFrame(m_manager->getPlaybackFrame(), false);
     } else if (m_followPan) {
@@ -745,6 +765,8 @@
 	m_cache = 0;
     }
 
+    emit layerModelChanged();
+
     checkProgress(obj);
 
     update();
@@ -869,10 +891,15 @@
 }
 
 void
-View::globalCentreFrameChanged(unsigned long f)
+View::globalCentreFrameChanged(unsigned long rf)
 {
     if (m_followPan) {
-        setCentreFrame(alignToReference(f), false);
+        size_t f = alignFromReference(rf);
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+        std::cerr << "View[" << this << "]::globalCentreFrameChanged(" << rf
+                  << "): setting centre frame to " << f << std::endl;
+#endif
+        setCentreFrame(f, false);
     }
 }
 
@@ -1098,6 +1125,7 @@
     }
 
     Model *anyModel = 0;
+    Model *alignedModel = 0;
     Model *goodModel = 0;
 
     for (LayerList::const_iterator i = m_layers.begin();
@@ -1111,8 +1139,10 @@
         Model *model = (*i)->getModel();
         if (!model) continue;
 
+        anyModel = model;
+
         if (model->getAlignmentReference()) {
-            anyModel = model;
+            alignedModel = model;
             if (layer->isLayerOpaque() ||
                 dynamic_cast<RangeSummarisableTimeValueModel *>(model)) {
                 goodModel = model;
@@ -1121,6 +1151,7 @@
     }
 
     if (goodModel) return goodModel;
+    else if (alignedModel) return alignedModel;
     else return anyModel;
 }
 
@@ -1150,17 +1181,17 @@
 
     Model *aligningModel = getAligningModel();
     if (!aligningModel) return pf;
-
+/*
     Model *pm = m_manager->getPlaybackModel();
 
 //    std::cerr << "View[" << this << "]::getAlignedPlaybackFrame: pf = " << pf;
 
     if (pm) {
-        pf = pm->alignFromReference(pf);
+        pf = pm->alignToReference(pf);
 //        std::cerr << " -> " << pf;
     }
-
-    int af = aligningModel->alignToReference(pf);
+*/
+    int af = aligningModel->alignFromReference(pf);
 
 //    std::cerr << ", aligned = " << af << std::endl;
     return af;
@@ -1389,6 +1420,14 @@
 }
 
 void
+View::setPaintFont(QPainter &paint)
+{
+    QFont font(paint.font());
+    font.setPointSize(Preferences::getInstance()->getViewFontSize());
+    paint.setFont(font);
+}
+
+void
 View::paintEvent(QPaintEvent *e)
 {
 //    Profiler prof("View::paintEvent", false);
@@ -1573,7 +1612,7 @@
 
 	if (repaintCache) paint.begin(m_cache);
 	else paint.begin(this);
-
+        setPaintFont(paint);
 	paint.setClipRect(cacheRect);
 
         paint.setPen(getBackground());
@@ -1614,7 +1653,7 @@
 
     paint.begin(this);
     paint.setClipRect(nonCacheRect);
-
+    setPaintFont(paint);
     if (scrollables.empty()) {
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
@@ -1632,6 +1671,7 @@
     paint.end();
 
     paint.begin(this);
+    setPaintFont(paint);
     if (e) paint.setClipRect(e->rect());
     if (!m_selectionCached) {
 	drawSelections(paint);
@@ -1714,8 +1754,8 @@
     for (MultiSelection::SelectionList::iterator i = selections.begin();
 	 i != selections.end(); ++i) {
 
-	int p0 = getXForFrame(i->getStartFrame());
-	int p1 = getXForFrame(i->getEndFrame());
+	int p0 = getXForFrame(alignFromReference(i->getStartFrame()));
+	int p1 = getXForFrame(alignFromReference(i->getEndFrame()));
 
 	if (p1 < 0 || p0 > width()) continue;
 
@@ -1875,6 +1915,8 @@
     
     int labelCount = 0;
 
+    // top-left point, x-coord
+
     if ((b0 = topLayer->getXScaleValue(this, r.x(), v0, u0))) {
         axs = QString("%1 %2").arg(v0).arg(u0);
         if (u0 == "Hz" && Pitch::isFrequencyInMidiRange(v0)) {
@@ -1884,6 +1926,8 @@
         aw = paint.fontMetrics().width(axs);
         ++labelCount;
     }
+
+    // bottom-right point, x-coord
         
     if (r.width() > 0) {
         if ((b1 = topLayer->getXScaleValue(this, r.x() + r.width(), v1, u1))) {
@@ -1895,15 +1939,19 @@
             bw = paint.fontMetrics().width(bxs);
         }
     }
+
+    // dimension, width
         
     if (b0 && b1 && v1 != v0 && u0 == u1) {
-        dxs = QString("(%1 %2)").arg(fabs(v1 - v0)).arg(u1);
+        dxs = QString("[%1 %2]").arg(fabs(v1 - v0)).arg(u1);
         dw = paint.fontMetrics().width(dxs);
     }
     
     b0 = false;
     b1 = false;
 
+    // top-left point, y-coord
+
     if ((b0 = topLayer->getYScaleValue(this, r.y(), v0, u0))) {
         ays = QString("%1 %2").arg(v0).arg(u0);
         if (u0 == "Hz" && Pitch::isFrequencyInMidiRange(v0)) {
@@ -1914,6 +1962,8 @@
         ++labelCount;
     }
 
+    // bottom-right point, y-coord
+
     if (r.height() > 0) {
         if ((b1 = topLayer->getYScaleValue(this, r.y() + r.height(), v1, u1))) {
             bys = QString("%1 %2").arg(v1).arg(u1);
@@ -1929,13 +1979,24 @@
     float dy = 0.f;
     QString du;
 
+    // dimension, height
+        
     if ((bd = topLayer->getYScaleDifference(this, r.y(), r.y() + r.height(),
                                             dy, du)) &&
         dy != 0) {
         if (du != "") {
-            dys = QString("(%1 %2)").arg(dy).arg(du);
+            if (du == "Hz") {
+                int semis;
+                float cents;
+                semis = Pitch::getPitchForFrequencyDifference(v0, v1, &cents);
+                dys = QString("[%1 %2 (%3)]")
+                    .arg(dy).arg(du)
+                    .arg(Pitch::getLabelForPitchRange(semis, cents));
+            } else {
+                dys = QString("[%1 %2]").arg(dy).arg(du);
+            }
         } else {
-            dys = QString("(%1)").arg(dy);
+            dys = QString("[%1]").arg(dy);
         }
         dw = std::max(dw, paint.fontMetrics().width(dys));
     }
--- a/view/View.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/View.h	Wed Feb 27 11:59:42 2008 +0000
@@ -271,6 +271,8 @@
     void propertyContainerSelected(PropertyContainer *pc);
     void propertyChanged(PropertyContainer::PropertyName);
 
+    void layerModelChanged();
+
     void centreFrameChanged(unsigned long frame,
                             bool globalScroll,
                             PlaybackFollowMode followMode);
@@ -308,7 +310,8 @@
     virtual void drawSelections(QPainter &);
     virtual bool shouldLabelSelections() const { return true; }
     virtual bool render(QPainter &paint, int x0, size_t f0, size_t f1);
-
+    virtual void setPaintFont(QPainter &paint);
+    
     typedef std::vector<Layer *> LayerList;
 
     int getModelsSampleRate() const;
--- a/view/ViewManager.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/ViewManager.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -57,7 +57,7 @@
     settings.endGroup();
 
     if (getGlobalDarkBackground()) {
-
+/*
         std::cerr << "dark palette:" << std::endl;
         std::cerr << "window = " << QApplication::palette().color(QPalette::Window).name().toStdString() << std::endl;
         std::cerr << "windowtext = " << QApplication::palette().color(QPalette::WindowText).name().toStdString() << std::endl;
@@ -70,7 +70,7 @@
         std::cerr << "light = " << QApplication::palette().color(QPalette::Light).name().toStdString() << std::endl;
         std::cerr << "dark = " << QApplication::palette().color(QPalette::Dark).name().toStdString() << std::endl;
         std::cerr << "mid = " << QApplication::palette().color(QPalette::Mid).name().toStdString() << std::endl;
-
+*/
         m_lightPalette = QPalette(QColor("#000000"),  // WindowText
                                   QColor("#dddfe4"),  // Button
                                   QColor("#ffffff"),  // Light
@@ -83,6 +83,7 @@
                                   
 
     } else {
+/*
         std::cerr << "light palette:" << std::endl;
         std::cerr << "window = " << QApplication::palette().color(QPalette::Window).name().toStdString() << std::endl;
         std::cerr << "windowtext = " << QApplication::palette().color(QPalette::WindowText).name().toStdString() << std::endl;
@@ -95,7 +96,7 @@
         std::cerr << "light = " << QApplication::palette().color(QPalette::Light).name().toStdString() << std::endl;
         std::cerr << "dark = " << QApplication::palette().color(QPalette::Dark).name().toStdString() << std::endl;
         std::cerr << "mid = " << QApplication::palette().color(QPalette::Mid).name().toStdString() << std::endl;
-
+*/
         m_darkPalette = QPalette(QColor("#ffffff"),  // WindowText
                                  QColor("#3e3e3e"),  // Button
                                  QColor("#808080"),  // Light
@@ -173,6 +174,20 @@
     m_playbackModel = model;
 }
 
+size_t
+ViewManager::alignPlaybackFrameToReference(size_t frame) const
+{
+    if (!m_playbackModel) return frame;
+    else return m_playbackModel->alignToReference(frame);
+}
+
+size_t
+ViewManager::alignReferenceToPlaybackFrame(size_t frame) const
+{
+    if (!m_playbackModel) return frame;
+    else return m_playbackModel->alignFromReference(frame);
+}
+
 bool
 ViewManager::haveInProgressSelection() const
 {
@@ -260,18 +275,19 @@
     MultiSelection::SelectionList sl = getSelections();
     if (sl.empty()) return frame;
 
-    size_t selectionStartFrame = sl.begin()->getStartFrame();
-    if (frame < selectionStartFrame) {
-        frame = selectionStartFrame;
-        return frame;
+    for (MultiSelection::SelectionList::const_iterator i = sl.begin();
+         i != sl.end(); ++i) {
+
+        if (frame < i->getEndFrame()) {
+            if (frame < i->getStartFrame()) {
+                return i->getStartFrame();
+            } else {
+                return frame;
+            }
+        }
     }
 
-    MultiSelection::SelectionList::iterator i = sl.end();
-    --i;
-    size_t selectionEndFrame = i->getEndFrame();
-    if (frame > selectionEndFrame) frame = selectionEndFrame;
-
-    return frame;
+    return sl.begin()->getStartFrame();
 }
 
 void
--- a/view/ViewManager.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/view/ViewManager.h	Wed Feb 27 11:59:42 2008 +0000
@@ -69,6 +69,9 @@
     Model *getPlaybackModel() const;
     void setPlaybackModel(Model *);
 
+    size_t alignPlaybackFrameToReference(size_t) const;
+    size_t alignReferenceToPlaybackFrame(size_t) const;
+
     bool haveInProgressSelection() const;
     const Selection &getInProgressSelection(bool &exclusive) const;
     void setInProgressSelection(const Selection &selection, bool exclusive);
@@ -98,6 +101,7 @@
 	SelectMode,
         EditMode,
 	DrawMode,
+	EraseMode,
 	MeasureMode
     };
     ToolMode getToolMode() const { return m_toolMode; }
--- a/widgets/AudioDial.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/AudioDial.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -75,6 +75,7 @@
     m_knobColor(Qt::black),
     m_meterColor(Qt::white),
     m_defaultValue(0),
+    m_defaultMappedValue(0),
     m_mappedValue(0),
     m_noMappedUpdate(false),
     m_showTooltip(true),
@@ -334,15 +335,24 @@
 void AudioDial::setDefaultValue(int defaultValue)
 {
     m_defaultValue = defaultValue;
+    if (m_rangeMapper) {
+        m_defaultMappedValue = m_rangeMapper->getValueForPosition(defaultValue);
+    }
 }
 
-
 void AudioDial::setValue(int value)
 {
     QDial::setValue(value);
     updateMappedValue(value);
 }
 
+void AudioDial::setDefaultMappedValue(float value)
+{
+    m_defaultMappedValue = value;
+    if (m_rangeMapper) {
+        m_defaultValue = m_rangeMapper->getPositionForValue(value);
+    }
+}
 
 void AudioDial::setMappedValue(float mappedValue)
 {
@@ -407,6 +417,18 @@
     }
 }
 
+void
+AudioDial::setToDefault()
+{
+    if (m_rangeMapper) {
+        setMappedValue(m_defaultMappedValue);
+        return;
+    }
+    int dv = m_defaultValue;
+    if (dv < minimum()) dv = minimum();
+    if (dv > maximum()) dv = maximum();
+    setValue(m_defaultValue);
+}
 
 // Alternate mouse behavior event handlers.
 void AudioDial::mousePressEvent(QMouseEvent *mouseEvent)
@@ -416,10 +438,7 @@
     } 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);
+        setToDefault();
     } else if (mouseEvent->button() == Qt::LeftButton) {
 	m_mousePressed = true;
 	m_posMouse = mouseEvent->pos();
--- a/widgets/AudioDial.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/AudioDial.h	Wed Feb 27 11:59:42 2008 +0000
@@ -107,8 +107,12 @@
 
     void setValue(int value);
 
+    void setDefaultMappedValue(float mappedValue);
+
     void setMappedValue(float mappedValue);
 
+    void setToDefault();
+
 protected:
     void drawTick(QPainter &paint, float angle, int size, bool internal);
     virtual void paintEvent(QPaintEvent *);
@@ -129,6 +133,7 @@
     QColor m_meterColor;
     
     int m_defaultValue;
+    float m_defaultMappedValue;
     float m_mappedValue;
     bool m_noMappedUpdate;
 
--- a/widgets/ImageDialog.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/ImageDialog.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -186,7 +186,7 @@
                                       tr("The URL scheme \"%1\" is not supported")
                                       .arg(url.scheme()));
             } else {
-                m_remoteFile = new FileSource(url, true);
+                m_remoteFile = new FileSource(url, FileSource::ProgressDialog);
                 m_remoteFile->waitForData();
                 if (!m_remoteFile->isOK()) {
                     QMessageBox::critical(this, tr("File download failed"),
--- a/widgets/LabelCounterInputDialog.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/LabelCounterInputDialog.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -37,7 +37,7 @@
     layout->addWidget(label, 0, 0);
 
     QSpinBox *counter = new QSpinBox;
-    counter->setMinimum(1);
+    counter->setMinimum(-10);
     counter->setMaximum(10000);
     counter->setSingleStep(1);
     m_origSecondCounter = m_labeller->getSecondLevelCounterValue();
@@ -47,7 +47,7 @@
     layout->addWidget(counter, 0, 1);
 
     counter = new QSpinBox;
-    counter->setMinimum(1);
+    counter->setMinimum(-10);
     counter->setMaximum(10000);
     counter->setSingleStep(1);
     m_origCounter = m_labeller->getCounterValue();
--- a/widgets/LayerTree.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/LayerTree.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -21,17 +21,34 @@
 #include "view/Pane.h"
 #include "layer/Layer.h"
 #include "data/model/Model.h"
+#include "data/model/WaveFileModel.h"
 
 #include <QIcon>
 #include <iostream>
 
 
-LayerTreeModel::LayerTreeModel(PaneStack *stack, QObject *parent) :
+ModelDataModel::ModelDataModel(PaneStack *stack, bool waveModelsOnly,
+                               QObject *parent) :
     QAbstractItemModel(parent),
-    m_stack(stack)
+    m_stack(stack),
+    m_waveModelsOnly(waveModelsOnly)
 {
-    connect(stack, SIGNAL(paneAdded()), this, SIGNAL(layoutChanged()));
-    connect(stack, SIGNAL(paneDeleted()), this, SIGNAL(layoutChanged()));
+    if (m_waveModelsOnly) {
+        m_modelTypeColumn = -1;
+        m_modelNameColumn = 0;
+        m_modelMakerColumn = 1;
+        m_modelSourceColumn = 2;
+        m_columnCount = 3;
+    } else {
+        m_modelTypeColumn = 0;
+        m_modelNameColumn = 1;
+        m_modelMakerColumn = 2;
+        m_modelSourceColumn = 3;
+        m_columnCount = 4;
+    }
+
+    connect(stack, SIGNAL(paneAdded()), this, SLOT(paneAdded()));
+    connect(stack, SIGNAL(paneDeleted()), this, SLOT(paneDeleted()));
 
     for (int i = 0; i < stack->getPaneCount(); ++i) {
         Pane *pane = stack->getPane(i);
@@ -46,6 +63,238 @@
                 this, SLOT(propertyContainerPropertyChanged(PropertyContainer *)));
         connect(pane, SIGNAL(propertyContainerNameChanged(PropertyContainer *)),
                 this, SLOT(propertyContainerPropertyChanged(PropertyContainer *)));
+        connect(pane, SIGNAL(layerModelChanged()),
+                this, SLOT(paneLayerModelChanged()));
+    }
+
+    rebuildModelSet();
+}
+
+ModelDataModel::~ModelDataModel()
+{
+}
+
+void
+ModelDataModel::rebuildModelSet()
+{
+    std::set<Model *> unfound = m_models;
+
+    for (int i = 0; i < m_stack->getPaneCount(); ++i) {
+
+        Pane *pane = m_stack->getPane(i);
+        if (!pane) continue;
+
+        for (int j = 0; j < pane->getLayerCount(); ++j) {
+
+            Layer *layer = pane->getLayer(j);
+            if (!layer) continue;
+
+            Model *model = layer->getModel();
+            if (!model) continue;
+
+            if (m_waveModelsOnly) {
+                if (!dynamic_cast<WaveFileModel *>(model)) continue;
+            }
+
+            if (m_models.find(model) == m_models.end()) {
+                connect(model, SIGNAL(aboutToBeDeleted()),
+                        this, SLOT(rebuildModelSet()));
+                m_models.insert(model);
+            } else {
+                unfound.erase(model);
+            }
+        }
+    }
+
+    for (std::set<Model *>::iterator i = unfound.begin();
+         i != unfound.end(); ++i) {
+        m_models.erase(*i);
+    }
+
+    std::cerr << "ModelDataModel::rebuildModelSet: " << m_models.size() << " models" << std::endl;
+}
+
+void
+ModelDataModel::paneAdded()
+{
+    rebuildModelSet();
+    emit layoutChanged();
+}
+
+void
+ModelDataModel::paneDeleted()
+{
+    rebuildModelSet();
+    emit layoutChanged();
+}
+
+void
+ModelDataModel::paneLayerModelChanged()
+{
+    rebuildModelSet();
+    emit layoutChanged();
+}
+
+void
+ModelDataModel::propertyContainerAdded(PropertyContainer *)
+{
+    rebuildModelSet();
+    emit layoutChanged();
+}
+
+void
+ModelDataModel::propertyContainerRemoved(PropertyContainer *)
+{
+    rebuildModelSet();
+    emit layoutChanged();
+}
+
+void
+ModelDataModel::propertyContainerSelected(PropertyContainer *)
+{
+}
+
+void
+ModelDataModel::propertyContainerPropertyChanged(PropertyContainer *pc)
+{
+}
+
+void
+ModelDataModel::playParametersAudibilityChanged(bool a)
+{
+}
+
+QVariant
+ModelDataModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid()) return QVariant();
+
+    QObject *obj = static_cast<QObject *>(index.internalPointer());
+    int row = index.row(), col = index.column();
+
+    //!!! not exactly the ideal use of a std::set
+    std::set<Model *>::iterator itr = m_models.begin();
+    for (int i = 0; i < row && itr != m_models.end(); ++i, ++itr);
+    if (itr == m_models.end()) return QVariant();
+
+    Model *model = *itr;
+
+    if (role != Qt::DisplayRole) {
+        if (m_waveModelsOnly && col == m_modelNameColumn &&
+            role == Qt::DecorationRole) {
+            // There is no meaningful icon for a model, in general --
+            // the icons we have represent layer types and it would be
+            // misleading to use them for models.  However, if we're
+            // only showing wave models, we use the waveform icon just
+            // for decorative purposes.
+            return QVariant(QIcon(QString(":/icons/waveform.png")));
+        }
+        return QVariant();
+    }
+    
+    if (col == m_modelTypeColumn) {
+        return QVariant(model->getTypeName());
+    } else if (col == m_modelNameColumn) {
+        return QVariant(model->objectName());
+    } else if (col == m_modelMakerColumn) {
+        return QVariant(model->getMaker());
+    } else if (col == m_modelSourceColumn) {
+        return QVariant(model->getLocation());
+    }        
+    
+    return QVariant();
+}
+
+bool
+ModelDataModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    return false;
+}
+
+Qt::ItemFlags
+ModelDataModel::flags(const QModelIndex &index) const
+{
+    Qt::ItemFlags flags = Qt::ItemIsEnabled;
+    return flags;
+}
+
+QVariant
+ModelDataModel::headerData(int section,
+			   Qt::Orientation orientation,
+			   int role) const
+{
+    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+	if (section == m_modelTypeColumn) return QVariant(tr("Type"));
+	else if (section == m_modelNameColumn) return QVariant(tr("Name"));
+	else if (section == m_modelMakerColumn) return QVariant(tr("Maker"));
+	else if (section == m_modelSourceColumn) return QVariant(tr("Source"));
+    }
+
+    return QVariant();
+}
+
+QModelIndex
+ModelDataModel::index(int row, int column, const QModelIndex &parent) const
+{
+    if (!parent.isValid()) {
+        if (row >= m_models.size()) return QModelIndex();
+	return createIndex(row, column, 0);
+    }
+
+    return QModelIndex();
+}
+
+QModelIndex
+ModelDataModel::parent(const QModelIndex &index) const
+{
+    return QModelIndex();
+}
+
+int
+ModelDataModel::rowCount(const QModelIndex &parent) const
+{
+    if (!parent.isValid()) return m_models.size();
+    return 0;
+}
+
+int
+ModelDataModel::columnCount(const QModelIndex &parent) const
+{
+    return m_columnCount;
+}
+
+
+
+LayerTreeModel::LayerTreeModel(PaneStack *stack, QObject *parent) :
+    QAbstractItemModel(parent),
+    m_stack(stack)
+{
+    m_layerNameColumn = 0;
+    m_layerVisibleColumn = 1;
+    m_layerPlayedColumn = 2;
+    m_modelNameColumn = 3;
+    m_columnCount = 4;
+
+    connect(stack, SIGNAL(paneAdded()), this, SLOT(paneAdded()));
+    connect(stack, SIGNAL(paneAboutToBeDeleted(Pane *)),
+            this, SLOT(paneAboutToBeDeleted(Pane *)));
+
+    for (int i = 0; i < stack->getPaneCount(); ++i) {
+        Pane *pane = stack->getPane(i);
+        if (!pane) continue;
+        connect(pane, SIGNAL(propertyContainerAdded(PropertyContainer *)),
+                this, SLOT(propertyContainerAdded(PropertyContainer *)));
+        connect(pane, SIGNAL(propertyContainerRemoved(PropertyContainer *)),
+                this, SLOT(propertyContainerRemoved(PropertyContainer *)));
+        connect(pane, SIGNAL(propertyContainerSelected(PropertyContainer *)),
+                this, SLOT(propertyContainerSelected(PropertyContainer *)));
+        connect(pane, SIGNAL(propertyContainerPropertyChanged(PropertyContainer *)),
+                this, SLOT(propertyContainerPropertyChanged(PropertyContainer *)));
+        connect(pane, SIGNAL(propertyContainerNameChanged(PropertyContainer *)),
+                this, SLOT(propertyContainerPropertyChanged(PropertyContainer *)));
+        connect(pane, SIGNAL(layerModelChanged()),
+                this, SLOT(paneLayerModelChanged()));
+
         for (int j = 0; j < pane->getLayerCount(); ++j) {
             Layer *layer = pane->getLayer(j);
             if (!layer) continue;
@@ -62,6 +311,20 @@
 }
 
 void
+LayerTreeModel::paneAdded()
+{
+    emit layoutChanged();
+}
+
+void
+LayerTreeModel::paneAboutToBeDeleted(Pane *pane)
+{
+    std::cerr << "paneDeleted: " << pane << std::endl;
+    m_deletedPanes.insert(pane);
+    emit layoutChanged();
+}
+
+void
 LayerTreeModel::propertyContainerAdded(PropertyContainer *)
 {
     emit layoutChanged();
@@ -80,6 +343,12 @@
 }
 
 void
+LayerTreeModel::paneLayerModelChanged()
+{
+    emit layoutChanged();
+}
+
+void
 LayerTreeModel::propertyContainerPropertyChanged(PropertyContainer *pc)
 {
     for (int i = 0; i < m_stack->getPaneCount(); ++i) {
@@ -88,9 +357,9 @@
         for (int j = 0; j < pane->getLayerCount(); ++j) {
             if (pane->getLayer(j) == pc) {
                 emit dataChanged(createIndex(pane->getLayerCount() - j - 1,
-                                             0, pane),
+                                             m_layerNameColumn, pane),
                                  createIndex(pane->getLayerCount() - j - 1,
-                                             3, pane));
+                                             m_modelNameColumn, pane));
             }
         }
     }
@@ -116,9 +385,9 @@
                           << params << "," << a << "): row " << pane->getLayerCount() - j - 1 << ", col " << 2 << std::endl;
 
                 emit dataChanged(createIndex(pane->getLayerCount() - j - 1,
-                                             2, pane),
+                                             m_layerPlayedColumn, pane),
                                  createIndex(pane->getLayerCount() - j - 1,
-                                             2, pane));
+                                             m_layerPlayedColumn, pane));
             }
         }
     }
@@ -148,7 +417,7 @@
     if (pane && pane->getLayerCount() > row) {
         Layer *layer = pane->getLayer(pane->getLayerCount() - row - 1);
         if (layer) {
-            if (col == 0) {
+            if (col == m_layerNameColumn) {
                 switch (role) {
                 case Qt::DisplayRole:
                     return QVariant(layer->objectName());
@@ -158,14 +427,14 @@
                                .arg(layer->getPropertyContainerIconName())));
                 default: break;
                 }
-            } else if (col == 1) {
+            } else if (col == m_layerVisibleColumn) {
                 if (role == Qt::CheckStateRole) {
                     return QVariant(layer->isLayerDormant(pane) ?
                                     Qt::Unchecked : Qt::Checked);
                 } else if (role == Qt::TextAlignmentRole) {
                     return QVariant(Qt::AlignHCenter);
                 }
-            } else if (col == 2) {
+            } else if (col == m_layerPlayedColumn) {
                 if (role == Qt::CheckStateRole) {
                     PlayParameters *params = layer->getPlayParameters();
                     if (params) return QVariant(params->isPlayMuted() ?
@@ -174,7 +443,7 @@
                 } else if (role == Qt::TextAlignmentRole) {
                     return QVariant(Qt::AlignHCenter);
                 }
-            } else if (col == 3) {
+            } else if (col == m_modelNameColumn) {
                 Model *model = layer->getModel();
                 if (model && role == Qt::DisplayRole) {
                     return QVariant(model->objectName());
@@ -200,13 +469,13 @@
     Layer *layer = pane->getLayer(pane->getLayerCount() - row - 1);
     if (!layer) return false;
 
-    if (col == 1) {
+    if (col == m_layerVisibleColumn) {
         if (role == Qt::CheckStateRole) {
             layer->showLayer(pane, value.toInt() == Qt::Checked);
             emit dataChanged(index, index);
             return true;
         }
-    } else if (col == 2) {
+    } else if (col == m_layerPlayedColumn) {
         if (role == Qt::CheckStateRole) {
             PlayParameters *params = layer->getPlayParameters();
             if (params) {
@@ -226,7 +495,8 @@
     Qt::ItemFlags flags = Qt::ItemIsEnabled;
     if (!index.isValid()) return flags;
 
-    if (index.column() == 1 || index.column() == 2) {
+    if (index.column() == m_layerVisibleColumn ||
+        index.column() == m_layerPlayedColumn) {
         flags |= Qt::ItemIsUserCheckable;
     } else if (index.column() == 0) {
         flags |= Qt::ItemIsSelectable;
@@ -241,10 +511,10 @@
 			   int role) const
 {
     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
-	if (section == 0) return QVariant(tr("Layer"));
-        else if (section == 1) return QVariant(tr("Shown"));
-        else if (section == 2) return QVariant(tr("Played"));
-	else if (section == 3) return QVariant(tr("Model"));
+	if (section == m_layerNameColumn) return QVariant(tr("Layer"));
+        else if (section == m_layerVisibleColumn) return QVariant(tr("Shown"));
+        else if (section == m_layerPlayedColumn) return QVariant(tr("Played"));
+	else if (section == m_modelNameColumn) return QVariant(tr("Model"));
     }
 
     return QVariant();
@@ -280,6 +550,11 @@
 {
     QObject *obj = static_cast<QObject *>(index.internalPointer());
 
+    if (m_deletedPanes.find(obj) != m_deletedPanes.end()) {
+//        m_deletedPanes.erase(obj);
+        return QModelIndex();
+    }
+
     Pane *pane = dynamic_cast<Pane *>(obj);
     if (pane) {
         int index = m_stack->getPaneIndex(pane);
@@ -308,10 +583,10 @@
 int
 LayerTreeModel::columnCount(const QModelIndex &parent) const
 {
-    if (!parent.isValid()) return 4;
+    if (!parent.isValid()) return m_columnCount;
 
     QObject *obj = static_cast<QObject *>(parent.internalPointer());
-    if (obj == m_stack) return 4; // row for a layer
+    if (obj == m_stack) return m_columnCount; // row for a layer
 
     return 1;
 }
--- a/widgets/LayerTree.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/LayerTree.h	Wed Feb 27 11:59:42 2008 +0000
@@ -19,10 +19,62 @@
 
 #include <QAbstractItemModel>
 
+#include <set>
+
 class PaneStack;
 class View;
+class Pane;
 class Layer;
 class PropertyContainer;
+class Model;
+
+class ModelDataModel : public QAbstractItemModel
+{
+    Q_OBJECT
+
+public:
+    ModelDataModel(PaneStack *stack, bool waveModelsOnly, QObject *parent = 0);
+    virtual ~ModelDataModel();
+
+    QVariant data(const QModelIndex &index, int role) const;
+
+    bool setData(const QModelIndex &index, const QVariant &value, int role);
+
+    Qt::ItemFlags flags(const QModelIndex &index) const;
+
+    QVariant headerData(int section, Qt::Orientation orientation,
+                        int role = Qt::DisplayRole) const;
+
+    QModelIndex index(int row, int column,
+                      const QModelIndex &parent = QModelIndex()) const;
+
+    QModelIndex parent(const QModelIndex &index) const;
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+    int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+protected slots:
+    void paneAdded();
+    void paneDeleted();
+    void propertyContainerAdded(PropertyContainer *);
+    void propertyContainerRemoved(PropertyContainer *);
+    void propertyContainerSelected(PropertyContainer *);
+    void propertyContainerPropertyChanged(PropertyContainer *);
+    void playParametersAudibilityChanged(bool);
+    void paneLayerModelChanged();
+    void rebuildModelSet();
+
+protected:
+    PaneStack *m_stack;
+    bool m_waveModelsOnly;
+    int m_modelTypeColumn;
+    int m_modelNameColumn;
+    int m_modelMakerColumn;
+    int m_modelSourceColumn;
+    int m_columnCount;
+
+    std::set<Model *> m_models;
+};
 
 class LayerTreeModel : public QAbstractItemModel
 {
@@ -49,15 +101,24 @@
     int rowCount(const QModelIndex &parent = QModelIndex()) const;
     int columnCount(const QModelIndex &parent = QModelIndex()) const;
 
-protected:
-    PaneStack *m_stack;
-
 protected slots:
+    void paneAdded();
+    void paneAboutToBeDeleted(Pane *);
     void propertyContainerAdded(PropertyContainer *);
     void propertyContainerRemoved(PropertyContainer *);
     void propertyContainerSelected(PropertyContainer *);
     void propertyContainerPropertyChanged(PropertyContainer *);
+    void paneLayerModelChanged();
     void playParametersAudibilityChanged(bool);
+
+protected:
+    PaneStack *m_stack;
+    std::set<QObject *> m_deletedPanes;
+    int m_layerNameColumn;
+    int m_layerVisibleColumn;
+    int m_layerPlayedColumn;
+    int m_modelNameColumn;
+    int m_columnCount;
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LayerTreeDialog.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -0,0 +1,103 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2007 QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LayerTreeDialog.h"
+
+#include "LayerTree.h"
+#include "view/PaneStack.h"
+
+#include <QTreeView>
+#include <QTableView>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QDialogButtonBox>
+#include <QHeaderView>
+#include <QApplication>
+#include <QDesktopWidget>
+
+LayerTreeDialog::LayerTreeDialog(PaneStack *stack, QWidget *parent) :
+    QDialog(parent),
+    m_paneStack(stack)
+{
+    setWindowTitle(tr("Layer Summary"));
+
+    QGridLayout *grid = new QGridLayout;
+    setLayout(grid);
+    
+    QGroupBox *modelBox = new QGroupBox;
+    modelBox->setTitle(tr("Audio Data Sources"));
+    grid->addWidget(modelBox, 0, 0);
+    grid->setRowStretch(0, 15);
+
+    QGridLayout *subgrid = new QGridLayout;
+    modelBox->setLayout(subgrid);
+
+    subgrid->setSpacing(0);
+    subgrid->setMargin(5);
+
+    m_modelView = new QTableView;
+    subgrid->addWidget(m_modelView);
+
+    m_modelView->verticalHeader()->hide();
+    m_modelView->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
+    m_modelView->setShowGrid(false);
+
+    m_modelModel = new ModelDataModel(m_paneStack, true);
+    m_modelView->setModel(m_modelModel);
+
+    QGroupBox *layerBox = new QGroupBox;
+    layerBox->setTitle(tr("Panes and Layers"));
+    grid->addWidget(layerBox, 1, 0);
+    grid->setRowStretch(1, 20);
+
+    subgrid = new QGridLayout;
+    layerBox->setLayout(subgrid);
+
+    subgrid->setSpacing(0);
+    subgrid->setMargin(5);
+
+    m_layerView = new QTreeView;
+    m_layerView->header()->setResizeMode(QHeaderView::ResizeToContents);
+    subgrid->addWidget(m_layerView);
+
+    m_layerModel = new LayerTreeModel(m_paneStack);
+    m_layerView->setModel(m_layerModel);
+    m_layerView->expandAll();
+
+    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
+    connect(bb, SIGNAL(rejected()), this, SLOT(reject()));
+    grid->addWidget(bb, 2, 0);
+    grid->setRowStretch(2, 0);
+    
+    QDesktopWidget *desktop = QApplication::desktop();
+    QRect available = desktop->availableGeometry();
+
+    int width = available.width() / 2;
+    int height = available.height() / 3;
+    if (height < 370) {
+        if (available.height() > 500) height = 370;
+    }
+    if (width < 500) {
+        if (available.width() > 650) width = 500;
+    }
+
+    resize(width, height);
+}
+
+LayerTreeDialog::~LayerTreeDialog()
+{
+    delete m_layerModel;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LayerTreeDialog.h	Wed Feb 27 11:59:42 2008 +0000
@@ -0,0 +1,43 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2007 QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _LAYER_TREE_DIALOG_H_
+#define _LAYER_TREE_DIALOG_H_
+
+#include <QDialog>
+
+class ModelDataModel;
+class LayerTreeModel;
+class PaneStack;
+class QTreeView;
+class QTableView;
+
+class LayerTreeDialog : public QDialog
+{
+    Q_OBJECT
+    
+public:
+    LayerTreeDialog(PaneStack *stack, QWidget *parent = 0);
+    ~LayerTreeDialog();
+
+protected:
+    PaneStack *m_paneStack;
+    ModelDataModel *m_modelModel;
+    QTableView *m_modelView;
+    LayerTreeModel *m_layerModel;
+    QTreeView *m_layerView;
+};
+
+#endif
--- a/widgets/PluginParameterBox.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/PluginParameterBox.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -18,6 +18,7 @@
 #include "AudioDial.h"
 
 #include "plugin/PluginXml.h"
+#include "plugin/RealTimePluginInstance.h" // for PortHint stuff
 
 #include "base/RangeMapper.h"
 
@@ -98,19 +99,31 @@
         float deft = params[i].defaultValue;
         float value = m_plugin->getParameter(params[i].identifier);
 
+        int hint = PortHint::NoHint;
+        RealTimePluginInstance *rtpi = dynamic_cast<RealTimePluginInstance *>
+            (m_plugin);
+        if (rtpi) {
+            hint = rtpi->getParameterDisplayHint(i);
+        }
+
         float qtz = 0.0;
         if (params[i].isQuantized) qtz = params[i].quantizeStep;
 
+        std::cerr << "PluginParameterBox: hint = " << hint << ", min = " << min << ", max = "
+                  << max << ", qtz = " << qtz << std::endl;
+
         std::vector<std::string> valueNames = params[i].valueNames;
 
         // construct an integer range
 
         int imin = 0, imax = 100;
 
-        if (qtz > 0.0) {
-            imax = int((max - min) / qtz);
-        } else {
-            qtz = (max - min) / 100.0;
+        if (!(hint & PortHint::Logarithmic)) {
+            if (qtz > 0.0) {
+                imax = int((max - min) / qtz);
+            } else {
+                qtz = (max - min) / 100.0;
+            }
         }
 
         //!!! would be nice to ensure the default value corresponds to
@@ -162,12 +175,19 @@
             dial->setMaximum(imax);
             dial->setPageStep(1);
             dial->setNotchesVisible((imax - imin) <= 12);
-            dial->setDefaultValue(lrintf((deft - min) / qtz));
-            dial->setValue(lrintf((value - min) / qtz));
+//!!!            dial->setDefaultValue(lrintf((deft - min) / qtz));
+//            dial->setValue(lrintf((value - min) / qtz));
             dial->setFixedWidth(32);
             dial->setFixedHeight(32);
-            dial->setRangeMapper(new LinearRangeMapper
-                                 (imin, imax, min, max, unit));
+            RangeMapper *rm = 0;
+            if (hint & PortHint::Logarithmic) {
+                rm = new LogRangeMapper(imin, imax, min, max, unit);
+            } else {
+                rm = new LinearRangeMapper(imin, imax, min, max, unit);
+            }
+            dial->setRangeMapper(rm);
+            dial->setDefaultValue(rm->getPositionForValue(deft));
+            dial->setValue(rm->getPositionForValue(value));
             dial->setShowToolTip(true);
             connect(dial, SIGNAL(valueChanged(int)),
                     this, SLOT(dialChanged(int)));
@@ -178,7 +198,7 @@
             spinbox->setMinimum(min);
             spinbox->setMaximum(max);
             spinbox->setSuffix(QString(" %1").arg(unit));
-            spinbox->setSingleStep(qtz);
+            if (qtz != 0) spinbox->setSingleStep(qtz);
             spinbox->setValue(value);
             spinbox->setDecimals(4);
             connect(spinbox, SIGNAL(valueChanged(double)),
@@ -238,6 +258,8 @@
         newValue = min + ival * qtz;
     }
 
+    std::cerr << "PluginParameterBox::dialChanged: newValue = " << newValue << std::endl;
+
     QDoubleSpinBox *spin = m_params[identifier].spin;
     if (spin) {
         spin->blockSignals(true);
@@ -320,7 +342,11 @@
     AudioDial *dial = m_params[identifier].dial;
     if (dial) {
         dial->blockSignals(true);
-        dial->setValue(ival);
+        if (dial->rangeMapper()) {
+            dial->setMappedValue(value);
+        } else {
+            dial->setValue(ival);
+        }
         dial->blockSignals(false);
     }
 
--- a/widgets/PluginParameterDialog.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/PluginParameterDialog.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -440,7 +440,8 @@
 }
 
 void
-PluginParameterDialog::setCandidateInputModels(const QStringList &models)
+PluginParameterDialog::setCandidateInputModels(const QStringList &models,
+                                               QString defaultModel)
 {
     m_inputModels->clear();
 
@@ -449,15 +450,17 @@
     QString lastModel = settings.value("lastinputmodel").toString();
     settings.endGroup();
 
+    if (defaultModel == "") defaultModel = lastModel;
+
     m_inputModels->show();
 
     m_inputModelList = models;
     m_inputModels->addItems(TextAbbrev::abbreviate(models, 80));
     m_inputModels->setCurrentIndex(0);
 
-    if (lastModel != "") {
+    if (defaultModel != "") {
         for (int i = 0; i < models.size(); ++i) {
-            if (lastModel == models[i]) {
+            if (defaultModel == models[i]) {
                 m_inputModels->setCurrentIndex(i);
                 m_currentInputModel = models[i];
                 break;
--- a/widgets/PluginParameterDialog.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/PluginParameterDialog.h	Wed Feb 27 11:59:42 2008 +0000
@@ -54,7 +54,8 @@
     void setShowProcessingOptions(bool showWindowSize,
                                   bool showFrequencyDomainOptions);
 
-    void setCandidateInputModels(const QStringList &names);
+    void setCandidateInputModels(const QStringList &names,
+                                 QString defaultName);
     void setShowSelectionOnlyOption(bool show);
 
     Vamp::PluginBase *getPlugin() { return m_plugin; }
--- a/widgets/PropertyBox.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/PropertyBox.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -29,9 +29,11 @@
 
 #include "AudioDial.h"
 #include "LEDButton.h"
+#include "IconLoader.h"
 
 #include "NotifyingCheckBox.h"
 #include "NotifyingComboBox.h"
+#include "NotifyingPushButton.h"
 #include "ColourNameDialog.h"
 
 #include <QGridLayout>
@@ -270,6 +272,7 @@
 
     QString groupName = m_container->getPropertyGroupName(name);
     QString propertyLabel = m_container->getPropertyLabel(name);
+    QString iconName = m_container->getPropertyIconName(name);
 
 #ifdef DEBUG_PROPERTY_BOX
     std::cerr << "PropertyBox[" << this
@@ -307,36 +310,45 @@
 
     case PropertyContainer::ToggleProperty:
     {
-        NotifyingCheckBox *cb;
+        QAbstractButton *button = 0;
 
 	if (have) {
-	    cb = dynamic_cast<NotifyingCheckBox *>(m_propertyControllers[name]);
-	    assert(cb);
+            button = dynamic_cast<QAbstractButton *>(m_propertyControllers[name]);
+            assert(button);
 	} else {
 #ifdef DEBUG_PROPERTY_BOX 
 	    std::cerr << "PropertyBox: creating new checkbox" << std::endl;
 #endif
-	    cb = new NotifyingCheckBox();
-	    cb->setObjectName(name);
-	    connect(cb, SIGNAL(stateChanged(int)),
-		    this, SLOT(propertyControllerChanged(int)));
-            connect(cb, SIGNAL(mouseEntered()),
+            if (iconName != "") {
+                button = new NotifyingPushButton();
+                button->setCheckable(true);
+                QIcon icon(IconLoader().load(iconName));
+                button->setIcon(icon);
+                button->setObjectName(name);
+                button->setFixedSize(QSize(18, 18));
+            } else {
+                button = new NotifyingCheckBox();
+                button->setObjectName(name);
+            }
+	    connect(button, SIGNAL(toggled(bool)),
+		    this, SLOT(propertyControllerChanged(bool)));
+            connect(button, SIGNAL(mouseEntered()),
                     this, SLOT(mouseEnteredWidget()));
-            connect(cb, SIGNAL(mouseLeft()),
+            connect(button, SIGNAL(mouseLeft()),
                     this, SLOT(mouseLeftWidget()));
 	    if (inGroup) {
-		cb->setToolTip(propertyLabel);
-		m_groupLayouts[groupName]->addWidget(cb);
+		button->setToolTip(propertyLabel);
+		m_groupLayouts[groupName]->addWidget(button);
 	    } else {
-		m_layout->addWidget(cb, row, 1, 1, 2);
+		m_layout->addWidget(button, row, 1, 1, 2);
 	    }
-	    m_propertyControllers[name] = cb;
+	    m_propertyControllers[name] = button;
 	}
 
-	if (cb->isChecked() != (value > 0)) {
-	    cb->blockSignals(true);
-	    cb->setChecked(value > 0);
-	    cb->blockSignals(false);
+        if (button->isChecked() != (value > 0)) {
+	    button->blockSignals(true);
+	    button->setChecked(value > 0);
+	    button->blockSignals(false);
 	}
 	break;
     }
@@ -577,6 +589,12 @@
 }    
 
 void
+PropertyBox::propertyControllerChanged(bool on)
+{
+    propertyControllerChanged(on ? 1 : 0);
+}
+
+void
 PropertyBox::propertyControllerChanged(int value)
 {
     QObject *obj = sender();
@@ -624,8 +642,8 @@
     if (!newColour.isValid()) return;
 
     ColourNameDialog dialog(tr("Name New Colour"),
-                            tr("Enter name for the new colour:"),
-                            newColour, "", this);
+                            tr("Enter a name for the new colour:"),
+                            newColour, newColour.name(), this);
     dialog.showDarkBackgroundCheckbox(tr("Prefer black background for this colour"));
     if (dialog.exec() == QDialog::Accepted) {
         //!!! command
@@ -762,7 +780,7 @@
         emit contextHelpChanged(tr("Toggle Playback of %1").arg(cname));
     } else if (wname == "") {
         return;
-    } else if (dynamic_cast<NotifyingCheckBox *>(w)) {
+    } else if (dynamic_cast<QAbstractButton *>(w)) {
         emit contextHelpChanged(tr("Toggle %1 property of %2")
                                 .arg(wname).arg(cname));
     } else {
--- a/widgets/PropertyBox.h	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/PropertyBox.h	Wed Feb 27 11:59:42 2008 +0000
@@ -54,6 +54,7 @@
 
 protected slots:
     void propertyControllerChanged(int);
+    void propertyControllerChanged(bool);
 
     void playGainChanged(float);
     void playGainDialChanged(int);
--- a/widgets/PropertyStack.cpp	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/PropertyStack.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -114,17 +114,6 @@
 
         shortName = QString("&%1 %2").arg(i + 1).arg(shortName);
 
-#ifdef Q_WS_MAC
-
-        // Qt 4.2 on OS/X doesn't show the icons in the tab bar, and
-        // I'm not sure why -- use labels instead
-
-        addTab(box, shortName);
-        
-#else
-
-        // Icons on other platforms
-
 	QString iconName = container->getPropertyContainerIconName();
 
         QIcon icon(IconLoader().load(iconName));
@@ -135,8 +124,6 @@
 	    setTabToolTip(i, name);
 	}
 
-#endif
-
 	m_boxes.push_back(box);
     }    
 
--- a/widgets/widgets.pro	Mon Nov 19 15:50:37 2007 +0000
+++ b/widgets/widgets.pro	Wed Feb 27 11:59:42 2008 +0000
@@ -23,6 +23,7 @@
            KeyReference.h \
            LabelCounterInputDialog.h \
            LayerTree.h \
+           LayerTreeDialog.h \
            LEDButton.h \
            ListInputDialog.h \
            NotifyingCheckBox.h \
@@ -49,6 +50,7 @@
            KeyReference.cpp \
            LabelCounterInputDialog.cpp \
            LayerTree.cpp \
+           LayerTreeDialog.cpp \
            LEDButton.cpp \
            ListInputDialog.cpp \
            NotifyingCheckBox.cpp \