changeset 373:0895517bb2d1 1.2-stable

* merge from trunk (1.2 ended up being tracked from trunk, but we may want this branch for fixes later)
author Chris Cannam
date Wed, 27 Feb 2008 10:32:45 +0000
parents 813170c57b13
children
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/SpectrumLayer.cpp layer/SpectrumLayer.h layer/TextLayer.cpp layer/TextLayer.h layer/TimeInstantLayer.cpp layer/TimeInstantLayer.h layer/TimeRulerLayer.cpp 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 widgets/AudioDial.cpp widgets/AudioDial.h widgets/ImageDialog.cpp widgets/LabelCounterInputDialog.cpp widgets/PluginParameterBox.cpp widgets/PropertyBox.cpp widgets/PropertyStack.cpp
diffstat 38 files changed, 1167 insertions(+), 508 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/Colour3DPlotLayer.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/Colour3DPlotLayer.h	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/ImageLayer.cpp	Wed Feb 27 10:32:45 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,21 +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(m_model->alignToReference(i->frame));
+            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"));
 
@@ -834,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
@@ -880,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/ImageLayer.h	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/Layer.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/Layer.h	Wed Feb 27 10:32:45 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(); }
 
@@ -193,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
@@ -202,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; }
 
@@ -465,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;
@@ -540,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/LayerFactory.cpp	Wed Feb 27 10:32:45 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 *
@@ -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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/LayerFactory.h	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/NoteLayer.cpp	Wed Feb 27 10:32:45 2008 +0000
@@ -33,6 +33,7 @@
 #include <QPainterPath>
 #include <QMouseEvent>
 #include <QTextStream>
+#include <QMessageBox>
 
 #include <iostream>
 #include <cmath>
@@ -916,7 +917,7 @@
 }    
 
 void
-NoteLayer::copy(Selection s, Clipboard &to)
+NoteLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -927,19 +928,38 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label);
-            point.setReferenceFrame(m_model->alignToReference(i->frame));
+            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"));
 
@@ -948,9 +968,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();
+            }
         }
+
         NoteModel::Point newPoint(frame);
   
         if (i->haveLabel()) newPoint.label = i->getLabel();
--- a/layer/NoteLayer.h	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/NoteLayer.h	Wed Feb 27 10:32:45 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 const Model *getModel() const { return m_model; }
--- a/layer/SingleColourLayer.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/SingleColourLayer.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/SingleColourLayer.h	Wed Feb 27 10:32:45 2008 +0000
@@ -70,6 +70,7 @@
 
     int m_colour;
     bool m_colourExplicitlySet;
+    bool m_defaultColourSet;
 };
 
 #endif
--- a/layer/SpectrumLayer.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/SpectrumLayer.cpp	Wed Feb 27 10:32:45 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
@@ -640,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 *>
@@ -660,6 +676,8 @@
         pkh = 10;
 //!!!    }
 
+    paint.save();
+
     if (fft && m_showPeaks) {
 
         // draw peak lines
@@ -799,6 +817,8 @@
 	    px = x;
 	}
 //    }
+
+    paint.restore();
 }
 
 void
--- a/layer/SpectrumLayer.h	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/SpectrumLayer.h	Wed Feb 27 10:32:45 2008 +0000
@@ -24,6 +24,7 @@
 #include "data/model/DenseTimeValueModel.h"
 
 #include <QColor>
+#include <QMutex>
 
 class FFTModel;
 
@@ -113,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TextLayer.cpp	Wed Feb 27 10:32:45 2008 +0000
@@ -27,6 +27,7 @@
 #include <QMouseEvent>
 #include <QInputDialog>
 #include <QTextStream>
+#include <QMessageBox>
 
 #include <iostream>
 #include <cmath>
@@ -671,7 +672,7 @@
 }
 
 void
-TextLayer::copy(Selection s, Clipboard &to)
+TextLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -682,19 +683,38 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->height, i->label);
-            point.setReferenceFrame(m_model->alignToReference(i->frame));
+            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"));
 
@@ -713,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TextLayer.h	Wed Feb 27 10:32:45 2008 +0000
@@ -56,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TimeInstantLayer.cpp	Wed Feb 27 10:32:45 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>
@@ -711,7 +713,7 @@
 }
 
 void
-TimeInstantLayer::copy(Selection s, Clipboard &to)
+TimeInstantLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -722,43 +724,68 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->label);
-            point.setReferenceFrame(m_model->alignToReference(i->frame));
+            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"));
 
-    //!!!
-    
-    // Clipboard::haveReferenceFrames() will return true if any of the
-    // items in the clipboard came from an aligned, non-reference model.
-    
-    // We need to know whether these points came from our model or not
-    // -- if they did, we don't want to align them.
-
-    // If they didn't come from our model, and if reference frames are
-    // available, then we want to offer to align them.  If reference
-    // frames are unavailable but they came from the reference model,
-    // we want to offer to align them too.
-
     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();
+            }
         }
+
+        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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TimeInstantLayer.h	Wed Feb 27 10:32:45 2008 +0000
@@ -59,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; }
@@ -105,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TimeRulerLayer.cpp	Wed Feb 27 10:32:45 2008 +0000
@@ -27,6 +27,8 @@
 #include <iostream>
 #include <cmath>
 
+//#define DEBUG_TIME_RULER_LAYER 1
+
 using std::cerr;
 using std::endl;
 
@@ -192,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) {
--- a/layer/TimeValueLayer.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TimeValueLayer.cpp	Wed Feb 27 10:32:45 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;
@@ -1181,7 +1189,7 @@
 }    
 
 void
-TimeValueLayer::copy(Selection s, Clipboard &to)
+TimeValueLayer::copy(View *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1192,46 +1200,55 @@
 	 i != points.end(); ++i) {
 	if (s.contains(i->frame)) {
             Clipboard::Point point(i->frame, i->value, i->label);
-            point.setReferenceFrame(m_model->alignToReference(i->frame));
+            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) {
 
@@ -1278,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;
@@ -1319,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()) {
@@ -1353,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/TimeValueLayer.h	Wed Feb 27 10:32:45 2008 +0000
@@ -61,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/WaveformLayer.cpp	Wed Feb 27 10:32:45 2008 +0000
@@ -396,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
 {
@@ -403,8 +470,6 @@
 	return;
     }
   
-    long startFrame = v->getStartFrame();
-    long endFrame = v->getEndFrame();
     int zoomLevel = v->getZoomLevel();
 
 #ifdef DEBUG_WAVEFORM_PAINT
@@ -478,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 = 
@@ -507,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];
@@ -613,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) {
@@ -639,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;
@@ -650,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 {
@@ -696,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;
                 }
             }
 
@@ -890,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/layer/WaveformLayer.h	Wed Feb 27 10:32:45 2008 +0000
@@ -202,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/Overview.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/Pane.cpp	Wed Feb 27 10:32:45 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) ||
@@ -1303,12 +1314,8 @@
 
     } 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) {
 
@@ -1327,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);
     }
@@ -1356,7 +1367,10 @@
 
     } else if (mode == ViewManager::SelectMode) {
 
-        if (!hasTopLayerTimeXAxis()) return;
+        if (!hasTopLayerTimeXAxis()) {
+            m_releasing = false;
+            return;
+        }
 
 	if (m_manager && m_manager->haveInProgressSelection()) {
 
@@ -1396,13 +1410,15 @@
 
     } 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) {
 
@@ -1413,6 +1429,7 @@
     }
 
     m_clickedInRange = false;
+    m_releasing = false;
 
     emit paneInteractedWith();
 }
@@ -1424,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();
 
@@ -1509,12 +1542,44 @@
 
     } 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) {
 
@@ -1615,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());
 
@@ -1672,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;
@@ -1683,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;
@@ -1692,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) {
@@ -1715,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)
 {
--- a/view/Pane.h	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/Pane.h	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/PaneStack.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/PaneStack.h	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/View.cpp	Wed Feb 27 10:32:45 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>
@@ -328,9 +330,11 @@
 
 	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);
         }
     }
@@ -349,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;
 }
@@ -503,9 +512,6 @@
 View::LayerProgressBar::LayerProgressBar(QWidget *parent) :
     QProgressBar(parent)
 {
-    QFont f(font());
-    f.setPointSize(f.pointSize() * 8 / 10);
-    setFont(f);
 }
 
 void
@@ -523,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()),
@@ -655,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) {
@@ -880,8 +895,10 @@
 {
     if (m_followPan) {
         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);
     }
 }
@@ -1108,6 +1125,7 @@
     }
 
     Model *anyModel = 0;
+    Model *alignedModel = 0;
     Model *goodModel = 0;
 
     for (LayerList::const_iterator i = m_layers.begin();
@@ -1121,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;
@@ -1131,6 +1151,7 @@
     }
 
     if (goodModel) return goodModel;
+    else if (alignedModel) return alignedModel;
     else return anyModel;
 }
 
@@ -1399,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);
@@ -1583,7 +1612,7 @@
 
 	if (repaintCache) paint.begin(m_cache);
 	else paint.begin(this);
-
+        setPaintFont(paint);
 	paint.setClipRect(cacheRect);
 
         paint.setPen(getBackground());
@@ -1624,7 +1653,7 @@
 
     paint.begin(this);
     paint.setClipRect(nonCacheRect);
-
+    setPaintFont(paint);
     if (scrollables.empty()) {
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
@@ -1642,6 +1671,7 @@
     paint.end();
 
     paint.begin(this);
+    setPaintFont(paint);
     if (e) paint.setClipRect(e->rect());
     if (!m_selectionCached) {
 	drawSelections(paint);
@@ -1885,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)) {
@@ -1894,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))) {
@@ -1905,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)) {
@@ -1924,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);
@@ -1939,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/View.h	Wed Feb 27 10:32:45 2008 +0000
@@ -310,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	Thu Nov 29 10:43:54 2007 +0000
+++ b/view/ViewManager.cpp	Wed Feb 27 10:32:45 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
@@ -274,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/widgets/AudioDial.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/AudioDial.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/AudioDial.h	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/ImageDialog.cpp	Wed Feb 27 10:32:45 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	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/LabelCounterInputDialog.cpp	Wed Feb 27 10:32:45 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/PluginParameterBox.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/PluginParameterBox.cpp	Wed Feb 27 10:32:45 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/PropertyBox.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/PropertyBox.cpp	Wed Feb 27 10:32:45 2008 +0000
@@ -642,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
--- a/widgets/PropertyStack.cpp	Thu Nov 29 10:43:54 2007 +0000
+++ b/widgets/PropertyStack.cpp	Wed Feb 27 10:32:45 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);
     }