changeset 1045:f535f6e5dbb0 alignment-simple

Merge in from SV 3.0-integration branches
author Chris Cannam
date Wed, 02 Mar 2016 17:25:27 +0000
parents f2c63ec85901 (current diff) 4e5c1c326794 (diff)
children 834e94eb1bca
files svgui.pro view/View.h
diffstat 62 files changed, 2768 insertions(+), 1940 deletions(-) [+]
line wrap: on
line diff
--- a/acinclude.m4	Mon Jun 15 09:15:55 2015 +0100
+++ b/acinclude.m4	Wed Mar 02 17:25:27 2016 +0000
@@ -69,6 +69,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, $QTDIR/bin/qmake-qt5,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, $QTDIR/bin/qt5-qmake,,$QTDIR/bin/)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, $QTDIR/bin/qmake,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
@@ -78,6 +81,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, qmake-qt5,,$PATH)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, qt5-qmake,,$PATH)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, qmake,,$PATH)
 fi
 if test x$QMAKE = x ; then
--- a/configure	Mon Jun 15 09:15:55 2015 +0100
+++ b/configure	Wed Mar 02 17:25:27 2016 +0000
@@ -4137,6 +4137,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $QTDIR/bin/
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="$QTDIR/bin/qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -4254,6 +4293,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
--- a/configure.ac	Mon Jun 15 09:15:55 2015 +0100
+++ b/configure.ac	Wed Mar 02 17:25:27 2016 +0000
@@ -88,7 +88,7 @@
 SV_MODULE_REQUIRED([rubberband],[rubberband],[rubberband/RubberBandStretcher.h],[rubberband],[rubberband_new])
 
 SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new])
-SV_MODULE_OPTIONAL([portaudio_2_0],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
+SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported])
 SV_MODULE_OPTIONAL([JACK],[jack >= 0.100],[jack/jack.h],[jack],[jack_client_open])
 SV_MODULE_OPTIONAL([libpulse],[libpulse >= 0.9],[pulse/pulseaudio.h],[pulse],[pa_stream_new])
 SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init])
--- a/layer/Colour3DPlotLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -25,6 +25,7 @@
 #include <QImage>
 #include <QRect>
 #include <QTextStream>
+#include <QSettings>
 
 #include <iostream>
 
@@ -54,14 +55,16 @@
     m_normalizeVisibleArea(false),
     m_normalizeHybrid(false),
     m_invertVertical(false),
-    m_rectified(false),
     m_opaque(false),
     m_smooth(false),
     m_peakResolution(256),
     m_miny(0),
     m_maxy(0)
 {
-    
+    QSettings settings;
+    settings.beginGroup("Preferences");
+    setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt());
+    settings.endGroup();
 }
 
 Colour3DPlotLayer::~Colour3DPlotLayer()
@@ -163,7 +166,6 @@
     list.push_back("Gain");
     list.push_back("Bin Scale");
     list.push_back("Invert Vertical Scale");
-    list.push_back("Show Rectified");
     list.push_back("Opaque");
     list.push_back("Smooth");
     return list;
@@ -177,7 +179,6 @@
     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");
-    if (name == "Show Rectified") return tr("Half-Wave Rectify");
     if (name == "Gain") return tr("Gain");
     if (name == "Opaque") return tr("Always Opaque");
     if (name == "Smooth") return tr("Smooth");
@@ -191,7 +192,6 @@
     if (name == "Normalize Columns") return "normalise-columns";
     if (name == "Normalize Visible Area") return "normalise";
     if (name == "Invert Vertical Scale") return "invert-vertical";
-    if (name == "Show Rectified") return "derivative";
     if (name == "Opaque") return "opaque";
     if (name == "Smooth") return "smooth";
     return "";
@@ -204,7 +204,6 @@
     if (name == "Normalize Columns") return ToggleProperty;
     if (name == "Normalize Visible Area") return ToggleProperty;
     if (name == "Invert Vertical Scale") return ToggleProperty;
-    if (name == "Show Rectified") return ToggleProperty;
     if (name == "Opaque") return ToggleProperty;
     if (name == "Smooth") return ToggleProperty;
     return ValueProperty;
@@ -216,7 +215,6 @@
     if (name == "Normalize Columns" ||
         name == "Normalize Visible Area" ||
 	name == "Colour Scale" ||
-        name == "Show Rectified" ||
         name == "Gain") return tr("Scale");
     if (name == "Bin Scale" ||
         name == "Invert Vertical Scale") return tr("Bins");
@@ -281,13 +279,6 @@
         *deflt = 0;
 	val = (m_invertVertical ? 1 : 0);
 
-    } else if (name == "Show Rectified") {
-
-        if (min) *min = 0;
-        if (max) *max = 0;
-        if (deflt) *deflt = 0;
-        val = (m_rectified ? 1.0 : 0.0);
-
     } else if (name == "Bin Scale") {
 
 	*min = 0;
@@ -368,8 +359,6 @@
 	setNormalizeVisibleArea(value ? true : false);
     } else if (name == "Invert Vertical Scale") {
 	setInvertVertical(value ? true : false);
-    } else if (name == "Show Rectified") {
-        setShowRectified(value > 0.5);
     } else if (name == "Opaque") {
 	setOpaque(value ? true : false);
     } else if (name == "Smooth") {
@@ -487,15 +476,6 @@
 }
 
 void
-Colour3DPlotLayer::setShowRectified(bool show)
-{
-    if (m_rectified == show) return;
-    m_rectified = show;
-    cacheInvalid();
-    emit layerParametersChanged();
-}
-
-void
 Colour3DPlotLayer::setOpaque(bool n)
 {
     if (m_opaque == n) return;
@@ -530,7 +510,7 @@
 }
 
 void
-Colour3DPlotLayer::setLayerDormant(const View *v, bool dormant)
+Colour3DPlotLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     if (dormant) {
 
@@ -554,7 +534,7 @@
 }
 
 bool
-Colour3DPlotLayer::isLayerScrollable(const View *v) const
+Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     if (m_normalizeVisibleArea) {
         return false;
@@ -613,7 +593,7 @@
 }
 
 bool
-Colour3DPlotLayer::getYScaleValue(const View *, int,
+Colour3DPlotLayer::getYScaleValue(const LayerGeometryProvider *, int,
                                   double &, QString &) const
 {
     return false;//!!!
@@ -669,13 +649,13 @@
 }
 
 double
-Colour3DPlotLayer::getYForBin(View *v, double bin) const
+Colour3DPlotLayer::getYForBin(LayerGeometryProvider *v, double bin) const
 {
     double y = bin;
     if (!m_model) return y;
     double mn = 0, mx = m_model->getHeight();
     getDisplayExtents(mn, mx);
-    double h = v->height();
+    double h = v->getPaintHeight();
     if (m_binScale == LinearBinScale) {
         y = h - (((bin - mn) * h) / (mx - mn));
     } else {
@@ -687,19 +667,19 @@
 }
 
 int
-Colour3DPlotLayer::getIYForBin(View *v, int bin) const
+Colour3DPlotLayer::getIYForBin(LayerGeometryProvider *v, int bin) const
 {
     return int(round(getYForBin(v, bin)));
 }
 
 double
-Colour3DPlotLayer::getBinForY(View *v, double y) const
+Colour3DPlotLayer::getBinForY(LayerGeometryProvider *v, double y) const
 {
     double bin = y;
     if (!m_model) return bin;
     double mn = 0, mx = m_model->getHeight();
     getDisplayExtents(mn, mx);
-    double h = v->height();
+    double h = v->getPaintHeight();
     if (m_binScale == LinearBinScale) {
         bin = mn + ((h - y) * (mx - mn)) / h;
     } else {
@@ -711,13 +691,13 @@
 }
 
 int
-Colour3DPlotLayer::getIBinForY(View *v, int y) const
+Colour3DPlotLayer::getIBinForY(LayerGeometryProvider *v, int y) const
 {
     return int(floor(getBinForY(v, y)));
 }
 
 QString
-Colour3DPlotLayer::getFeatureDescription(View *v, QPoint &pos) const
+Colour3DPlotLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     if (!m_model) return "";
 
@@ -748,8 +728,8 @@
     if (symin < 0) symin = 0;
     if (symax > sh) symax = sh;
 
- //    double binHeight = double(v->height()) / (symax - symin);
-//    int sy = int((v->height() - y) / binHeight) + symin;
+ //    double binHeight = double(v->getPaintHeight()) / (symax - symin);
+//    int sy = int((v->getPaintHeight() - y) / binHeight) + symin;
 
     int sy = getIBinForY(v, y);
 
@@ -779,14 +759,15 @@
 }
 
 int
-Colour3DPlotLayer::getColourScaleWidth(QPainter &) const
+Colour3DPlotLayer::getColourScaleWidth(QPainter &p) const
 {
-    int cw = 20;
+    // Font is rotated
+    int cw = p.fontMetrics().height();
     return cw;
 }
 
 int
-Colour3DPlotLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const
+Colour3DPlotLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
     if (!m_model) return 0;
 
@@ -808,7 +789,7 @@
 }
 
 void
-Colour3DPlotLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const
+Colour3DPlotLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
     if (!m_model) return;
 
@@ -868,7 +849,7 @@
         paint.save();
 
         QFont font = paint.font();
-        font.setPixelSize(10);
+        font.setPixelSize(int(font.pixelSize() * 0.65));
         paint.setFont(font);
 
         int msw = paint.fontMetrics().width(maxstr);
@@ -906,6 +887,8 @@
 
     int py = h;
 
+    int defaultFontHeight = paint.fontMetrics().height();
+    
     for (int i = symin; i <= symax; ++i) {
 
         int y0;
@@ -915,9 +898,9 @@
 
         if (i > symin) {
             if (paint.fontMetrics().height() >= h) {
-                if (h >= 8) {
+                if (h >= defaultFontHeight * 0.8) {
                     QFont tf = paint.font();
-                    tf.setPixelSize(h-2);
+                    tf.setPixelSize(int(h * 0.8));
                     paint.setFont(tf);
                 } else {
                     continue;
@@ -954,36 +937,25 @@
 {
     Profiler profiler("Colour3DPlotLayer::getColumn");
 
-    DenseThreeDimensionalModel::Column prev;
-    if (m_rectified && (col > m_model->getStartFrame())) {
-        prev = m_model->getColumn(col - 1);
-    }
-    
     DenseThreeDimensionalModel::Column values = m_model->getColumn(col);
-
-    if (m_rectified && !prev.empty()) {
-        for (int y = 0; y < values.size(); ++y) {
-            if (values[y] < prev[y]) values[y] = 0;
-            else values[y] -= prev[y];
-        }
-    }
-    
-    while (values.size() < m_model->getHeight()) values.push_back(0.f);
+    values.resize(m_model->getHeight(), 0.f);
     if (!m_normalizeColumns && !m_normalizeHybrid) return values;
 
     double colMax = 0.f, colMin = 0.f;
     double min = 0.f, max = 0.f;
 
+    int nv = int(values.size());
+    
     min = m_model->getMinimumLevel();
     max = m_model->getMaximumLevel();
 
-    for (int y = 0; y < values.size(); ++y) {
+    for (int y = 0; y < nv; ++y) {
         if (y == 0 || values.at(y) > colMax) colMax = values.at(y);
         if (y == 0 || values.at(y) < colMin) colMin = values.at(y);
     }
     if (colMin == colMax) colMax = colMin + 1;
     
-    for (int y = 0; y < values.size(); ++y) {
+    for (int y = 0; y < nv; ++y) {
     
         double value = values.at(y);
         double norm = (value - colMin) / (colMax - colMin);
@@ -994,7 +966,7 @@
 
     if (m_normalizeHybrid && (colMax > 0.0)) {
         double logmax = log10(colMax);
-        for (int y = 0; y < values.size(); ++y) {
+        for (int y = 0; y < nv; ++y) {
             values[y] = float(values[y] * logmax);
         }
     }
@@ -1166,7 +1138,7 @@
             double colMax = 0.f, colMin = 0.f;
 
             for (int y = 0; y < cacheHeight; ++y) {
-                if (y >= values.size()) break;
+                if (!in_range_for(values, y)) break;
                 if (y == 0 || values[y] > colMax) colMax = values[y];
                 if (y == 0 || values[y] < colMin) colMin = values[y];
             }
@@ -1216,7 +1188,7 @@
         for (int y = 0; y < cacheHeight; ++y) {
 
             double value = min;
-            if (y < values.size()) {
+            if (in_range_for(values, y)) {
                 value = values.at(y);
             }
 
@@ -1283,7 +1255,7 @@
 }
 
 bool
-Colour3DPlotLayer::shouldPaintDenseIn(const View *v) const
+Colour3DPlotLayer::shouldPaintDenseIn(const LayerGeometryProvider *v) const
 {
     if (!m_model || !v || !(v->getViewManager())) {
         return false;
@@ -1292,7 +1264,7 @@
         v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate();
     if (m_opaque || 
         m_smooth ||
-        m_model->getHeight() >= v->height() ||
+        m_model->getHeight() >= v->getPaintHeight() ||
         ((m_model->getResolution() * srRatio) / v->getZoomLevel()) < 2) {
         return true;
     }
@@ -1300,7 +1272,7 @@
 }
 
 void
-Colour3DPlotLayer::paint(View *v, QPainter &paint, QRect rect) const
+Colour3DPlotLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
 /*
     if (m_model) {
@@ -1315,13 +1287,13 @@
     int completion = 0;
     if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) {
 	if (completion > 0) {
-	    paint.fillRect(0, 10, v->width() * completion / 100,
+	    paint.fillRect(0, 10, v->getPaintWidth() * completion / 100,
 			   10, QColor(120, 120, 120));
 	}
 	return;
     }
 
-    if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->rect();
+    if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->getPaintRect();
 
     sv_frame_t modelStart = m_model->getStartFrame();
     sv_frame_t modelEnd = m_model->getEndFrame();
@@ -1339,7 +1311,7 @@
     int x0 = rect.left();
     int x1 = rect.right() + 1;
 
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     double srRatio =
         v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate();
@@ -1461,7 +1433,7 @@
 }
 
 void
-Colour3DPlotLayer::paintDense(View *v, QPainter &paint, QRect rect) const
+Colour3DPlotLayer::paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     Profiler profiler("Colour3DPlotLayer::paintDense", true);
     if (!m_cache) return;
@@ -1477,7 +1449,7 @@
     int x1 = rect.right() + 1;
 
     const int w = x1 - x0; // const so it can be used as array size below
-    int h = v->height(); // we always paint full height
+    int h = v->getPaintHeight(); // we always paint full height
     int sh = m_model->getHeight();
 
     int symin = m_miny;
@@ -1692,7 +1664,7 @@
 }
 
 bool
-Colour3DPlotLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+Colour3DPlotLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				      int &resolution,
 				      SnapType snap) const
 {
--- a/layer/Colour3DPlotLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/Colour3DPlotLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -49,20 +49,20 @@
         return m_model ? m_model->getZoomConstraint() : 0;
     }
     virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame, 
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, 
 				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourHasMeaningfulValue;
@@ -70,7 +70,7 @@
 
     void setModel(const DenseThreeDimensionalModel *model);
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual PropertyList getProperties() const;
     virtual PropertyType getPropertyType(const PropertyName &) const;
@@ -139,9 +139,6 @@
     void setInvertVertical(bool i);
     bool getInvertVertical() const;
 
-    void setShowRectified(bool);
-    bool getShowRectified() const { return m_rectified; }
-
     void setOpaque(bool i);
     bool getOpaque() const;
 
@@ -154,7 +151,7 @@
     virtual bool getDisplayExtents(double &min, double &max) const;
     virtual bool setDisplayExtents(double min, double max);
 
-    virtual bool getYScaleValue(const View *, int /* y */,
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int /* y */,
                                 double &/* value */, QString &/* unit */) const;
 
     virtual int getVerticalZoomSteps(int &defaultStep) const;
@@ -190,7 +187,6 @@
     bool        m_normalizeVisibleArea;
     bool        m_normalizeHybrid;
     bool        m_invertVertical;
-    bool        m_rectified;
     bool        m_opaque;
     bool        m_smooth;
     int         m_peakResolution;
@@ -206,12 +202,12 @@
      * and the vertical scale is the usual way up). Bin number may be
      * fractional, to obtain a position part-way through a bin.
      */
-    double getYForBin(View *, double bin) const;
+    double getYForBin(LayerGeometryProvider *, double bin) const;
 
     /**
      * As getYForBin, but rounding to integer values.
      */
-    int getIYForBin(View *, int bin) const;
+    int getIYForBin(LayerGeometryProvider *, int bin) const;
     
     /**
      * Return the bin number, possibly fractional, at the given y
@@ -219,12 +215,12 @@
      * at which the bins "start" (i.e. the bottom of the visible bin,
      * if the vertical scale is the usual way up).
      */
-    double getBinForY(View *, double y) const;
+    double getBinForY(LayerGeometryProvider *, double y) const;
 
     /**
      * As getBinForY, but rounding to integer values.
      */
-    int getIBinForY(View *, int y) const;
+    int getIBinForY(LayerGeometryProvider *, int y) const;
     
     DenseThreeDimensionalModel::Column getColumn(int col) const;
 
@@ -233,11 +229,11 @@
      * are so small you can't see their borders. False for big,
      * translucent cells.
      */
-    bool shouldPaintDenseIn(const View *) const; 
+    bool shouldPaintDenseIn(const LayerGeometryProvider *) const; 
 
     int getColourScaleWidth(QPainter &) const;
     void fillCache(int firstBin, int lastBin) const;
-    void paintDense(View *v, QPainter &paint, QRect rect) const;
+    void paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 };
 
 #endif
--- a/layer/ColourMapper.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/ColourMapper.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -21,6 +21,44 @@
 
 #include "base/Debug.h"
 
+#include <vector>
+
+using namespace std;
+
+static vector<QColor> convertStrings(const vector<QString> &strs)
+{
+    vector<QColor> converted;
+    for (const auto &s: strs) converted.push_back(QColor(s));
+    reverse(converted.begin(), converted.end());
+    return converted;
+}
+
+static vector<QColor> ice = convertStrings({
+        // Based on ColorBrewer ylGnBu
+        "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5",
+        "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040"
+        });
+
+static vector<QColor> cherry = convertStrings({
+        "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497",
+        "#ae017e", "#7a0177", "#49006a"
+        });
+    
+static void
+mapDiscrete(double norm, vector<QColor> &colours, double &r, double &g, double &b)
+{
+    int n = int(colours.size());
+    double m = norm * (n-1);
+    if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; }
+    if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; }
+    int base(int(floor(m)));
+    double prop0 = (base + 1.0) - m, prop1 = m - base;
+    QColor c0(colours[base]), c1(colours[base+1]);
+    r = c0.redF() * prop0 + c1.redF() * prop1;
+    g = c0.greenF() * prop0 + c1.greenF() * prop1;
+    b = c0.blueF() * prop0 + c1.blueF() * prop1;
+}
+
 ColourMapper::ColourMapper(int map, double min, double max) :
     QObject(),
     m_map(map),
@@ -51,12 +89,12 @@
     StandardMap map = (StandardMap)n;
 
     switch (map) {
-    case DefaultColours:   return tr("Default");
+    case Green:            return tr("Green");
     case WhiteOnBlack:     return tr("White on Black");
     case BlackOnWhite:     return tr("Black on White");
-    case RedOnBlue:        return tr("Red on Blue");
-    case YellowOnBlack:    return tr("Yellow on Black");
-    case BlueOnBlack:      return tr("Blue on Black");
+    case Cherry:           return tr("Cherry");
+    case Wasp:             return tr("Wasp");
+    case Ice:              return tr("Ice");
     case Sunset:           return tr("Sunset");
     case FruitSalad:       return tr("Fruit Salad");
     case Banded:           return tr("Banded");
@@ -85,7 +123,7 @@
 
     switch (map) {
 
-    case DefaultColours:
+    case Green:
         h = blue - norm * 2.0 * pieslice;
         s = 0.5f + norm/2.0;
         v = norm;
@@ -101,30 +139,17 @@
         hsv = false;
         break;
 
-    case RedOnBlue:
-        h = blue - pieslice/4.0 + norm * (pieslice + pieslice/4.0);
-        s = 1.0;
-        v = norm;
+    case Cherry:
+        hsv = false;
+        mapDiscrete(norm, cherry, r, g, b);
         break;
 
-    case YellowOnBlack:
+    case Wasp:
         h = 0.15;
         s = 1.0;
         v = norm;
         break;
 
-    case BlueOnBlack:
-        h = blue;
-        s = 1.0;
-        v = norm * 2.0;
-        if (v > 1.0) {
-            v = 1.0;
-            s = 1.0 - (sqrt(norm) - 0.707) * 3.413;
-            if (s < 0.0) s = 0.0;
-            if (s > 1.0) s = 1.0;
-        }
-        break;
-
     case Sunset:
         r = (norm - 0.24) * 2.38;
         if (r > 1.0) r = 1.0;
@@ -207,6 +232,10 @@
         hsv = false;
 */
         break;
+
+    case Ice:
+        hsv = false;
+        mapDiscrete(norm, ice, r, g, b);
     }
 
     if (hsv) {
@@ -224,7 +253,7 @@
 
     switch (map) {
 
-    case DefaultColours:
+    case Green:
         return QColor(255, 150, 50);
 
     case WhiteOnBlack:
@@ -233,13 +262,13 @@
     case BlackOnWhite:
         return Qt::darkGreen;
 
-    case RedOnBlue:
+    case Cherry:
         return Qt::green;
 
-    case YellowOnBlack:
+    case Wasp:
         return QColor::fromHsv(240, 255, 255);
 
-    case BlueOnBlack:
+    case Ice:
         return Qt::red;
 
     case Sunset:
@@ -277,12 +306,12 @@
     case HighGain:
         return true;
 
-    case DefaultColours:
+    case Green:
     case Sunset:
     case WhiteOnBlack:
-    case RedOnBlue:
-    case YellowOnBlack:
-    case BlueOnBlack:
+    case Cherry:
+    case Wasp:
+    case Ice:
     case FruitSalad:
     case Banded:
     case Highlight:
--- a/layer/ColourMapper.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/ColourMapper.h	Wed Mar 02 17:25:27 2016 +0000
@@ -33,13 +33,13 @@
     virtual ~ColourMapper();
 
     enum StandardMap {
-        DefaultColours,
+        Green,
         Sunset,
         WhiteOnBlack,
         BlackOnWhite,
-        RedOnBlue,
-        YellowOnBlack,
-        BlueOnBlack,
+        Cherry,
+        Wasp,
+        Ice,
         FruitSalad,
         Banded,
         Highlight,
--- a/layer/ColourScaleLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/ColourScaleLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,11 +19,13 @@
 #include <QString>
 #include <QColor>
 
+class LayerGeometryProvider;
+
 class ColourScaleLayer
 {
 public:
     virtual QString getScaleUnits() const = 0;
-    virtual QColor getColourForValue(View *v, double value) const = 0;
+    virtual QColor getColourForValue(LayerGeometryProvider *v, double value) const = 0;
 };
 
 #endif
--- a/layer/FlexiNoteLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/FlexiNoteLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -204,7 +204,7 @@
 }
 
 bool
-FlexiNoteLayer::isLayerScrollable(const View *v) const
+FlexiNoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -405,7 +405,7 @@
 }
 
 FlexiNoteModel::PointList
-FlexiNoteLayer::getLocalPoints(View *v, int x) const
+FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return FlexiNoteModel::PointList();
 
@@ -448,7 +448,7 @@
 }
 
 bool
-FlexiNoteLayer::getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &p) const
+FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &p) const
 {
     if (!m_model) return false;
 
@@ -476,7 +476,7 @@
 }
 
 bool
-FlexiNoteLayer::getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &p) const
+FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &p) const
 {
     // GF: find the note that is closest to the cursor
     if (!m_model) return false;
@@ -505,7 +505,7 @@
 }
 
 QString
-FlexiNoteLayer::getFeatureDescription(View *v, QPoint &pos) const
+FlexiNoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -593,7 +593,7 @@
 }
 
 bool
-FlexiNoteLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+FlexiNoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
                                    int &resolution,
                                    SnapType snap) const
 {
@@ -673,7 +673,7 @@
 }
 
 void
-FlexiNoteLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const
+FlexiNoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -730,11 +730,11 @@
 }
 
 int
-FlexiNoteLayer::getYForValue(View *v, double val) const
+FlexiNoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -765,11 +765,11 @@
 }
 
 double
-FlexiNoteLayer::getValueForY(View *v, int y) const
+FlexiNoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -794,7 +794,7 @@
 }
 
 void
-FlexiNoteLayer::paint(View *v, QPainter &paint, QRect rect) const
+FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -860,8 +860,8 @@
                 !FlexiNoteModel::Point::Comparator()(illuminatePoint, p) &&
                 !FlexiNoteModel::Point::Comparator()(p, illuminatePoint)) {
 
-                paint.drawLine(x, -1, x, v->height() + 1);
-                paint.drawLine(x+w, -1, x+w, v->height() + 1);
+                paint.drawLine(x, -1, x, v->getPaintHeight() + 1);
+                paint.drawLine(x+w, -1, x+w, v->getPaintHeight() + 1);
         
                 paint.setPen(v->getForeground());
                 // paint.setBrush(v->getForeground());
@@ -904,7 +904,7 @@
 }
 
 int
-FlexiNoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || shouldAutoAlign()) {
         return 0;
@@ -918,7 +918,7 @@
 }
 
 void
-FlexiNoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
     if (!m_model || m_model->getPoints().empty()) return;
 
@@ -927,7 +927,7 @@
     bool logarithmic;
 
     int w = getVerticalScaleWidth(v, false, paint);
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -956,7 +956,7 @@
 }
 
 void
-FlexiNoteLayer::drawStart(View *v, QMouseEvent *e)
+FlexiNoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -980,7 +980,7 @@
 }
 
 void
-FlexiNoteLayer::drawDrag(View *v, QMouseEvent *e)
+FlexiNoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -1009,7 +1009,7 @@
 }
 
 void
-FlexiNoteLayer::drawEnd(View *, QMouseEvent *)
+FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -1019,7 +1019,7 @@
 }
 
 void
-FlexiNoteLayer::eraseStart(View *v, QMouseEvent *e)
+FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1034,12 +1034,12 @@
 }
 
 void
-FlexiNoteLayer::eraseDrag(View *, QMouseEvent *)
+FlexiNoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-FlexiNoteLayer::eraseEnd(View *v, QMouseEvent *e)
+FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1059,7 +1059,7 @@
 }
 
 void
-FlexiNoteLayer::editStart(View *v, QMouseEvent *e)
+FlexiNoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
@@ -1110,7 +1110,7 @@
 }
 
 void
-FlexiNoteLayer::editDrag(View *v, QMouseEvent *e)
+FlexiNoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
@@ -1190,7 +1190,7 @@
 }
 
 void
-FlexiNoteLayer::editEnd(View *v, QMouseEvent *e)
+FlexiNoteLayer::editEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
@@ -1229,7 +1229,7 @@
 }
 
 void
-FlexiNoteLayer::splitStart(View *v, QMouseEvent *e)
+FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     // GF: note splitting starts (!! remove printing soon)
     std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
@@ -1252,7 +1252,7 @@
 }
 
 void
-FlexiNoteLayer::splitEnd(View *v, QMouseEvent *e)
+FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     // GF: note splitting ends. (!! remove printing soon)
     std::cerr << "splitEnd" << std::endl;
@@ -1271,13 +1271,13 @@
 }
 
 void
-FlexiNoteLayer::splitNotesAt(View *v, sv_frame_t frame)
+FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame)
 {
     splitNotesAt(v, frame, 0);
 }
 
 void
-FlexiNoteLayer::splitNotesAt(View *v, sv_frame_t frame, QMouseEvent *e)
+FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
 {
     FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
     if (onPoints.empty()) return;
@@ -1317,7 +1317,7 @@
 }
 
 void
-FlexiNoteLayer::addNote(View *v, QMouseEvent *e)
+FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e)
 {
     std::cerr << "addNote" << std::endl;
     if (!m_model) return;
@@ -1356,14 +1356,14 @@
 }
 
 SparseTimeValueModel *
-FlexiNoteLayer::getAssociatedPitchModel(View *v) const
+FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const
 {
     // Better than we used to do, but still not very satisfactory
 
 //    cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
 
-    for (int i = 0; i < v->getLayerCount(); ++i) {
-        Layer *layer = v->getLayer(i);
+    for (int i = 0; i < v->getView()->getLayerCount(); ++i) {
+        Layer *layer = v->getView()->getLayer(i);
         if (layer &&
             layer->getLayerPresentationName() != "candidate") {
 //            cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
@@ -1381,7 +1381,7 @@
 }
 
 void
-FlexiNoteLayer::snapSelectedNotesToPitchTrack(View *v, Selection s)
+FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
 {
     if (!m_model) return;
 
@@ -1419,7 +1419,7 @@
 }
 
 void
-FlexiNoteLayer::mergeNotes(View *v, Selection s, bool inclusive)
+FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
 {
     FlexiNoteModel::PointList points =
         m_model->getPoints(s.getStartFrame(), s.getEndFrame());
@@ -1462,8 +1462,7 @@
 }
 
 bool
-FlexiNoteLayer::updateNoteValueFromPitchCurve(View *v,
-                                              FlexiNoteModel::Point &note) const
+FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const
 {
     SparseTimeValueModel *model = getAssociatedPitchModel(v);
     if (!model) return false;
@@ -1507,13 +1506,13 @@
 }
 
 void 
-FlexiNoteLayer::mouseMoveEvent(View *v, QMouseEvent *e)
+FlexiNoteLayer::mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *e)
 {
     // GF: context sensitive cursors
-    // v->setCursor(Qt::ArrowCursor);
+    // v->getView()->setCursor(Qt::ArrowCursor);
     FlexiNoteModel::Point note(0);
     if (!getNoteToEdit(v, e->x(), e->y(), note)) { 
-        // v->setCursor(Qt::UpArrowCursor);
+        // v->getView()->setCursor(Qt::UpArrowCursor);
         return; 
     }
 
@@ -1524,28 +1523,28 @@
                              closeToTop, closeToBottom);
     
     if (closeToLeft) {
-        v->setCursor(Qt::SizeHorCursor);
+        v->getView()->setCursor(Qt::SizeHorCursor);
         m_editMode = LeftBoundary;
         cerr << "edit mode -> LeftBoundary" << endl;
     } else if (closeToRight) {
-        v->setCursor(Qt::SizeHorCursor);
+        v->getView()->setCursor(Qt::SizeHorCursor);
         m_editMode = RightBoundary;
         cerr << "edit mode -> RightBoundary" << endl;
     } else if (closeToTop) {
-        v->setCursor(Qt::CrossCursor);
+        v->getView()->setCursor(Qt::CrossCursor);
         m_editMode = DragNote;
         cerr << "edit mode -> DragNote" << endl;
     } else if (closeToBottom) {
-        v->setCursor(Qt::UpArrowCursor);
+        v->getView()->setCursor(Qt::UpArrowCursor);
         m_editMode = SplitNote;
         cerr << "edit mode -> SplitNote" << endl;
     } else {
-        v->setCursor(Qt::ArrowCursor);
+        v->getView()->setCursor(Qt::ArrowCursor);
     }
 }
 
 void
-FlexiNoteLayer::getRelativeMousePosition(View *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
+FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
 {
     // GF: TODO: consoloidate the tolerance values
     if (!m_model) return;
@@ -1574,7 +1573,7 @@
 
 
 bool
-FlexiNoteLayer::editOpen(View *v, QMouseEvent *e)
+FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     std::cerr << "Opening note editor dialog" << std::endl;
     if (!m_model) return false;
@@ -1728,7 +1727,7 @@
 }
 
 void
-FlexiNoteLayer::copy(View *v, Selection s, Clipboard &to)
+FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1746,7 +1745,7 @@
 }
 
 bool
-FlexiNoteLayer::paste(View *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */)
+FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -1757,7 +1756,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), 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);
@@ -1894,7 +1893,7 @@
 }
 
 void
-FlexiNoteLayer::setVerticalRangeToNoteRange(View *v)
+FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v)
 {
     double minf = std::numeric_limits<double>::max();
     double maxf = 0;
@@ -1910,7 +1909,7 @@
     std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl;
     
     if (hasNotes) {
-        v->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); 
+        v->getView()->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); 
         // MM: this is a hack because we rely on 
         // * this layer being automatically aligned to layer 1
         // * layer one is a log frequency layer.
--- a/layer/FlexiNoteLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/FlexiNoteLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -38,50 +38,50 @@
 public:
     FlexiNoteLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
                     int &resolution,
                     SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void splitStart(View *v, QMouseEvent *);
-    virtual void splitEnd(View *v, QMouseEvent *);
+    virtual void splitStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void splitEnd(LayerGeometryProvider *v, QMouseEvent *);
     
-    virtual void addNote(View *v, QMouseEvent *e);
+    virtual void addNote(LayerGeometryProvider *v, QMouseEvent *e);
 
-    virtual void mouseMoveEvent(View *v, QMouseEvent *);
+    virtual void mouseMoveEvent(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
     virtual void deleteSelectionInclusive(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
-    void splitNotesAt(View *v, sv_frame_t frame);
-    void snapSelectedNotesToPitchTrack(View *v, Selection s);
-    void mergeNotes(View *v, Selection s, bool inclusive);
+    void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame);
+    void snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s);
+    void mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive);
 
     virtual const Model *getModel() const { return m_model; }
     void setModel(FlexiNoteModel *model);
@@ -116,11 +116,11 @@
     void setVerticalScale(VerticalScale scale);
     VerticalScale getVerticalScale() const { return m_verticalScale; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
@@ -156,11 +156,11 @@
 
     void setProperties(const QXmlAttributes &attributes);
     
-    void setVerticalRangeToNoteRange(View *v);
+    void setVerticalRangeToNoteRange(LayerGeometryProvider *v);
 
     /// VerticalScaleLayer methods
-    virtual int getYForValue(View *v, double value) const;
-    virtual double getValueForY(View *v, int y) const;
+    virtual int getYForValue(LayerGeometryProvider *v, double value) const;
+    virtual double getValueForY(LayerGeometryProvider *v, int y) const;
     virtual QString getScaleUnits() const;
 
 signals:
@@ -168,23 +168,19 @@
     void materialiseReAnalysis();
     
 protected:
-    void getScaleExtents(View *, double &min, double &max, bool &log) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
     bool shouldConvertMIDIToHz() const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    FlexiNoteModel::PointList getLocalPoints(View *v, int) const;
+    FlexiNoteModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
-    bool getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &) const;
-    bool getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &) const;
-    void getRelativeMousePosition(View *v, FlexiNoteModel::Point &note,
-                                  int x, int y,
-                                  bool &closeToLeft, bool &closeToRight,
-                                  bool &closeToTop, bool &closeToBottom) const;
-    SparseTimeValueModel *getAssociatedPitchModel(View *v) const;
-    bool updateNoteValueFromPitchCurve(View *v,
-                                       FlexiNoteModel::Point &note) const;
-    void splitNotesAt(View *v, sv_frame_t frame, QMouseEvent *e);
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const;
+    bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const;
+    void getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const;
+    SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const;
+    bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const;
+    void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e);
 
     FlexiNoteModel *m_model;
     bool m_editing;
--- a/layer/ImageLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/ImageLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -116,14 +116,14 @@
 }
 
 bool
-ImageLayer::isLayerScrollable(const View *) const
+ImageLayer::isLayerScrollable(const LayerGeometryProvider *) const
 {
     return true;
 }
 
 
 ImageModel::PointList
-ImageLayer::getLocalPoints(View *v, int x, int ) const
+ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const
 {
     if (!m_model) return ImageModel::PointList();
 
@@ -169,7 +169,7 @@
 }
 
 QString
-ImageLayer::getFeatureDescription(View *v, QPoint &pos) const
+ImageLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -208,7 +208,7 @@
 //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer
 
 bool
-ImageLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+ImageLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 			      int &resolution,
 			      SnapType snap) const
 {
@@ -280,7 +280,7 @@
 }
 
 void
-ImageLayer::paint(View *v, QPainter &paint, QRect rect) const
+ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -290,7 +290,7 @@
 //    Profiler profiler("ImageLayer::paint", true);
 
 //    int x0 = rect.left(), x1 = rect.right();
-    int x0 = 0, x1 = v->width();
+    int x0 = 0, x1 = v->getPaintWidth();
 
     sv_frame_t frame0 = v->getFrameForX(x0);
     sv_frame_t frame1 = v->getFrameForX(x1);
@@ -299,7 +299,7 @@
     if (points.empty()) return;
 
     paint.save();
-    paint.setClipRect(rect.x(), 0, rect.width(), v->height());
+    paint.setClipRect(rect.x(), 0, rect.width(), v->getPaintHeight());
 
     QColor penColour;
     penColour = v->getForeground();
@@ -338,7 +338,7 @@
 }
 
 void
-ImageLayer::drawImage(View *v, QPainter &paint, const ImageModel::Point &p,
+ImageLayer::drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p,
                       int x, int nx) const
 {
     QString label = p.label;
@@ -358,12 +358,12 @@
     int bottomMargin = 10;
     int spacing = 5;
 
-    if (v->height() < 100) {
+    if (v->getPaintHeight() < 100) {
         topMargin = 5;
         bottomMargin = 5;
     }
 
-    int maxBoxHeight = v->height() - topMargin - bottomMargin;
+    int maxBoxHeight = v->getPaintHeight() - topMargin - bottomMargin;
 
     int availableWidth = nx - x - 3;
     if (availableWidth < 20) availableWidth = 20;
@@ -372,7 +372,7 @@
 
     if (label != "") {
 
-        int likelyHeight = v->height() / 4;
+        int likelyHeight = v->getPaintHeight() / 4;
 
         int likelyWidth = // available height times image aspect
             ((maxBoxHeight - likelyHeight) * imageSize.width())
@@ -435,10 +435,10 @@
         division += paint.fontMetrics().height();
     }                
 
-    bottomMargin = v->height() - topMargin - boxHeight;
-    if (bottomMargin > topMargin + v->height()/7) {
-        topMargin += v->height()/8;
-        bottomMargin -= v->height()/8;
+    bottomMargin = v->getPaintHeight() - topMargin - boxHeight;
+    if (bottomMargin > topMargin + v->getPaintHeight()/7) {
+        topMargin += v->getPaintHeight()/8;
+        bottomMargin -= v->getPaintHeight()/8;
     }
 
     paint.drawRect(x - 1,
@@ -480,7 +480,7 @@
 }
 
 void
-ImageLayer::setLayerDormant(const View *v, bool dormant)
+ImageLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     if (dormant) {
         // Delete the images named in the view's scaled map from the
@@ -517,7 +517,7 @@
 }
 
 QImage 
-ImageLayer::getImage(View *v, QString name, QSize maxSize) const
+ImageLayer::getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const
 {
 //    SVDEBUG << "ImageLayer::getImage(" << v << ", " << name << ", ("
 //              << maxSize.width() << "x" << maxSize.height() << "))" << endl;
@@ -554,7 +554,7 @@
 }
 
 void
-ImageLayer::drawStart(View *v, QMouseEvent *e)
+ImageLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -578,7 +578,7 @@
 }
 
 void
-ImageLayer::drawDrag(View *v, QMouseEvent *e)
+ImageLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -594,7 +594,7 @@
 }
 
 void
-ImageLayer::drawEnd(View *, QMouseEvent *)
+ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -638,7 +638,7 @@
 }
 
 void
-ImageLayer::editStart(View *v, QMouseEvent *e)
+ImageLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -660,7 +660,7 @@
 }
 
 void
-ImageLayer::editDrag(View *v, QMouseEvent *e)
+ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -680,7 +680,7 @@
 }
 
 void
-ImageLayer::editEnd(View *, QMouseEvent *)
+ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -694,7 +694,7 @@
 }
 
 bool
-ImageLayer::editOpen(View *v, QMouseEvent *e)
+ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -801,7 +801,7 @@
 }
 
 void
-ImageLayer::copy(View *v, Selection s, Clipboard &to)
+ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -819,7 +819,7 @@
 }
 
 bool
-ImageLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
+ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -830,7 +830,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), 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);
--- a/layer/ImageLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/ImageLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -38,31 +38,31 @@
     ImageLayer();
     virtual ~ImageLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
-    virtual bool editOpen(View *, QMouseEvent *); // on double-click
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); // on double-click
 
     virtual const Model *getModel() const { return m_model; }
     void setModel(ImageModel *model);
@@ -80,11 +80,11 @@
         return ColourAbsent;
     }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
@@ -92,9 +92,9 @@
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
     void setProperties(const QXmlAttributes &attributes);
 
@@ -105,18 +105,18 @@
     void fileSourceReady();
 
 protected:
-    ImageModel::PointList getLocalPoints(View *v, int x, int y) const;
+    ImageModel::PointList getLocalPoints(LayerGeometryProvider *v, int x, int y) const;
 
     bool getImageOriginalSize(QString name, QSize &size) const;
-    QImage getImage(View *v, QString name, QSize maxSize) const;
+    QImage getImage(LayerGeometryProvider *v, QString name, QSize maxSize) const;
 
-    void drawImage(View *v, QPainter &paint, const ImageModel::Point &p,
+    void drawImage(LayerGeometryProvider *v, QPainter &paint, const ImageModel::Point &p,
                    int x, int nx) const;
 
     //!!! how to reap no-longer-used images?
 
     typedef std::map<QString, QImage> ImageMap;
-    typedef std::map<const View *, ImageMap> ViewImageMap;
+    typedef std::map<const LayerGeometryProvider *, ImageMap> ViewImageMap;
     typedef std::map<QString, FileSource *> FileSourceMap;
 
     static ImageMap m_images;
--- a/layer/Layer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/Layer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -115,7 +115,7 @@
 }
 
 void
-Layer::setLayerDormant(const View *v, bool dormant)
+Layer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     const void *vv = (const void *)v;
     QMutexLocker locker(&m_dormancyMutex);
@@ -123,7 +123,7 @@
 }
 
 bool
-Layer::isLayerDormant(const View *v) const
+Layer::isLayerDormant(const LayerGeometryProvider *v) const
 {
     const void *vv = (const void *)v;
     QMutexLocker locker(&m_dormancyMutex);
@@ -132,14 +132,14 @@
 }
 
 void
-Layer::showLayer(View *view, bool show)
+Layer::showLayer(LayerGeometryProvider *view, bool show)
 {
     setLayerDormant(view, !show);
     emit layerParametersChanged();
 }
 
 bool
-Layer::getXScaleValue(const View *v, int x, double &value, QString &unit) const
+Layer::getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const
 {
     if (!hasTimeXAxis()) return false;
 
@@ -152,7 +152,7 @@
 }
 
 bool
-Layer::getYScaleDifference(const View *v, int y0, int y1,
+Layer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
                            double &diff, QString &unit) const
 {
     double v0, v1;
@@ -166,31 +166,31 @@
 }
 
 sv_frame_t
-Layer::alignToReference(View *v, sv_frame_t frame) const
+Layer::alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
     const Model *m = getModel();
     SVDEBUG << "Layer::alignToReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << endl;
     if (m && m->getAlignmentReference()) {
         return m->alignToReference(frame);
     } else {
-        return v->alignToReference(frame);
+        return v->getView()->alignToReference(frame);
     }
 }
 
 sv_frame_t
-Layer::alignFromReference(View *v, sv_frame_t frame) const
+Layer::alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
     const Model *m = getModel();
     SVDEBUG << "Layer::alignFromReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : 0) << endl;
     if (m && m->getAlignmentReference()) {
         return m->alignFromReference(frame);
     } else {
-        return v->alignFromReference(frame);
+        return v->getView()->alignFromReference(frame);
     }
 }
 
 bool
-Layer::clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const
+Layer::clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const
 {
     // Notes on pasting to an aligned layer:
     // 
@@ -371,7 +371,7 @@
 }
 
 void
-Layer::measureStart(View *v, QMouseEvent *e)
+Layer::measureStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     setMeasureRectFromPixrect(v, m_draggingRect,
                               QRect(e->x(), e->y(), 0, 0));
@@ -379,7 +379,7 @@
 }
 
 void
-Layer::measureDrag(View *v, QMouseEvent *e)
+Layer::measureDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_haveDraggingRect) return;
 
@@ -391,7 +391,7 @@
 }
 
 void
-Layer::measureEnd(View *v, QMouseEvent *e)
+Layer::measureEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_haveDraggingRect) return;
     measureDrag(v, e);
@@ -405,7 +405,7 @@
 }
 
 void
-Layer::measureDoubleClick(View *, QMouseEvent *)
+Layer::measureDoubleClick(LayerGeometryProvider *, QMouseEvent *)
 {
     // nothing, in the base class
 }
@@ -425,7 +425,7 @@
 }
 
 void
-Layer::paintMeasurementRects(View *v, QPainter &paint,
+Layer::paintMeasurementRects(LayerGeometryProvider *v, QPainter &paint,
                              bool showFocus, QPoint focusPoint) const
 {
     updateMeasurePixrects(v);
@@ -457,7 +457,7 @@
 }
 
 bool
-Layer::nearestMeasurementRectChanged(View *v, QPoint prev, QPoint now) const
+Layer::nearestMeasurementRectChanged(LayerGeometryProvider *v, QPoint prev, QPoint now) const
 {
     updateMeasurePixrects(v);
     
@@ -468,7 +468,7 @@
 }
 
 void
-Layer::updateMeasurePixrects(View *v) const
+Layer::updateMeasurePixrects(LayerGeometryProvider *v) const
 {
     sv_frame_t sf = v->getStartFrame();
     sv_frame_t ef = v->getEndFrame();
@@ -507,26 +507,26 @@
 }
 
 void
-Layer::updateMeasureRectYCoords(View *v, const MeasureRect &r) const
+Layer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
 {
-    int y0 = int(lrint(r.startY * v->height()));
-    int y1 = int(lrint(r.endY * v->height()));
+    int y0 = int(lrint(r.startY * v->getPaintHeight()));
+    int y1 = int(lrint(r.endY * v->getPaintHeight()));
     r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0);
 }
 
 void
-Layer::setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const
+Layer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
 {
     if (start) {
-        r.startY = double(y) / double(v->height());
+        r.startY = double(y) / double(v->getPaintHeight());
         r.endY = r.startY;
     } else {
-        r.endY = double(y) / double(v->height());
+        r.endY = double(y) / double(v->getPaintHeight());
     }
 }
 
 void
-Layer::setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const
+Layer::setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const
 {
     r.pixrect = pixrect;
     r.haveFrames = hasTimeXAxis();
@@ -566,13 +566,13 @@
 }
 
 void
-Layer::paintMeasurementRect(View *v, QPainter &paint,
+Layer::paintMeasurementRect(LayerGeometryProvider *v, QPainter &paint,
                             const MeasureRect &r, bool focus) const
 {
     if (r.haveFrames) {
         
         int x0 = -1;
-        int x1 = v->width() + 1;
+        int x1 = v->getPaintWidth() + 1;
         
         if (r.startFrame >= v->getStartFrame()) {
             x0 = v->getXForFrame(r.startFrame);
--- a/layer/Layer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/Layer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -39,6 +39,7 @@
 class Model;
 class QPainter;
 class View;
+class LayerGeometryProvider;
 class QMouseEvent;
 class Clipboard;
 class RangeMapper;
@@ -83,12 +84,13 @@
     /**
      * Paint the given rectangle of this layer onto the given view
      * using the given painter, superimposing it on top of any
-     * existing material in that view.  The view is provided here
-     * because it is possible for one layer to exist in more than one
-     * view, so the dimensions of the view may vary from one paint
-     * call to another (without any view having been resized).
+     * existing material in that view.  The LayerGeometryProvider (an
+     * interface implemented by View) is provided here because it is
+     * possible for one layer to exist in more than one view, so the
+     * dimensions of the view may vary from one paint call to another
+     * (without any view having been resized).
      */
-    virtual void paint(View *, QPainter &, QRect) const = 0;   
+    virtual void paint(LayerGeometryProvider *, QPainter &, QRect) const = 0;   
 
     /**
      * Enable or disable synchronous painting.  If synchronous
@@ -128,25 +130,25 @@
     virtual QString getLayerPresentationName() const;
     virtual QPixmap getLayerPresentationPixmap(QSize) const { return QPixmap(); }
 
-    virtual int getVerticalScaleWidth(View *, bool detailed,
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool detailed,
                                       QPainter &) const = 0;
 
-    virtual void paintVerticalScale(View *, bool /* detailed */,
+    virtual void paintVerticalScale(LayerGeometryProvider *, bool /* detailed */,
                                     QPainter &, QRect) const { }
 
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */,
+    virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint /* cursorPos */,
                                      std::vector<QRect> &) const {
         return false;
     }
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const { }
+    virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const { }
 
-    virtual void paintMeasurementRects(View *, QPainter &,
+    virtual void paintMeasurementRects(LayerGeometryProvider *, QPainter &,
                                        bool showFocus, QPoint focusPoint) const;
 
-    virtual bool nearestMeasurementRectChanged(View *, QPoint prev,
+    virtual bool nearestMeasurementRectChanged(LayerGeometryProvider *, QPoint prev,
                                                QPoint now) const;
 
-    virtual QString getFeatureDescription(View *, QPoint &) const {
+    virtual QString getFeatureDescription(LayerGeometryProvider *, QPoint &) const {
 	return "";
     }
 
@@ -180,7 +182,7 @@
      * (and leave frame unmodified).  If returning true, also return
      * the resolution of the model in this layer in sample frames.
      */
-    virtual bool snapToFeatureFrame(View * /* v */,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider * /* v */,
 				    sv_frame_t & /* frame */,
 				    int &resolution,
 				    SnapType /* snap */) const {
@@ -204,7 +206,7 @@
      * (and leave frame unmodified).  If returning true, also return
      * the resolution of the model in this layer in sample frames.
      */
-    virtual bool snapToSimilarFeature(View * /* v */,
+    virtual bool snapToSimilarFeature(LayerGeometryProvider * /* v */,
                                       sv_frame_t & /* source frame */,
                                       int &resolution,
                                       SnapType /* snap */) const {
@@ -217,30 +219,30 @@
     // Layer needs to get actual mouse events, I guess.  Draw mode is
     // probably the easier.
 
-    virtual void drawStart(View *, QMouseEvent *) { }
-    virtual void drawDrag(View *, QMouseEvent *) { }
-    virtual void drawEnd(View *, QMouseEvent *) { }
+    virtual void drawStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void drawDrag(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void drawEnd(LayerGeometryProvider *, QMouseEvent *) { }
 
-    virtual void eraseStart(View *, QMouseEvent *) { }
-    virtual void eraseDrag(View *, QMouseEvent *) { }
-    virtual void eraseEnd(View *, QMouseEvent *) { }
+    virtual void eraseStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void eraseDrag(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void eraseEnd(LayerGeometryProvider *, QMouseEvent *) { }
 
-    virtual void editStart(View *, QMouseEvent *) { }
-    virtual void editDrag(View *, QMouseEvent *) { }
-    virtual void editEnd(View *, QMouseEvent *) { }
+    virtual void editStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void editDrag(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void editEnd(LayerGeometryProvider *, QMouseEvent *) { }
 
-    virtual void splitStart(View *, QMouseEvent *) { }
-    virtual void splitEnd(View *, QMouseEvent *) { }
-    virtual void addNote(View *, QMouseEvent *) { };
+    virtual void splitStart(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void splitEnd(LayerGeometryProvider *, QMouseEvent *) { }
+    virtual void addNote(LayerGeometryProvider *, QMouseEvent *) { };
 
     // Measurement rectangle (or equivalent).  Unlike draw and edit,
     // the base Layer class can provide working implementations of
     // these for most situations.
     //
-    virtual void measureStart(View *, QMouseEvent *);
-    virtual void measureDrag(View *, QMouseEvent *);
-    virtual void measureEnd(View *, QMouseEvent *);
-    virtual void measureDoubleClick(View *, QMouseEvent *);
+    virtual void measureStart(LayerGeometryProvider *, QMouseEvent *);
+    virtual void measureDrag(LayerGeometryProvider *, QMouseEvent *);
+    virtual void measureEnd(LayerGeometryProvider *, QMouseEvent *);
+    virtual void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *);
 
     virtual bool haveCurrentMeasureRect() const {
         return m_haveCurrentMeasureRect;
@@ -252,13 +254,13 @@
      * double-click).  If there is no item or editing is not
      * supported, return false.
      */
-    virtual bool editOpen(View *, QMouseEvent *) { return false; }
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *) { return false; }
 
     virtual void moveSelection(Selection, sv_frame_t /* newStartFrame */) { }
     virtual void resizeSelection(Selection, Selection /* newSize */) { }
     virtual void deleteSelection(Selection) { }
 
-    virtual void copy(View *, Selection, Clipboard & /* to */) { }
+    virtual void copy(LayerGeometryProvider *, Selection, Clipboard & /* to */) { }
 
     /**
      * Paste from the given clipboard onto the layer at the given
@@ -267,7 +269,7 @@
      * return false if the user cancelled the paste operation.  This
      * function should return true if a paste actually occurred.
      */
-    virtual bool paste(View *,
+    virtual bool paste(LayerGeometryProvider *,
                        const Clipboard & /* from */,
                        sv_frame_t /* frameOffset */,
                        bool /* interactive */) { return false; }
@@ -289,7 +291,7 @@
      * scrolling better if it is known that individual views can be
      * scrolled safely in this way.
      */
-    virtual bool isLayerScrollable(const View *) const { return true; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return true; }
 
     /**
      * This should return true if the layer completely obscures any
@@ -344,14 +346,14 @@
      * isReady(int *) call.  The view may choose to show a progress
      * meter if it finds that this returns < 100 at any given moment.
      */
-    virtual int getCompletion(View *) const { return 100; }
+    virtual int getCompletion(LayerGeometryProvider *) const { return 100; }
 
     /**
      * Return an error string if any errors have occurred while
      * loading or processing data for the given view.  Return the
      * empty string if no error has occurred.
      */
-    virtual QString getError(View *) const { return ""; }
+    virtual QString getError(LayerGeometryProvider *) const { return ""; }
 
     virtual void setObjectName(const QString &name);
 
@@ -400,13 +402,13 @@
      * A layer class that overrides this function must also call this
      * class's implementation.
      */
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
     /**
      * Return whether the layer is dormant (i.e. hidden) in the given
      * view.
      */
-    virtual bool isLayerDormant(const View *v) const;
+    virtual bool isLayerDormant(const LayerGeometryProvider *v) const;
 
     virtual PlayParameters *getPlayParameters();
 
@@ -457,14 +459,14 @@
      * measurement tool.  The default implementation works correctly
      * if the layer hasTimeXAxis().
      */
-    virtual bool getXScaleValue(const View *v, int x,
+    virtual bool getXScaleValue(const LayerGeometryProvider *v, int x,
                                 double &value, QString &unit) const;
 
     /** 
      * Return the value and unit at the given y coordinate in the
      * given view.
      */
-    virtual bool getYScaleValue(const View *, int /* y */,
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int /* y */,
                                 double &/* value */, QString &/* unit */) const {
         return false;
     }
@@ -475,7 +477,7 @@
      * The default implementation just calls getYScaleValue twice and
      * returns the difference, with the same unit.
      */
-    virtual bool getYScaleDifference(const View *v, int y0, int y1,
+    virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
                                      double &diff, QString &unit) const;
         
     /**
@@ -526,7 +528,7 @@
     virtual bool canExistWithoutModel() const { return false; }
 
 public slots:
-    void showLayer(View *, bool show);
+    void showLayer(LayerGeometryProvider *, bool show);
 
 signals:
     void modelChanged();
@@ -545,9 +547,9 @@
 protected:
     void connectSignals(const Model *);
 
-    virtual sv_frame_t alignToReference(View *v, sv_frame_t frame) const;
-    virtual sv_frame_t alignFromReference(View *v, sv_frame_t frame) const;
-    bool clipboardHasDifferentAlignment(View *v, const Clipboard &clip) const;
+    virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const;
+    virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const;
+    bool clipboardHasDifferentAlignment(LayerGeometryProvider *v, const Clipboard &clip) const;
 
     struct MeasureRect {
 
@@ -612,16 +614,16 @@
     // Note that pixrects are only correct for a single view.
     // So we should update them at the start of the paint procedure
     // (painting is single threaded) and only use them after that.
-    void updateMeasurePixrects(View *v) const;
+    void updateMeasurePixrects(LayerGeometryProvider *v) const;
 
-    virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const;
-    virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const;
-    virtual void setMeasureRectFromPixrect(View *v, MeasureRect &r, QRect pixrect) const;
+    virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const;
+    virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const;
+    virtual void setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const;
 
     // This assumes updateMeasurementPixrects has been called
     MeasureRectSet::const_iterator findFocusedMeasureRect(QPoint) const;
 
-    void paintMeasurementRect(View *v, QPainter &paint,
+    void paintMeasurementRect(LayerGeometryProvider *v, QPainter &paint,
                               const MeasureRect &r, bool focus) const;
 
     QString m_presentationName;
--- a/layer/LayerFactory.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LayerFactory.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -289,6 +289,7 @@
     if (name == "timeruler") return TimeRuler;
     if (name == "timeinstants") return TimeInstants;
     if (name == "timevalues") return TimeValues;
+    if (name == "notes") return Notes;
     if (name == "flexinotes") return FlexiNotes;
     if (name == "regions") return Regions;
     if (name == "text") return Text;
--- a/layer/LinearColourScale.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LinearColourScale.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -20,24 +20,24 @@
 
 #include <cmath>
 
-#include "view/View.h"
+#include "view/LayerGeometryProvider.h"
 
 int
-LinearColourScale::getWidth(View *,
+LinearColourScale::getWidth(LayerGeometryProvider *,
 			    QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 15;
 }
 
 void
-LinearColourScale::paintVertical(View *v,
+LinearColourScale::paintVertical(LayerGeometryProvider *v,
 				 const ColourScaleLayer *layer,
 				 QPainter &paint,
 				 int /* x0 */,
 				 double min,
 				 double max)
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     int n = 10;
 
--- a/layer/LinearColourScale.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LinearColourScale.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,16 +19,16 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class ColourScaleLayer;
 
 class LinearColourScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
+    (LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
      double minf, double maxf);
 };
 
--- a/layer/LinearNumericalScale.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LinearNumericalScale.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -23,14 +23,14 @@
 #include "view/View.h"
 
 int
-LinearNumericalScale::getWidth(View *,
+LinearNumericalScale::getWidth(LayerGeometryProvider *,
 			       QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 10;
 }
 
 void
-LinearNumericalScale::paintVertical(View *v,
+LinearNumericalScale::paintVertical(LayerGeometryProvider *v,
 				    const VerticalScaleLayer *layer,
 				    QPainter &paint,
 				    int x0,
@@ -69,7 +69,7 @@
         double dispval = val;
 
 	if (i == n-1 &&
-	    v->height() < paint.fontMetrics().height() * (n*2)) {
+	    v->getPaintHeight() < paint.fontMetrics().height() * (n*2)) {
 	    if (layer->getScaleUnits() != "") drawText = false;
 	}
 	dispval = int(rint(val / round) * round);
--- a/layer/LinearNumericalScale.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LinearNumericalScale.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,17 +19,17 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class VerticalScaleLayer;
 
 class LinearNumericalScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const VerticalScaleLayer *layer, QPainter &paint, int x0,
-     double minf, double maxf);
+    (LayerGeometryProvider *v, const VerticalScaleLayer *layer,
+     QPainter &paint, int x0, double minf, double maxf);
 };
 
 #endif
--- a/layer/LogColourScale.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LogColourScale.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -25,21 +25,21 @@
 #include "view/View.h"
 
 int
-LogColourScale::getWidth(View *,
+LogColourScale::getWidth(LayerGeometryProvider *,
 			    QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 15;
 }
 
 void
-LogColourScale::paintVertical(View *v,
+LogColourScale::paintVertical(LayerGeometryProvider *v,
 			      const ColourScaleLayer *layer,
 			      QPainter &paint,
 			      int /* x0 */,
 			      double minlog,
 			      double maxlog)
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     int n = 10;
 
--- a/layer/LogColourScale.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LogColourScale.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,16 +19,16 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class ColourScaleLayer;
 
 class LogColourScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
+    (LayerGeometryProvider *v, const ColourScaleLayer *layer, QPainter &paint, int x0,
      double minf, double maxf);
 };
 
--- a/layer/LogNumericalScale.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LogNumericalScale.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -27,14 +27,14 @@
 //#define DEBUG_TIME_VALUE_LAYER 1
 
 int
-LogNumericalScale::getWidth(View *,
+LogNumericalScale::getWidth(LayerGeometryProvider *,
 			    QPainter &paint)
 {
     return paint.fontMetrics().width("-000.00") + 10;
 }
 
 void
-LogNumericalScale::paintVertical(View *v,
+LogNumericalScale::paintVertical(LayerGeometryProvider *v,
 				 const VerticalScaleLayer *layer,
 				 QPainter &paint,
 				 int x0,
@@ -79,7 +79,7 @@
         bool drawText = true;
 
 	if (i == n-1 &&
-	    v->height() < paint.fontMetrics().height() * (n*2)) {
+	    v->getPaintHeight() < paint.fontMetrics().height() * (n*2)) {
 	    if (layer->getScaleUnits() != "") drawText = false;
 	}
 
--- a/layer/LogNumericalScale.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/LogNumericalScale.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,16 +19,16 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 class VerticalScaleLayer;
 
 class LogNumericalScale
 {
 public:
-    int getWidth(View *v, QPainter &paint);
+    int getWidth(LayerGeometryProvider *v, QPainter &paint);
 
     void paintVertical
-    (View *v, const VerticalScaleLayer *layer, QPainter &paint, int x0,
+    (LayerGeometryProvider *v, const VerticalScaleLayer *layer, QPainter &paint, int x0,
      double minlog, double maxlog);
 };
 
--- a/layer/NoteLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/NoteLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -191,7 +191,7 @@
 }
 
 bool
-NoteLayer::isLayerScrollable(const View *v) const
+NoteLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -389,7 +389,7 @@
 }
 
 NoteModel::PointList
-NoteLayer::getLocalPoints(View *v, int x) const
+NoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return NoteModel::PointList();
 
@@ -432,7 +432,7 @@
 }
 
 bool
-NoteLayer::getPointToDrag(View *v, int x, int y, NoteModel::Point &p) const
+NoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, NoteModel::Point &p) const
 {
     if (!m_model) return false;
 
@@ -460,7 +460,7 @@
 }
 
 QString
-NoteLayer::getFeatureDescription(View *v, QPoint &pos) const
+NoteLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -547,7 +547,7 @@
 }
 
 bool
-NoteLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+NoteLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 			      int &resolution,
 			      SnapType snap) const
 {
@@ -619,7 +619,7 @@
 }
 
 void
-NoteLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const
+NoteLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -677,11 +677,11 @@
 }
 
 int
-NoteLayer::getYForValue(View *v, double val) const
+NoteLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -712,11 +712,11 @@
 }
 
 double
-NoteLayer::getValueForY(View *v, int y) const
+NoteLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -741,7 +741,7 @@
 }
 
 void
-NoteLayer::paint(View *v, QPainter &paint, QRect rect) const
+NoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -830,7 +830,7 @@
 }
 
 int
-NoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || shouldAutoAlign()) {
         return 0;
@@ -844,7 +844,7 @@
 }
 
 void
-NoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
     if (!m_model || m_model->getPoints().empty()) return;
 
@@ -853,7 +853,7 @@
     bool logarithmic;
 
     int w = getVerticalScaleWidth(v, false, paint);
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -882,7 +882,7 @@
 }
 
 void
-NoteLayer::drawStart(View *v, QMouseEvent *e)
+NoteLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -906,7 +906,7 @@
 }
 
 void
-NoteLayer::drawDrag(View *v, QMouseEvent *e)
+NoteLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -935,7 +935,7 @@
 }
 
 void
-NoteLayer::drawEnd(View *, QMouseEvent *)
+NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -945,7 +945,7 @@
 }
 
 void
-NoteLayer::eraseStart(View *v, QMouseEvent *e)
+NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -960,12 +960,12 @@
 }
 
 void
-NoteLayer::eraseDrag(View *, QMouseEvent *)
+NoteLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-NoteLayer::eraseEnd(View *v, QMouseEvent *e)
+NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -985,7 +985,7 @@
 }
 
 void
-NoteLayer::editStart(View *v, QMouseEvent *e)
+NoteLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -1008,7 +1008,7 @@
 }
 
 void
-NoteLayer::editDrag(View *v, QMouseEvent *e)
+NoteLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -1037,7 +1037,7 @@
 }
 
 void
-NoteLayer::editEnd(View *, QMouseEvent *)
+NoteLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -1065,7 +1065,7 @@
 }
 
 bool
-NoteLayer::editOpen(View *v, QMouseEvent *e)
+NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -1193,7 +1193,7 @@
 }    
 
 void
-NoteLayer::copy(View *v, Selection s, Clipboard &to)
+NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1211,7 +1211,7 @@
 }
 
 bool
-NoteLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
+NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -1222,7 +1222,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), 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);
--- a/layer/NoteLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/NoteLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -35,37 +35,37 @@
 public:
     NoteLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -91,11 +91,11 @@
     void setVerticalScale(VerticalScale scale);
     VerticalScale getVerticalScale() const { return m_verticalScale; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
@@ -132,19 +132,19 @@
     void setProperties(const QXmlAttributes &attributes);
 
     /// VerticalScaleLayer methods
-    virtual int getYForValue(View *v, double value) const;
-    virtual double getValueForY(View *v, int y) const;
+    virtual int getYForValue(LayerGeometryProvider *v, double value) const;
+    virtual double getValueForY(LayerGeometryProvider *v, int y) const;
     virtual QString getScaleUnits() const;
 
 protected:
-    void getScaleExtents(View *, double &min, double &max, bool &log) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
     bool shouldConvertMIDIToHz() const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    NoteModel::PointList getLocalPoints(View *v, int) const;
+    NoteModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
-    bool getPointToDrag(View *v, int x, int y, NoteModel::Point &) const;
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, NoteModel::Point &) const;
 
     NoteModel *m_model;
     bool m_editing;
--- a/layer/PianoScale.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/PianoScale.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -21,10 +21,10 @@
 
 #include "base/Pitch.h"
 
-#include "view/View.h"
+#include "view/LayerGeometryProvider.h"
 
 void
-PianoScale::paintPianoVertical(View *v,
+PianoScale::paintPianoVertical(LayerGeometryProvider *v,
 			       QPainter &paint,
 			       QRect r,
 			       double minf,
--- a/layer/PianoScale.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/PianoScale.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,13 +19,13 @@
 #include <QRect>
 
 class QPainter;
-class View;
+class LayerGeometryProvider;
 
 class PianoScale
 {
 public:
     void paintPianoVertical
-    (View *v, QPainter &paint, QRect rect, double minf, double maxf);
+    (LayerGeometryProvider *v, QPainter &paint, QRect rect, double minf, double maxf);
 };
 
 #endif
--- a/layer/RegionLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/RegionLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -244,7 +244,7 @@
 }
 
 bool
-RegionLayer::isLayerScrollable(const View *v) const
+RegionLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -302,11 +302,11 @@
 }
 
 RegionModel::PointList
-RegionLayer::getLocalPoints(View *v, int x) const
+RegionLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return RegionModel::PointList();
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     RegionModel::PointList onPoints =
 	m_model->getPoints(frame);
@@ -345,11 +345,11 @@
 }
 
 bool
-RegionLayer::getPointToDrag(View *v, int x, int y, RegionModel::Point &p) const
+RegionLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, RegionModel::Point &p) const
 {
     if (!m_model) return false;
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     RegionModel::PointList onPoints = m_model->getPoints(frame);
     if (onPoints.empty()) return false;
@@ -383,7 +383,7 @@
 }
 
 QString
-RegionLayer::getFeatureDescription(View *v, QPoint &pos) const
+RegionLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -453,7 +453,7 @@
 }
 
 bool
-RegionLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+RegionLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
                                 int &resolution,
                                 SnapType snap) const
 {
@@ -536,7 +536,7 @@
 }
 
 bool
-RegionLayer::snapToSimilarFeature(View *v, sv_frame_t &frame,
+RegionLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
                                   int &resolution,
                                   SnapType snap) const
 {
@@ -624,7 +624,7 @@
 }
 
 void
-RegionLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const
+RegionLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -676,9 +676,9 @@
 }
 
 int
-RegionLayer::spacingIndexToY(View *v, int i) const
+RegionLayer::spacingIndexToY(LayerGeometryProvider *v, int i) const
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
     int n = int(m_spacingMap.size());
     // this maps from i (spacing of the value from the spacing
     // map) and n (number of region types) to y
@@ -687,10 +687,10 @@
 }
 
 double
-RegionLayer::yToSpacingIndex(View *v, int y) const
+RegionLayer::yToSpacingIndex(LayerGeometryProvider *v, int y) const
 {
     // we return an inexact result here (double rather than int)
-    int h = v->height();
+    int h = v->getPaintHeight();
     int n = int(m_spacingMap.size());
     // from y = h - ((h * i) / n) + (h / (2 * n)) as above (vh taking place of i)
     double vh = double(2*h*n - h - 2*n*y) / double(2*h);
@@ -698,11 +698,11 @@
 }
 
 int
-RegionLayer::getYForValue(View *v, double val) const
+RegionLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     if (m_verticalScale == EqualSpaced) {
 
@@ -733,17 +733,17 @@
 }
 
 double
-RegionLayer::getValueForY(View *v, int y) const
+RegionLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
     return getValueForY(v, y, -1);
 }
 
 double
-RegionLayer::getValueForY(View *v, int y, int avoid) const
+RegionLayer::getValueForY(LayerGeometryProvider *v, int y, int avoid) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     if (m_verticalScale == EqualSpaced) {
 
@@ -836,7 +836,7 @@
 }
 
 QColor
-RegionLayer::getColourForValue(View *v, double val) const
+RegionLayer::getColourForValue(LayerGeometryProvider *v, double val) const
 {
     double min, max;
     bool log;
@@ -866,7 +866,7 @@
 }
 
 void
-RegionLayer::paint(View *v, QPainter &paint, QRect rect) const
+RegionLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -942,7 +942,7 @@
 	if (w < 1) w = 1;
 
 	if (m_plotStyle == PlotSegmentation) {
-            paint.setPen(getForegroundQColor(v));
+            paint.setPen(getForegroundQColor(v->getView()));
             paint.setBrush(getColourForValue(v, p.value));
         } else {
             paint.setPen(getBaseQColor());
@@ -958,15 +958,15 @@
                 RegionModel::Point::Comparator()(illuminatePoint, p) ||
                 RegionModel::Point::Comparator()(p, illuminatePoint)) {
 
-                paint.setPen(QPen(getForegroundQColor(v), 1));
-                paint.drawLine(x, 0, x, v->height());
+                paint.setPen(QPen(getForegroundQColor(v->getView()), 1));
+                paint.drawLine(x, 0, x, v->getPaintHeight());
                 paint.setPen(Qt::NoPen);
 
             } else {
-                paint.setPen(QPen(getForegroundQColor(v), 2));
+                paint.setPen(QPen(getForegroundQColor(v->getView()), 2));
             }
 
-	    paint.drawRect(x, -1, ex - x, v->height() + 2);
+	    paint.drawRect(x, -1, ex - x, v->getPaintHeight() + 2);
 
 	} else {
 
@@ -1040,7 +1040,7 @@
                 labelX = x + 5;
                 labelY = v->getTextLabelHeight(this, paint);
                 if (labelX < nextLabelMinX) {
-                    if (lastLabelY < v->height()/2) {
+                    if (lastLabelY < v->getPaintHeight()/2) {
                         labelY = lastLabelY + fontHeight;
                     }
                 }
@@ -1056,7 +1056,7 @@
 }
 
 int
-RegionLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+RegionLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || 
         m_verticalScale == AutoAlignScale || 
@@ -1078,7 +1078,7 @@
 }
 
 void
-RegionLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+RegionLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
     if (!m_model || m_model->getPoints().empty()) return;
 
@@ -1121,11 +1121,11 @@
 }
 
 void
-RegionLayer::drawStart(View *v, QMouseEvent *e)
+RegionLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -1145,7 +1145,7 @@
 }
 
 void
-RegionLayer::drawDrag(View *v, QMouseEvent *e)
+RegionLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1175,7 +1175,7 @@
 }
 
 void
-RegionLayer::drawEnd(View *, QMouseEvent *)
+RegionLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
     if (!m_model || !m_editing) return;
     finish(m_editingCommand);
@@ -1186,7 +1186,7 @@
 }
 
 void
-RegionLayer::eraseStart(View *v, QMouseEvent *e)
+RegionLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1202,12 +1202,12 @@
 }
 
 void
-RegionLayer::eraseDrag(View *, QMouseEvent *)
+RegionLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-RegionLayer::eraseEnd(View *v, QMouseEvent *e)
+RegionLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1229,7 +1229,7 @@
 }
 
 void
-RegionLayer::editStart(View *v, QMouseEvent *e)
+RegionLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1254,7 +1254,7 @@
 }
 
 void
-RegionLayer::editDrag(View *v, QMouseEvent *e)
+RegionLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1263,7 +1263,7 @@
     int newx = m_dragPointX + xdist;
     int newy = m_dragPointY + ydist;
 
-    long frame = v->getFrameForX(newx);
+    sv_frame_t frame = v->getFrameForX(newx);
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -1289,7 +1289,7 @@
 }
 
 void
-RegionLayer::editEnd(View *, QMouseEvent *)
+RegionLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
     if (!m_model || !m_editing) return;
 
@@ -1317,7 +1317,7 @@
 }
 
 bool
-RegionLayer::editOpen(View *v, QMouseEvent *e)
+RegionLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -1447,7 +1447,7 @@
 }    
 
 void
-RegionLayer::copy(View *v, Selection s, Clipboard &to)
+RegionLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1465,7 +1465,7 @@
 }
 
 bool
-RegionLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
+RegionLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -1476,7 +1476,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), 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);
--- a/layer/RegionLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/RegionLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -39,41 +39,41 @@
 public:
     RegionLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
     virtual QString getLabelPreceding(sv_frame_t) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
-    virtual bool snapToSimilarFeature(View *v, sv_frame_t &frame,
+    virtual bool snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
                                       int &resolution,
                                       SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -110,11 +110,11 @@
     void setPlotStyle(PlotStyle style);
     PlotStyle getPlotStyle() const { return m_plotStyle; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
@@ -127,23 +127,23 @@
     void setProperties(const QXmlAttributes &attributes);
 
     /// VerticalScaleLayer and ColourScaleLayer methods
-    int getYForValue(View *v, double value) const;
-    double getValueForY(View *v, int y) const;
+    int getYForValue(LayerGeometryProvider *v, double value) const;
+    double getValueForY(LayerGeometryProvider *v, int y) const;
     virtual QString getScaleUnits() const;
-    QColor getColourForValue(View *v, double value) const;
+    QColor getColourForValue(LayerGeometryProvider *v, double value) const;
 
 protected slots:
     void recalcSpacing();
 
 protected:
-    double getValueForY(View *v, int y, int avoid) const;
-    void getScaleExtents(View *, double &min, double &max, bool &log) const;
+    double getValueForY(LayerGeometryProvider *v, int y, int avoid) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    RegionModel::PointList getLocalPoints(View *v, int x) const;
+    RegionModel::PointList getLocalPoints(LayerGeometryProvider *v, int x) const;
 
-    bool getPointToDrag(View *v, int x, int y, RegionModel::Point &) const;
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, RegionModel::Point &) const;
 
     RegionModel *m_model;
     bool m_editing;
@@ -166,8 +166,8 @@
     // region value -> number of regions with this value
     SpacingMap m_distributionMap;
 
-    int spacingIndexToY(View *v, int i) const;
-    double yToSpacingIndex(View *v, int y) const;
+    int spacingIndexToY(LayerGeometryProvider *v, int i) const;
+    double yToSpacingIndex(LayerGeometryProvider *v, int y) const;
 
     void finish(RegionModel::EditCommand *command) {
         Command *c = command->finish();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ScrollableImageCache.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -0,0 +1,206 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ScrollableImageCache.h"
+
+#include <iostream>
+using namespace std;
+
+//#define DEBUG_SCROLLABLE_IMAGE_CACHE 1
+
+void
+ScrollableImageCache::scrollTo(sv_frame_t newStartFrame)
+{
+    if (!m_v) throw std::logic_error("ScrollableImageCache: not associated with a LayerGeometryProvider");
+	
+    int dx = (m_v->getXForFrame(m_startFrame) -
+	      m_v->getXForFrame(newStartFrame));
+
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+    cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame
+	 << " -> " << newStartFrame << ", dx = " << dx << endl;
+#endif
+    
+    m_startFrame = newStartFrame;
+	
+    if (!isValid()) {
+	return;
+    }
+
+    int w = m_image.width();
+
+    if (dx == 0) {
+	// haven't moved
+	return;
+    }
+
+    if (dx <= -w || dx >= w) {
+	// scrolled entirely off
+	invalidate();
+	return;
+    }
+	
+    // dx is in range, cache is scrollable
+
+    int dxp = dx;
+    if (dxp < 0) dxp = -dxp;
+
+    int copylen = (w - dxp) * int(sizeof(QRgb));
+    for (int y = 0; y < m_image.height(); ++y) {
+	QRgb *line = (QRgb *)m_image.scanLine(y);
+	if (dx < 0) {
+	    memmove(line, line + dxp, copylen);
+	} else {
+	    memmove(line + dxp, line, copylen);
+	}
+    }
+	
+    // update valid area
+        
+    int px = m_left;
+    int pw = m_width;
+	
+    px += dx;
+	
+    if (dx < 0) {
+	// we scrolled left
+	if (px < 0) {
+	    pw += px;
+	    px = 0;
+	    if (pw < 0) {
+		pw = 0;
+	    }
+	}
+    } else {
+	// we scrolled right
+	if (px + pw > w) {
+	    pw = w - px;
+	    if (pw < 0) {
+		pw = 0;
+	    }
+	}
+    }
+
+    m_left = px;
+    m_width = pw;
+}
+
+void
+ScrollableImageCache::adjustToTouchValidArea(int &left, int &width,
+					     bool &isLeftOfValidArea) const
+{
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+    cerr << "ScrollableImageCache::adjustToTouchValidArea: left " << left
+         << ", width " << width << endl;
+    cerr << "ScrollableImageCache: my left " << m_left
+         << ", width " << m_width << " so right " << (m_left + m_width) << endl;
+#endif
+    if (left < m_left) {
+	isLeftOfValidArea = true;
+	if (left + width <= m_left + m_width) {
+	    width = m_left - left;
+	}
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+        cerr << "ScrollableImageCache: we're left of valid area, adjusted width to " << width << endl;
+#endif
+    } else {
+	isLeftOfValidArea = false;
+	width = left + width - (m_left + m_width);
+	left = m_left + m_width;
+	if (width < 0) width = 0;
+#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
+        cerr << "ScrollableImageCache: we're right of valid area, adjusted left to " << left << ", width to " << width << endl;
+#endif
+    }
+}
+    
+void
+ScrollableImageCache::drawImage(int left,
+				int width,
+				QImage image,
+				int imageLeft,
+				int imageWidth)
+{
+    if (image.height() != m_image.height()) {
+	cerr << "ScrollableImageCache::drawImage: ERROR: Supplied image height "
+	     << image.height() << " does not match cache height "
+	     << m_image.height() << endl;
+	throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage");
+    }
+    if (left < 0 || width < 0 || left + width > m_image.width()) {
+	cerr << "ScrollableImageCache::drawImage: ERROR: Target area (left = "
+	     << left << ", width = " << width << ", so right = " << left + width
+             << ") out of bounds for cache of width " << m_image.width() << endl;
+	throw std::logic_error("Target area out of bounds in ScrollableImageCache::drawImage");
+    }
+    if (imageLeft < 0 || imageWidth < 0 ||
+	imageLeft + imageWidth > image.width()) {
+	cerr << "ScrollableImageCache::drawImage: ERROR: Source area (left = "
+	     << imageLeft << ", width = " << imageWidth << ", so right = "
+             << imageLeft + imageWidth << ") out of bounds for image of "
+	     << "width " << image.width() << endl;
+	throw std::logic_error("Source area out of bounds in ScrollableImageCache::drawImage");
+    }
+	
+    QPainter painter(&m_image);
+    painter.drawImage(QRect(left, 0, width, m_image.height()),
+		      image,
+		      QRect(imageLeft, 0, imageWidth, image.height()));
+    painter.end();
+
+    if (!isValid()) {
+	m_left = left;
+	m_width = width;
+	return;
+    }
+	
+    if (left < m_left) {
+	if (left + width > m_left + m_width) {
+	    // new image completely contains the old valid area --
+	    // use the new area as is
+	    m_left = left;
+	    m_width = width;
+	} else if (left + width < m_left) {
+	    // new image completely off left of old valid area --
+	    // we can't extend the valid area because the bit in
+	    // between is not valid, so must use the new area only
+	    m_left = left;
+	    m_width = width;
+	} else {
+	    // new image overlaps old valid area on left side --
+	    // use new left edge, and extend width to existing
+	    // right edge
+	    m_width = (m_left + m_width) - left;
+	    m_left = left;
+	}
+    } else {
+	if (left > m_left + m_width) {
+	    // new image completely off right of old valid area --
+	    // we can't extend the valid area because the bit in
+	    // between is not valid, so must use the new area only
+	    m_left = left;
+	    m_width = width;
+	} else if (left + width > m_left + m_width) {
+	    // new image overlaps old valid area on right side --
+	    // use existing left edge, and extend width to new
+	    // right edge
+	    m_width = (left + width) - m_left;
+	    // (m_left unchanged)
+	} else {
+	    // new image completely contained within old valid
+	    // area -- leave the old area unchanged
+	}
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ScrollableImageCache.h	Wed Mar 02 17:25:27 2016 +0000
@@ -0,0 +1,145 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SCROLLABLE_IMAGE_CACHE_H
+#define SCROLLABLE_IMAGE_CACHE_H
+
+#include "base/BaseTypes.h"
+
+#include "view/LayerGeometryProvider.h"
+
+#include <QImage>
+#include <QRect>
+#include <QPainter>
+
+/**
+ * A cached image for a view that scrolls horizontally, primarily the
+ * spectrogram. The cache object holds an image, reports the size of
+ * the image (likely the same as the underlying view, but it's the
+ * caller's responsibility to set the size appropriately), can scroll
+ * the image, and can report and update which contiguous horizontal
+ * range of the image is valid.
+ *
+ * The only way to *update* the valid area in a cache is to draw to it
+ * using the drawImage call.
+ */
+class ScrollableImageCache
+{
+public:
+    ScrollableImageCache(const LayerGeometryProvider *v = 0) :
+	m_v(v),
+	m_left(0),
+	m_width(0),
+	m_startFrame(0),
+	m_zoomLevel(0)
+    {}
+
+    void invalidate() {
+	m_width = 0;
+    }
+    
+    bool isValid() const {
+	return m_width > 0;
+    }
+
+    QSize getSize() const {
+	return m_image.size();
+    }
+    
+    void resize(QSize newSize) {
+	m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied);
+	invalidate();
+    }
+	
+    int getValidLeft() const {
+	return m_left;
+    }
+    
+    int getValidWidth() const {
+	return m_width;
+    }
+
+    int getValidRight() const {
+	return m_left + m_width;
+    }
+
+    QRect getValidArea() const {
+	return QRect(m_left, 0, m_width, m_image.height());
+    }
+    
+    int getZoomLevel() const {
+	return m_zoomLevel;
+    }
+    
+    void setZoomLevel(int zoom) {
+	m_zoomLevel = zoom;
+	invalidate();
+    }
+
+    sv_frame_t getStartFrame() const {
+	return m_startFrame;
+    }
+
+    /**
+     * Set the start frame and invalidate the cache. To scroll,
+     * i.e. to set the start frame while retaining cache validity
+     * where possible, use scrollTo() instead.
+     */
+    void setStartFrame(sv_frame_t frame) {
+	m_startFrame = frame;
+	invalidate();
+    }
+    
+    const QImage &getImage() const {
+	return m_image;
+    }
+
+    /**
+     * Set the new start frame for the cache, if possible also moving
+     * along any existing valid data within the cache so that it
+     * continues to be valid for the new start frame.
+     */
+    void scrollTo(sv_frame_t newStartFrame);
+
+    /**
+     * Take a left coordinate and width describing a region, and
+     * adjust them so that they are contiguous with the cache valid
+     * region and so that the union of the adjusted region with the
+     * cache valid region contains the supplied region.
+     */
+    void adjustToTouchValidArea(int &left, int &width,
+				bool &isLeftOfValidArea) const;
+    /**
+     * Draw from an image onto the cache. The supplied image must have
+     * the same height as the cache and the full height is always
+     * drawn. The left and width parameters determine the target
+     * region of the cache, the imageLeft and imageWidth parameters
+     * the source region of the image.
+     */
+    void drawImage(int left,
+		   int width,
+		   QImage image,
+		   int imageLeft,
+		   int imageWidth);
+    
+private:
+    const LayerGeometryProvider *m_v;
+    QImage m_image;
+    int m_left;  // of valid region
+    int m_width; // of valid region
+    sv_frame_t m_startFrame;
+    int m_zoomLevel;
+};
+
+#endif
--- a/layer/SingleColourLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SingleColourLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -137,7 +137,7 @@
 }
 
 void
-SingleColourLayer::setDefaultColourFor(View *v)
+SingleColourLayer::setDefaultColourFor(LayerGeometryProvider *v)
 {
 #ifdef DEBUG_COLOUR_SELECTION
     SVDEBUG << "SingleColourLayer::setDefaultColourFor: m_colourExplicitlySet = " << m_colourExplicitlySet << ", m_defaultColourSet " << m_defaultColourSet << endl;
@@ -244,19 +244,19 @@
 }
 
 QColor
-SingleColourLayer::getBackgroundQColor(View *v) const
+SingleColourLayer::getBackgroundQColor(LayerGeometryProvider *v) const
 {
     return v->getBackground();
 }
 
 QColor
-SingleColourLayer::getForegroundQColor(View *v) const
+SingleColourLayer::getForegroundQColor(LayerGeometryProvider *v) const
 {
     return v->getForeground();
 }
 
 std::vector<QColor>
-SingleColourLayer::getPartialShades(View *v) const
+SingleColourLayer::getPartialShades(LayerGeometryProvider *v) const
 {
     std::vector<QColor> s;
     QColor base = getBaseQColor();
--- a/layer/SingleColourLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SingleColourLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -71,16 +71,16 @@
 
     virtual void setProperties(const QXmlAttributes &attributes);
 
-    virtual void setDefaultColourFor(View *v);
+    virtual void setDefaultColourFor(LayerGeometryProvider *v);
 
 protected:
     SingleColourLayer();
     virtual ~SingleColourLayer();
 
     virtual QColor getBaseQColor() const;
-    virtual QColor getBackgroundQColor(View *v) const;
-    virtual QColor getForegroundQColor(View *v) const;
-    std::vector<QColor> getPartialShades(View *v) const;
+    virtual QColor getBackgroundQColor(LayerGeometryProvider *v) const;
+    virtual QColor getForegroundQColor(LayerGeometryProvider *v) const;
+    std::vector<QColor> getPartialShades(LayerGeometryProvider *v) const;
 
     virtual void flagBaseColourChanged() { }
     virtual int getDefaultColourHint(bool /* darkBackground */,
--- a/layer/SliceLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SliceLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -92,14 +92,14 @@
 }
 
 QString
-SliceLayer::getFeatureDescription(View *v, QPoint &p) const
+SliceLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
 {
     int minbin, maxbin, range;
     return getFeatureDescriptionAux(v, p, true, minbin, maxbin, range);
 }
 
 QString
-SliceLayer::getFeatureDescriptionAux(View *v, QPoint &p,
+SliceLayer::getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &p,
                                      bool includeBinDescription,
                                      int &minbin, int &maxbin, int &range) const
 {
@@ -108,7 +108,7 @@
     if (!m_sliceableModel) return "";
 
     int xorigin = m_xorigins[v];
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
     
     int mh = m_sliceableModel->getHeight();
     minbin = getBinForX(p.x() - xorigin, mh, w);
@@ -226,7 +226,7 @@
 }
 
 double
-SliceLayer::getYForValue(double value, const View *v, double &norm) const
+SliceLayer::getYForValue(double value, const LayerGeometryProvider *v, double &norm) const
 {
     norm = 0.0;
 
@@ -276,7 +276,7 @@
 }
 
 double
-SliceLayer::getValueForY(double y, const View *v) const
+SliceLayer::getValueForY(double y, const LayerGeometryProvider *v) const
 {
     double value = 0.0;
 
@@ -313,7 +313,7 @@
 }
 
 void
-SliceLayer::paint(View *v, QPainter &paint, QRect rect) const
+SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_sliceableModel || !m_sliceableModel->isOK() ||
         !m_sliceableModel->isReady()) return;
@@ -334,11 +334,11 @@
     paint.setPen(getBaseQColor());
 
     int xorigin = getVerticalScaleWidth(v, true, paint) + 1;
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
 
     m_xorigins[v] = xorigin; // for use in getFeatureDescription
     
-    int yorigin = v->height() - 20 - paint.fontMetrics().height() - 7;
+    int yorigin = v->getPaintHeight() - 20 - paint.fontMetrics().height() - 7;
     int h = yorigin - paint.fontMetrics().height() - 8;
 
     m_yorigins[v] = yorigin; // for getYForValue etc
@@ -501,7 +501,7 @@
 }
 
 int
-SliceLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const
+SliceLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
     if (m_energyScale == LinearScale || m_energyScale == AbsoluteScale) {
 	return std::max(paint.fontMetrics().width("0.0") + 13,
@@ -513,7 +513,7 @@
 }
 
 void
-SliceLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const
+SliceLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
     double thresh = m_threshold;
     if (m_energyScale != LinearScale && m_energyScale != AbsoluteScale) {
@@ -523,7 +523,7 @@
 //    int h = (rect.height() * 3) / 4;
 //    int y = (rect.height() / 2) - (h / 2);
     
-    int yorigin = v->height() - 20 - paint.fontMetrics().height() - 6;
+    int yorigin = v->getPaintHeight() - 20 - paint.fontMetrics().height() - 6;
     int h = yorigin - paint.fontMetrics().height() - 8;
     if (h < 0) return;
 
--- a/layer/SliceLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SliceLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -38,12 +38,12 @@
 
     void setSliceableModel(const Model *model);    
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourAndBackgroundSignificant;
@@ -67,7 +67,7 @@
 
     virtual bool hasTimeXAxis() const { return false; }
 
-    virtual bool isLayerScrollable(const View *) const { return false; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; }
 
     enum EnergyScale { LinearScale, MeterScale, dBScale, AbsoluteScale };
 
@@ -112,10 +112,10 @@
     virtual double getXForBin(int bin, int totalBins, double w) const;
     virtual int getBinForX(double x, int totalBins, double w) const;
 
-    virtual double getYForValue(double value, const View *v, double &norm) const;
-    virtual double getValueForY(double y, const View *v) const;
+    virtual double getYForValue(double value, const LayerGeometryProvider *v, double &norm) const;
+    virtual double getValueForY(double y, const LayerGeometryProvider *v) const;
     
-    virtual QString getFeatureDescriptionAux(View *v, QPoint &,
+    virtual QString getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &,
                                              bool includeBinDescription,
                                              int &minbin, int &maxbin,
                                              int &range) const;
@@ -141,9 +141,9 @@
     float                             m_initialThreshold;
     float                             m_gain;
     mutable std::vector<int>          m_scalePoints;
-    mutable std::map<const View *, int> m_xorigins;
-    mutable std::map<const View *, int> m_yorigins;
-    mutable std::map<const View *, int> m_heights;
+    mutable std::map<const LayerGeometryProvider *, int> m_xorigins;
+    mutable std::map<const LayerGeometryProvider *, int> m_yorigins;
+    mutable std::map<const LayerGeometryProvider *, int> m_heights;
     mutable sv_frame_t                m_currentf0;
     mutable sv_frame_t                m_currentf1;
     mutable std::vector<float>        m_values;
--- a/layer/SpectrogramLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SpectrogramLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -33,11 +33,11 @@
 #include <QImage>
 #include <QPixmap>
 #include <QRect>
-#include <QTimer>
 #include <QApplication>
 #include <QMessageBox>
 #include <QMouseEvent>
 #include <QTextStream>
+#include <QSettings>
 
 #include <iostream>
 
@@ -48,9 +48,9 @@
 #include <alloca.h>
 #endif
 
-//#define DEBUG_SPECTROGRAM_REPAINT 1
-
-using std::vector;
+#define DEBUG_SPECTROGRAM_REPAINT 1
+
+using namespace std;
 
 SpectrogramLayer::SpectrogramLayer(Configuration config) :
     m_model(0),
@@ -73,18 +73,16 @@
     m_colourMap(0),
     m_frequencyScale(LinearFrequencyScale),
     m_binDisplay(AllBins),
-    m_normalizeColumns(false),
-    m_normalizeVisibleArea(false),
-    m_normalizeHybrid(false),
+    m_normalization(NoNormalization),
     m_lastEmittedZoomStep(-1),
     m_synchronous(false),
     m_haveDetailedScale(false),
-    m_lastPaintBlockWidth(0),
-    m_updateTimer(0),
-    m_candidateFillStartFrame(0),
     m_exiting(false),
     m_sliceableModel(0)
 {
+    QString colourConfigName = "spectrogram-colour";
+    int colourConfigDefault = int(ColourMapper::Green);
+    
     if (config == FullRangeDb) {
         m_initialMaxFrequency = 0;
         setMaxFrequency(0);
@@ -97,6 +95,8 @@
 	setColourScale(LinearColourScale);
         setColourMap(ColourMapper::Sunset);
         setFrequencyScale(LogFrequencyScale);
+        colourConfigName = "spectrogram-melodic-colour";
+        colourConfigDefault = int(ColourMapper::Sunset);
 //        setGain(20);
     } else if (config == MelodicPeaks) {
 	setWindowSize(4096);
@@ -107,9 +107,16 @@
 	setFrequencyScale(LogFrequencyScale);
 	setColourScale(LinearColourScale);
 	setBinDisplay(PeakFrequencies);
-	setNormalizeColumns(true);
+        setNormalization(NormalizeColumns);
+        colourConfigName = "spectrogram-melodic-colour";
+        colourConfigDefault = int(ColourMapper::Sunset);
     }
 
+    QSettings settings;
+    settings.beginGroup("Preferences");
+    setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt());
+    settings.endGroup();
+    
     Preferences *prefs = Preferences::getInstance();
     connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
             this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
@@ -120,9 +127,6 @@
 
 SpectrogramLayer::~SpectrogramLayer()
 {
-    delete m_updateTimer;
-    m_updateTimer = 0;
-    
     invalidateFFTModels();
 }
 
@@ -155,8 +159,7 @@
     list.push_back("Colour Scale");
     list.push_back("Window Size");
     list.push_back("Window Increment");
-    list.push_back("Normalize Columns");
-    list.push_back("Normalize Visible Area");
+    list.push_back("Normalization");
     list.push_back("Bin Display");
     list.push_back("Threshold");
     list.push_back("Gain");
@@ -175,8 +178,7 @@
     if (name == "Colour Scale") return tr("Colour Scale");
     if (name == "Window Size") return tr("Window Size");
     if (name == "Window Increment") return tr("Window Overlap");
-    if (name == "Normalize Columns") return tr("Normalize Columns");
-    if (name == "Normalize Visible Area") return tr("Normalize Visible Area");
+    if (name == "Normalization") return tr("Normalization");
     if (name == "Bin Display") return tr("Bin Display");
     if (name == "Threshold") return tr("Threshold");
     if (name == "Gain") return tr("Gain");
@@ -189,10 +191,8 @@
 }
 
 QString
-SpectrogramLayer::getPropertyIconName(const PropertyName &name) const
+SpectrogramLayer::getPropertyIconName(const PropertyName &) const
 {
-    if (name == "Normalize Columns") return "normalise-columns";
-    if (name == "Normalize Visible Area") return "normalise";
     return "";
 }
 
@@ -201,8 +201,6 @@
 {
     if (name == "Gain") return RangeProperty;
     if (name == "Colour Rotation") return RangeProperty;
-    if (name == "Normalize Columns") return ToggleProperty;
-    if (name == "Normalize Visible Area") return ToggleProperty;
     if (name == "Threshold") return RangeProperty;
     if (name == "Zero Padding") return ToggleProperty;
     return ValueProperty;
@@ -219,8 +217,7 @@
     if (name == "Colour" ||
 	name == "Threshold" ||
 	name == "Colour Rotation") return tr("Colour");
-    if (name == "Normalize Columns" ||
-        name == "Normalize Visible Area" ||
+    if (name == "Normalization" ||
         name == "Gain" ||
 	name == "Colour Scale") return tr("Scale");
     return QString();
@@ -365,15 +362,12 @@
         *deflt = int(AllBins);
 	val = (int)m_binDisplay;
 
-    } else if (name == "Normalize Columns") {
+    } else if (name == "Normalization") {
 	
-        *deflt = 0;
-	val = (m_normalizeColumns ? 1 : 0);
-
-    } else if (name == "Normalize Visible Area") {
-	
-        *deflt = 0;
-	val = (m_normalizeVisibleArea ? 1 : 0);
+        *min = 0;
+        *max = 3;
+        *deflt = int(NoNormalization);
+        val = (int)m_normalization;
 
     } else {
 	val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
@@ -399,6 +393,9 @@
 	case 4: return tr("Phase");
 	}
     }
+    if (name == "Normalization") {
+        return ""; // icon only
+    }
     if (name == "Window Size") {
 	return QString("%1").arg(32 << value);
     }
@@ -465,6 +462,22 @@
     return tr("<unknown>");
 }
 
+QString
+SpectrogramLayer::getPropertyValueIconName(const PropertyName &name,
+                                           int value) const
+{
+    if (name == "Normalization") {
+        switch(value) {
+        default:
+        case 0: return "normalise-none";
+        case 1: return "normalise-columns";
+        case 2: return "normalise";
+        case 3: return "normalise-hybrid";
+        }
+    }
+    return "";
+}
+
 RangeMapper *
 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const
 {
@@ -555,86 +568,26 @@
 	case 1: setBinDisplay(PeakBins); break;
 	case 2: setBinDisplay(PeakFrequencies); break;
 	}
-    } else if (name == "Normalize Columns") {
-	setNormalizeColumns(value ? true : false);
-    } else if (name == "Normalize Visible Area") {
-	setNormalizeVisibleArea(value ? true : false);
+    } else if (name == "Normalization") {
+        switch (value) {
+        default:
+        case 0: setNormalization(NoNormalization); break;
+        case 1: setNormalization(NormalizeColumns); break;
+        case 2: setNormalization(NormalizeVisibleArea); break;
+        case 3: setNormalization(NormalizeHybrid); break;
+        }
     }
 }
 
 void
 SpectrogramLayer::invalidateImageCaches()
 {
+#ifdef DEBUG_SPECTROGRAM
+    cerr << "SpectrogramLayer::invalidateImageCaches called" << endl;
+#endif
     for (ViewImageCache::iterator i = m_imageCaches.begin();
          i != m_imageCaches.end(); ++i) {
-        i->second.validArea = QRect();
-    }
-}
-
-void
-SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame)
-{
-    for (ViewImageCache::iterator i = m_imageCaches.begin();
-         i != m_imageCaches.end(); ++i) {
-
-        //!!! when are views removed from the map? on setLayerDormant?
-        const View *v = i->first;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "SpectrogramLayer::invalidateImageCaches(" 
-                  << startFrame << ", " << endFrame << "): view range is "
-                  << v->getStartFrame() << ", " << v->getEndFrame()
-                  << endl;
-
-        cerr << "Valid area was: " << i->second.validArea.x() << ", "
-                  << i->second.validArea.y() << " "
-                  << i->second.validArea.width() << "x"
-                  << i->second.validArea.height() << endl;
-#endif
-
-        if (int(startFrame) > v->getStartFrame()) {
-            if (startFrame >= v->getEndFrame()) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                cerr << "Modified start frame is off right of view" << endl;
-#endif
-                return;
-            }
-            int x = v->getXForFrame(startFrame);
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "clipping from 0 to " << x-1 << endl;
-#endif
-            if (x > 1) {
-                i->second.validArea &=
-                    QRect(0, 0, x-1, v->height());
-            } else {
-                i->second.validArea = QRect();
-            }
-        } else {
-            if (int(endFrame) < v->getStartFrame()) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                cerr << "Modified end frame is off left of view" << endl;
-#endif
-                return;
-            }
-            int x = v->getXForFrame(endFrame);
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "clipping from " << x+1 << " to " << v->width()
-                      << endl;
-#endif
-            if (x < v->width()) {
-                i->second.validArea &=
-                    QRect(x+1, 0, v->width()-(x+1), v->height());
-            } else {
-                i->second.validArea = QRect();
-            }
-        }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Valid area is now: " << i->second.validArea.x() << ", "
-                  << i->second.validArea.y() << " "
-                  << i->second.validArea.width() << "x"
-                  << i->second.validArea.height() << endl;
-#endif
+        i->second.invalidate();
     }
 }
 
@@ -934,69 +887,30 @@
 }
 
 void
-SpectrogramLayer::setNormalizeColumns(bool n)
+SpectrogramLayer::setNormalization(Normalization n)
 {
-    if (m_normalizeColumns == n) return;
+    if (m_normalization == n) return;
 
     invalidateImageCaches();
     invalidateMagnitudes();
-    m_normalizeColumns = n;
+    m_normalization = n;
 
     emit layerParametersChanged();
 }
 
-bool
-SpectrogramLayer::getNormalizeColumns() const
+SpectrogramLayer::Normalization
+SpectrogramLayer::getNormalization() const
 {
-    return m_normalizeColumns;
+    return m_normalization;
 }
 
 void
-SpectrogramLayer::setNormalizeHybrid(bool n)
-{
-    if (m_normalizeHybrid == n) return;
-
-    invalidateImageCaches();
-    invalidateMagnitudes();
-    m_normalizeHybrid = n;
-
-    emit layerParametersChanged();
-}
-
-bool
-SpectrogramLayer::getNormalizeHybrid() const
-{
-    return m_normalizeHybrid;
-}
-
-void
-SpectrogramLayer::setNormalizeVisibleArea(bool n)
-{
-    SVDEBUG << "SpectrogramLayer::setNormalizeVisibleArea(" << n
-              << ") (from " << m_normalizeVisibleArea << ")" << endl;
-
-    if (m_normalizeVisibleArea == n) return;
-
-    invalidateImageCaches();
-    invalidateMagnitudes();
-    m_normalizeVisibleArea = n;
-
-    emit layerParametersChanged();
-}
-
-bool
-SpectrogramLayer::getNormalizeVisibleArea() const
-{
-    return m_normalizeVisibleArea;
-}
-
-void
-SpectrogramLayer::setLayerDormant(const View *v, bool dormant)
+SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
 {
     if (dormant) {
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
+        cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
                   << endl;
 #endif
 
@@ -1006,17 +920,20 @@
 
         Layer::setLayerDormant(v, true);
 
+        const View *view = v->getView();
+        
 	invalidateImageCaches();
-        m_imageCaches.erase(v);
-
-        if (m_fftModels.find(v) != m_fftModels.end()) {
-
-            if (m_sliceableModel == m_fftModels[v].first) {
+
+        m_imageCaches.erase(view->getId());
+
+        if (m_fftModels.find(view->getId()) != m_fftModels.end()) {
+
+            if (m_sliceableModel == m_fftModels[view->getId()]) {
                 bool replaced = false;
                 for (ViewFFTMap::iterator i = m_fftModels.begin();
                      i != m_fftModels.end(); ++i) {
-                    if (i->second.first != m_sliceableModel) {
-                        emit sliceableModelReplaced(m_sliceableModel, i->second.first);
+                    if (i->second != m_sliceableModel) {
+                        emit sliceableModelReplaced(m_sliceableModel, i->second);
                         replaced = true;
                         break;
                     }
@@ -1024,11 +941,11 @@
                 if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0);
             }
 
-            delete m_fftModels[v].first;
-            m_fftModels.erase(v);
-
-            delete m_peakCaches[v];
-            m_peakCaches.erase(v);
+            delete m_fftModels[view->getId()];
+            m_fftModels.erase(view->getId());
+
+            delete m_peakCaches[view->getId()];
+            m_peakCaches.erase(view->getId());
         }
 	
     } else {
@@ -1041,7 +958,7 @@
 SpectrogramLayer::cacheInvalid()
 {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::cacheInvalid()" << endl;
+    cerr << "SpectrogramLayer::cacheInvalid()" << endl;
 #endif
 
     invalidateImageCaches();
@@ -1049,84 +966,28 @@
 }
 
 void
-SpectrogramLayer::cacheInvalid(sv_frame_t from, sv_frame_t to)
+SpectrogramLayer::cacheInvalid(
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    sv_frame_t from, sv_frame_t to
+#else 
+    sv_frame_t     , sv_frame_t
+#endif
+    )
 {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
+    cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
 #endif
 
-    invalidateImageCaches(from, to);
+    // We used to call invalidateMagnitudes(from, to) to invalidate
+    // only those caches whose views contained some of the (from, to)
+    // range. That's the right thing to do; it has been lost in
+    // pulling out the image cache code, but it might not matter very
+    // much, since the underlying models for spectrogram layers don't
+    // change very often. Let's see.
+    invalidateImageCaches();
     invalidateMagnitudes();
 }
 
-void
-SpectrogramLayer::fillTimerTimedOut()
-{
-    if (!m_model) return;
-
-    bool allDone = true;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::fillTimerTimedOut: have " << m_fftModels.size() << " FFT models associated with views" << endl;
-#endif
-
-    for (ViewFFTMap::iterator i = m_fftModels.begin();
-         i != m_fftModels.end(); ++i) {
-
-        const FFTModel *model = i->second.first;
-        sv_frame_t lastFill = i->second.second;
-
-        if (model) {
-
-            sv_frame_t fill = model->getFillExtent();
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::fillTimerTimedOut: extent for " << model << ": " << fill << ", last " << lastFill << ", total " << m_model->getEndFrame() << endl;
-#endif
-
-            if (fill >= lastFill) {
-                if (fill >= m_model->getEndFrame() && lastFill > 0) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "complete!" << endl;
-#endif
-                    invalidateImageCaches();
-                    i->second.second = -1;
-                    emit modelChanged();
-
-                } else if (fill > lastFill) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "SpectrogramLayer: emitting modelChanged("
-                              << lastFill << "," << fill << ")" << endl;
-#endif
-                    invalidateImageCaches(lastFill, fill);
-                    i->second.second = fill;
-                    emit modelChangedWithin(lastFill, fill);
-                }
-            } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                cerr << "SpectrogramLayer: going backwards, emitting modelChanged("
-                          << m_model->getStartFrame() << "," << m_model->getEndFrame() << ")" << endl;
-#endif
-                invalidateImageCaches();
-                i->second.second = fill;
-                emit modelChangedWithin(m_model->getStartFrame(), m_model->getEndFrame());
-            }
-
-            if (i->second.second >= 0) {
-                allDone = false;
-            }
-        }
-    }
-
-    if (allDone) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "SpectrogramLayer: all complete!" << endl;
-#endif
-        delete m_updateTimer;
-        m_updateTimer = 0;
-    }
-}
-
 bool
 SpectrogramLayer::hasLightBackground() const 
 {
@@ -1181,17 +1042,17 @@
 }
 
 unsigned char
-SpectrogramLayer::getDisplayValue(View *v, double input) const
+SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const
 {
     int value;
 
     double min = 0.0;
     double max = 1.0;
 
-    if (m_normalizeVisibleArea) {
-        min = m_viewMags[v].getMin();
-        max = m_viewMags[v].getMax();
-    } else if (!m_normalizeColumns) {
+    if (m_normalization == NormalizeVisibleArea) {
+        min = m_viewMags[v->getId()].getMin();
+        max = m_viewMags[v->getId()].getMax();
+    } else if (m_normalization != NormalizeColumns) {
         if (m_colourScale == LinearColourScale //||
 //            m_colourScale == MeterColourScale) {
             ) {
@@ -1294,11 +1155,11 @@
 }
 
 bool
-SpectrogramLayer::getYBinRange(View *v, int y, double &q0, double &q1) const
+SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
 {
     Profiler profiler("SpectrogramLayer::getYBinRange");
     
-    int h = v->height();
+    int h = v->getPaintHeight();
     if (y < 0 || y >= h) return false;
 
     sv_samplerate_t sr = m_model->getSampleRate();
@@ -1320,11 +1181,11 @@
 }
 
 bool
-SpectrogramLayer::getSmoothedYBinRange(View *v, int y, double &q0, double &q1) const
+SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
 {
     Profiler profiler("SpectrogramLayer::getSmoothedYBinRange");
 
-    int h = v->height();
+    int h = v->getPaintHeight();
     if (y < 0 || y >= h) return false;
 
     sv_samplerate_t sr = m_model->getSampleRate();
@@ -1346,7 +1207,7 @@
 }
     
 bool
-SpectrogramLayer::getXBinRange(View *v, int x, double &s0, double &s1) const
+SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
 {
     sv_frame_t modelStart = m_model->getStartFrame();
     sv_frame_t modelEnd = m_model->getEndFrame();
@@ -1370,7 +1231,7 @@
 }
  
 bool
-SpectrogramLayer::getXBinSourceRange(View *v, int x, RealTime &min, RealTime &max) const
+SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
 {
     double s0 = 0, s1 = 0;
     if (!getXBinRange(v, x, s0, s1)) return false;
@@ -1389,7 +1250,7 @@
 }
 
 bool
-SpectrogramLayer::getYBinSourceRange(View *v, int y, double &freqMin, double &freqMax)
+SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
 const
 {
     double q0 = 0, q1 = 0;
@@ -1408,7 +1269,7 @@
 }
 
 bool
-SpectrogramLayer::getAdjustedYBinSourceRange(View *v, int x, int y,
+SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
 					     double &freqMin, double &freqMax,
 					     double &adjFreqMin, double &adjFreqMax)
 const
@@ -1443,8 +1304,6 @@
 
 	for (int s = s0i; s <= s1i; ++s) {
 
-            if (!fft->isColumnAvailable(s)) continue;
-
 	    double binfreq = (double(sr) * q) / m_windowSize;
 	    if (q == q0i) freqMin = binfreq;
 	    if (q == q1i) freqMax = binfreq;
@@ -1475,7 +1334,7 @@
 }
     
 bool
-SpectrogramLayer::getXYBinSourceRange(View *v, int x, int y,
+SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y,
 				      double &min, double &max,
 				      double &phaseMin, double &phaseMax) const
 {
@@ -1518,8 +1377,6 @@
             for (int s = s0i; s <= s1i; ++s) {
                 if (s >= 0 && q >= 0 && s < cw && q < ch) {
 
-                    if (!fft->isColumnAvailable(s)) continue;
-                    
                     double value;
 
                     value = fft->getPhaseAt(s, q);
@@ -1544,7 +1401,7 @@
 }
    
 int
-SpectrogramLayer::getZeroPadLevel(const View *v) const
+SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const
 {
     //!!! tidy all this stuff
 
@@ -1574,7 +1431,7 @@
     }
 
     double perPixel =
-        double(v->height()) /
+        double(v->getPaintHeight()) /
         double((maxbin - minbin) / (m_zeroPadLevel + 1));
 
     if (perPixel > 2.8) {
@@ -1587,42 +1444,44 @@
 }
 
 int
-SpectrogramLayer::getFFTSize(const View *v) const
+SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const
 {
     return m_fftSize * (getZeroPadLevel(v) + 1);
 }
 	
 FFTModel *
-SpectrogramLayer::getFFTModel(const View *v) const
+SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const
 {
     if (!m_model) return 0;
 
     int fftSize = getFFTSize(v);
 
-    if (m_fftModels.find(v) != m_fftModels.end()) {
-        if (m_fftModels[v].first == 0) {
+    const View *view = v->getView();
+    
+    if (m_fftModels.find(view->getId()) != m_fftModels.end()) {
+        if (m_fftModels[view->getId()] == 0) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl;
+            cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl;
 #endif
             return 0;
         }
-        if (m_fftModels[v].first->getHeight() != fftSize / 2 + 1) {
+        if (m_fftModels[view->getId()]->getHeight() != fftSize / 2 + 1) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[v].first->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl;
+            cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view->getId()]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl;
 #endif
-            delete m_fftModels[v].first;
-            m_fftModels.erase(v);
-            delete m_peakCaches[v];
-            m_peakCaches.erase(v);
+            delete m_fftModels[view->getId()];
+            m_fftModels.erase(view->getId());
+            delete m_peakCaches[view->getId()];
+            m_peakCaches.erase(view->getId());
         } else {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[v].first->getHeight() << endl;
+            cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view->getId()]->getHeight() << endl;
 #endif
-            return m_fftModels[v].first;
+            return m_fftModels[view->getId()];
         }
     }
 
-    if (m_fftModels.find(v) == m_fftModels.end()) {
+    if (m_fftModels.find(view->getId()) == m_fftModels.end()) {
 
         FFTModel *model = new FFTModel(m_model,
                                        m_channel,
@@ -1637,7 +1496,7 @@
                  tr("Failed to create the FFT model for this spectrogram.\n"
                     "There may be insufficient memory or disc space to continue."));
             delete model;
-            m_fftModels[v] = FFTFillPair(0, 0);
+            m_fftModels[view->getId()] = 0;
             return 0;
         }
 
@@ -1649,27 +1508,22 @@
             m_sliceableModel = model;
         }
 
-        m_fftModels[v] = FFTFillPair(model, 0);
-
-        delete m_updateTimer;
-        m_updateTimer = new QTimer((SpectrogramLayer *)this);
-        connect(m_updateTimer, SIGNAL(timeout()),
-                this, SLOT(fillTimerTimedOut()));
-        m_updateTimer->start(200);
+        m_fftModels[view->getId()] = model;
     }
 
-    return m_fftModels[v].first;
+    return m_fftModels[view->getId()];
 }
 
 Dense3DModelPeakCache *
-SpectrogramLayer::getPeakCache(const View *v) const
+SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const
 {
-    if (!m_peakCaches[v]) {
+    const View *view = v->getView();
+    if (!m_peakCaches[view->getId()]) {
         FFTModel *f = getFFTModel(v);
         if (!f) return 0;
-        m_peakCaches[v] = new Dense3DModelPeakCache(f, 8);
+        m_peakCaches[view->getId()] = new Dense3DModelPeakCache(f, 8);
     }
-    return m_peakCaches[v];
+    return m_peakCaches[view->getId()];
 }
 
 const Model *
@@ -1677,16 +1531,19 @@
 {
     if (m_sliceableModel) return m_sliceableModel;
     if (m_fftModels.empty()) return 0;
-    m_sliceableModel = m_fftModels.begin()->second.first;
+    m_sliceableModel = m_fftModels.begin()->second;
     return m_sliceableModel;
 }
 
 void
 SpectrogramLayer::invalidateFFTModels()
 {
+#ifdef DEBUG_SPECTROGRAM
+    cerr << "SpectrogramLayer::invalidateFFTModels called" << endl;
+#endif
     for (ViewFFTMap::iterator i = m_fftModels.begin();
          i != m_fftModels.end(); ++i) {
-        delete i->second.first;
+        delete i->second;
     }
     for (PeakCacheMap::iterator i = m_peakCaches.begin();
          i != m_peakCaches.end(); ++i) {
@@ -1706,19 +1563,22 @@
 void
 SpectrogramLayer::invalidateMagnitudes()
 {
+#ifdef DEBUG_SPECTROGRAM
+    cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl;
+#endif
     m_viewMags.clear();
-    for (std::vector<MagnitudeRange>::iterator i = m_columnMags.begin();
+    for (vector<MagnitudeRange>::iterator i = m_columnMags.begin();
          i != m_columnMags.end(); ++i) {
         *i = MagnitudeRange();
     }
 }
 
 bool
-SpectrogramLayer::updateViewMagnitudes(View *v) const
+SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const
 {
     MagnitudeRange mag;
 
-    int x0 = 0, x1 = v->width();
+    int x0 = 0, x1 = v->getPaintWidth();
     double s00 = 0, s01 = 0, s10 = 0, s11 = 0;
     
     if (!getXBinRange(v, x0, s00, s01)) {
@@ -1729,8 +1589,8 @@
         s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement();
     }
 
-    int s0 = int(std::min(s00, s10) + 0.0001);
-    int s1 = int(std::max(s01, s11) + 0.0001);
+    int s0 = int(min(s00, s10) + 0.0001);
+    int s1 = int(max(s01, s11) + 0.0001);
 
 //    SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl;
 
@@ -1745,13 +1605,15 @@
     }
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::updateViewMagnitudes returning from cols "
-              << s0 << " -> " << s1 << " inclusive" << endl;
+    cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols "
+         << s0 << " -> " << s1 << " inclusive" << endl;
+    cerr << "SpectrogramLayer::updateViewMagnitudes: for view id " << v->getId()
+         << ": min is " << mag.getMin() << ", max is " << mag.getMax() << endl;
 #endif
 
     if (!mag.isSet()) return false;
-    if (mag == m_viewMags[v]) return false;
-    m_viewMags[v] = mag;
+    if (mag == m_viewMags[v->getId()]) return false;
+    m_viewMags[v->getId()] = mag;
     return true;
 }
 
@@ -1761,23 +1623,27 @@
     m_synchronous = synchronous;
 }
 
+ScrollableImageCache &
+SpectrogramLayer::getImageCacheReference(const LayerGeometryProvider *view) const
+{
+    if (m_imageCaches.find(view->getId()) == m_imageCaches.end()) {
+        m_imageCaches[view->getId()] = ScrollableImageCache(view);
+    }
+    return m_imageCaches.at(view->getId());
+}
+
 void
-SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const
+SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    // What a lovely, old-fashioned function this is.
-    // It's practically FORTRAN 77 in its clarity and linearity.
-
     Profiler profiler("SpectrogramLayer::paint", false);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << endl;
+    cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
     
-    cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
+    cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
 #endif
 
     sv_frame_t startFrame = v->getStartFrame();
-    if (startFrame < 0) m_candidateFillStartFrame = 0;
-    else m_candidateFillStartFrame = startFrame;
 
     if (!m_model || !m_model->isOK() || !m_model->isReady()) {
 	return;
@@ -1795,310 +1661,135 @@
     const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false);
 
     int fftSize = getFFTSize(v);
-/*
-    FFTModel *fft = getFFTModel(v);
-    if (!fft) {
-	cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << endl;
-	return;
+
+    const View *view = v->getView();
+    ScrollableImageCache &cache = getImageCacheReference(view);
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.getValidLeft() << " width " << cache.getValidWidth() << ", height " << cache.getSize().height() << endl;
+    if (rect.x() + rect.width() + 1 < cache.getValidLeft() ||
+        rect.x() > cache.getValidRight()) {
+        cerr << "SpectrogramLayer: NOTE: requested rect is not contiguous with cache valid area" << endl;
     }
-*/
-    ImageCache &cache = m_imageCaches[v];
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::paint(): image cache valid area " << cache.
-
-validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl;
 #endif
 
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    bool stillCacheing = (m_updateTimer != 0);
-    SVDEBUG << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << endl;
-#endif
-
     int zoomLevel = v->getZoomLevel();
 
-    int x0 = 0;
-    int x1 = v->width();
-
-    bool recreateWholeImageCache = true;
-
-    x0 = rect.left();
-    x1 = rect.right() + 1;
-/*
-    double xPixelRatio = double(fft->getResolution()) / double(zoomLevel);
-    cerr << "xPixelRatio = " << xPixelRatio << endl;
-    if (xPixelRatio < 1.f) xPixelRatio = 1.f;
-*/
-    if (cache.validArea.width() > 0) {
-
-        int cw = cache.image.width();
-        int ch = cache.image.height();
-        
-	if (int(cache.zoomLevel) == zoomLevel &&
-	    cw == v->width() &&
-	    ch == v->height()) {
-
-	    if (v->getXForFrame(cache.startFrame) ==
-		v->getXForFrame(startFrame) &&
-                cache.validArea.x() <= x0 &&
-                cache.validArea.x() + cache.validArea.width() >= x1) {
-	    
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-		cerr << "SpectrogramLayer: image cache good" << endl;
-#endif
-
-		paint.drawImage(rect, cache.image, rect);
-                //!!!
-//                paint.drawImage(v->rect(), cache.image,
-//                                QRect(QPoint(0, 0), cache.image.size()));
-
-                illuminateLocalFeatures(v, paint);
-		return;
-
-	    } else {
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-		cerr << "SpectrogramLayer: image cache partially OK" << endl;
-#endif
-
-		recreateWholeImageCache = false;
-
-		int dx = v->getXForFrame(cache.startFrame) -
-		         v->getXForFrame(startFrame);
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-		cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl;
-#endif
-
-		if (dx != 0 &&
-                    dx > -cw &&
-                    dx <  cw) {
-                    
-                    int dxp = dx;
-                    if (dxp < 0) dxp = -dxp;
-                    size_t copy = (cw - dxp) * sizeof(QRgb);
-                    for (int y = 0; y < ch; ++y) {
-                        QRgb *line = (QRgb *)cache.image.scanLine(y);
-                        if (dx < 0) {
-                            memmove(line, line + dxp, copy);
-                        } else {
-                            memmove(line + dxp, line, copy);
-                        }
-                    }
-
-                    int px = cache.validArea.x();
-                    int pw = cache.validArea.width();
-
-		    if (dx < 0) {
-			x0 = cw + dx;
-			x1 = cw;
-                        px += dx;
-                        if (px < 0) {
-                            pw += px;
-                            px = 0;
-                            if (pw < 0) pw = 0;
-                        }
-		    } else {
-			x0 = 0;
-			x1 = dx;
-                        px += dx;
-                        if (px + pw > cw) {
-                            pw = int(cw) - px;
-                            if (pw < 0) pw = 0;
-                        }
-		    }
-                    
-                    cache.validArea =
-                        QRect(px, cache.validArea.y(),
-                              pw, cache.validArea.height());
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "valid area now "
-                              << px << "," << cache.validArea.y()
-                              << " " << pw << "x" << cache.validArea.height()
-                              << endl;
-#endif
-/*
-		    paint.drawImage(rect & cache.validArea,
-                                     cache.image,
-                                     rect & cache.validArea);
-*/
-                } else if (dx != 0) {
-
-                    // we scrolled too far to be of use
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "dx == " << dx << ": scrolled too far for cache to be useful" << endl;
-#endif
-
-                    cache.validArea = QRect();
-                    recreateWholeImageCache = true;
-                }
-	    }
-	} else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-	    cerr << "SpectrogramLayer: image cache useless" << endl;
-            if (int(cache.zoomLevel) != zoomLevel) {
-                cerr << "(cache zoomLevel " << cache.zoomLevel
-                          << " != " << zoomLevel << ")" << endl;
-            }
-            if (cw != v->width()) {
-                cerr << "(cache width " << cw
-                          << " != " << v->width();
-            }
-            if (ch != v->height()) {
-                cerr << "(cache height " << ch
-                          << " != " << v->height();
-            }
-#endif
-            cache.validArea = QRect();
-//            recreateWholeImageCache = true;
-	}
-    }
+    int x0 = v->getXForViewX(rect.x());
+    int x1 = v->getXForViewX(rect.x() + rect.width());
+    if (x0 < 0) x0 = 0;
+    if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth();
 
     if (updateViewMagnitudes(v)) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl;
+        cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "]" << endl;
 #endif
-        if (m_normalizeVisibleArea) {
-            cache.validArea = QRect();
-            recreateWholeImageCache = true;
+        if (m_normalization == NormalizeVisibleArea) {
+            cache.invalidate();
+        }
+    }
+
+    if (cache.getZoomLevel() != zoomLevel ||
+        cache.getSize() != v->getPaintSize()) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        cerr << "SpectrogramLayer: resizing image cache from "
+             << cache.getSize().width() << "x" << cache.getSize().height()
+             << " to "
+             << v->getPaintSize().width() << "x" << v->getPaintSize().height()
+             << " and updating zoom level from " << cache.getZoomLevel()
+             << " to " << zoomLevel
+             << endl;
+#endif
+        cache.resize(v->getPaintSize());
+        cache.setZoomLevel(zoomLevel);
+        cache.setStartFrame(startFrame);
+    }
+    
+    if (cache.isValid()) {
+        
+        if (v->getXForFrame(cache.getStartFrame()) ==
+            v->getXForFrame(startFrame) &&
+            cache.getValidLeft() <= x0 &&
+            cache.getValidRight() >= x1) {
+                
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+            cerr << "SpectrogramLayer: image cache hit!" << endl;
+#endif
+
+            paint.drawImage(rect, cache.getImage(), rect);
+
+            illuminateLocalFeatures(v, paint);
+            return;
+
+        } else {
+
+            // cache doesn't begin at the right frame or doesn't
+            // contain the complete view, but might be scrollable or
+            // partially usable
+                
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+            cerr << "SpectrogramLayer: scrolling the image cache if applicable" << endl;
+#endif
+
+            cache.scrollTo(startFrame);
+            
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+            cerr << "SpectrogramLayer: after scrolling, cache valid from "
+                 << cache.getValidLeft() << " width " << cache.getValidWidth()
+                 << endl;
+#endif
+        }
+    }
+
+    bool rightToLeft = false;
+    
+    if (!cache.isValid()) {
+        if (!m_synchronous) {
+            // When rendering the whole thing, start from somewhere near
+            // the middle so that the region of interest appears first
+
+            //!!! (perhaps we should have some cunning test to avoid
+            //!!! doing this if past repaints have appeared fast
+            //!!! enough to do the whole width in one shot)
+            if (x0 == 0 && x1 == v->getPaintWidth()) {
+                x0 = int(x1 * 0.3);
+            }
         }
     } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl;
-#endif
+        // When rendering only a part of the cache, we need to make
+        // sure that the part we're rendering is adjacent to (or
+        // overlapping) a valid area of cache, if we have one. The
+        // alternative is to ditch the valid area of cache and render
+        // only the requested area, but that's risky because this can
+        // happen when just waving the pointer over a small part of
+        // the view -- if we lose the partly-built cache every time
+        // the user does that, we'll never finish building it.
+        int left = x0;
+        int width = x1 - x0;
+        bool isLeftOfValidArea = false;
+        cache.adjustToTouchValidArea(left, width, isLeftOfValidArea);
+        x0 = left;
+        x1 = x0 + width;
+
+        // That call also told us whether we should be painting
+        // sub-regions of our target region in right-to-left order in
+        // order to ensure contiguity
+        rightToLeft = isLeftOfValidArea;
     }
-
-    if (recreateWholeImageCache) {
-        x0 = 0;
-        x1 = v->width();
-    }
-
-    struct timeval tv;
-    (void)gettimeofday(&tv, 0);
-    RealTime mainPaintStart = RealTime::fromTimeval(tv);
-
-    int paintBlockWidth = m_lastPaintBlockWidth;
-
-    if (m_synchronous) {
-        if (paintBlockWidth < x1 - x0) {
-            // always paint full width
-            paintBlockWidth = x1 - x0;
-        }
-    } else {
-        if (paintBlockWidth == 0) {
-            paintBlockWidth = (300000 / zoomLevel);
-        } else {
-            RealTime lastTime = m_lastPaintTime;
-            while (lastTime > RealTime::fromMilliseconds(200) &&
-                   paintBlockWidth > 50) {
-                paintBlockWidth /= 2;
-                lastTime = lastTime / 2;
-            }
-            while (lastTime < RealTime::fromMilliseconds(90) &&
-                   paintBlockWidth < 1500) {
-                paintBlockWidth *= 2;
-                lastTime = lastTime * 2;
-            }
-        }
-        
-        if (paintBlockWidth < 20) paintBlockWidth = 20;
-    }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << endl;
-#endif
-
+    
     // We always paint the full height when refreshing the cache.
     // Smaller heights can be used when painting direct from cache
     // (further up in this function), but we want to ensure the cache
     // is coherent without having to worry about vertical matching of
     // required and valid areas as well as horizontal.
-
-    int h = v->height();
-
-    if (cache.validArea.width() > 0) {
-
-        // If part of the cache is known to be valid, select a strip
-        // immediately to left or right of the valid part
-
-        //!!! this really needs to be coordinated with the selection
-        //!!! of m_drawBuffer boundaries in the bufferBinResolution
-        //!!! case below
-
-        int vx0 = 0, vx1 = 0;
-        vx0 = cache.validArea.x();
-        vx1 = cache.validArea.x() + cache.validArea.width();
-        
+    int h = v->getPaintHeight();
+    
+    int repaintWidth = x1 - x0;
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << endl;
-#endif         
-        if (x0 < vx0) {
-            if (x0 + paintBlockWidth < vx0) {
-                x0 = vx0 - paintBlockWidth;
-            }
-            x1 = vx0;
-        } else if (x0 >= vx1) {
-            x0 = vx1;
-            if (x1 > x0 + paintBlockWidth) {
-                x1 = x0 + paintBlockWidth;
-            }
-        } else {
-            // x0 is within the valid area
-            if (x1 > vx1) {
-                x0 = vx1;
-                if (x0 + paintBlockWidth < x1) {
-                    x1 = x0 + paintBlockWidth;
-                }
-            } else {
-                x1 = x0; // it's all valid, paint nothing
-            }
-        }
-         
-        cache.validArea = QRect
-            (std::min(vx0, x0), cache.validArea.y(),
-             std::max(vx1 - std::min(vx0, x0),
-                       x1 - std::min(vx0, x0)),
-             cache.validArea.height());
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Valid area becomes " << cache.validArea.x()
-                  << ", " << cache.validArea.y() << ", "
-                  << cache.validArea.width() << "x"
-                  << cache.validArea.height() << endl;
-#endif
-            
-    } else {
-        if (x1 > x0 + paintBlockWidth) {
-            int sfx = x1;
-            if (startFrame < 0) sfx = v->getXForFrame(0);
-            if (sfx >= x0 && sfx + paintBlockWidth <= x1) {
-                x0 = sfx;
-                x1 = x0 + paintBlockWidth;
-            } else {
-                int mid = (x1 + x0) / 2;
-                x0 = mid - paintBlockWidth/2;
-                x1 = x0 + paintBlockWidth;
-            }
-        }
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Valid area becomes " << x0 << ", 0, " << (x1-x0)
-                  << "x" << h << endl;
-#endif
-        cache.validArea = QRect(x0, 0, x1 - x0, h);
-    }
-
-/*
-    if (xPixelRatio != 1.f) {
-        x0 = int((int(x0 / xPixelRatio) - 4) * xPixelRatio + 0.0001);
-        x1 = int((int(x1 / xPixelRatio) + 4) * xPixelRatio + 0.0001);
-    }
-*/
-    int w = x1 - x0;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << endl;
+    cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1
+         << ", repaintWidth " << repaintWidth << ", h " << h
+         << ", rightToLeft " << rightToLeft << endl;
 #endif
 
     sv_samplerate_t sr = m_model->getSampleRate();
@@ -2148,30 +1839,17 @@
     int increment = getWindowIncrement();
     
     bool logarithmic = (m_frequencyScale == LogFrequencyScale);
-/*
-    double yforbin[maxbin - minbin + 1];
-
-    for (int q = minbin; q <= maxbin; ++q) {
-        double f0 = (double(q) * sr) / fftSize;
-        yforbin[q - minbin] =
-            v->getYForFrequency(f0, displayMinFreq, displayMaxFreq,
-                                logarithmic);
+
+    MagnitudeRange overallMag = m_viewMags[v->getId()];
+    bool overallMagChanged = false;
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    cerr << "SpectrogramLayer: " << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl;
+#endif
+
+    if (repaintWidth == 0) {
+        SVDEBUG << "*** NOTE: repaintWidth == 0" << endl;
     }
-*/
-    MagnitudeRange overallMag = m_viewMags[v];
-    bool overallMagChanged = false;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl;
-#endif
-
-    if (w == 0) {
-        SVDEBUG << "*** NOTE: w == 0" << endl;
-    }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    int pixels = 0;
-#endif
 
     Profiler outerprof("SpectrogramLayer::paint: all cols");
 
@@ -2188,28 +1866,34 @@
     // such boundaries at either side of the draw buffer -- one which
     // we draw up to, and one which we subsequently crop at.
 
-    bool bufferBinResolution = false;
-    if (increment > zoomLevel) bufferBinResolution = true;
+    bool bufferIsBinResolution = false;
+    if (increment > zoomLevel) bufferIsBinResolution = true;
 
     sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
     sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
 
     int bufwid;
 
-    if (bufferBinResolution) {
+    if (bufferIsBinResolution) {
 
         for (int x = x0; ; --x) {
             sv_frame_t f = v->getFrameForX(x);
             if ((f / increment) * increment == f) {
                 if (leftCropFrame == -1) leftCropFrame = f;
-                else if (x < x0 - 2) { leftBoundaryFrame = f; break; }
+                else if (x < x0 - 2) {
+                    leftBoundaryFrame = f;
+                    break;
+                }
             }
         }
-        for (int x = x0 + w; ; ++x) {
+        for (int x = x0 + repaintWidth; ; ++x) {
             sv_frame_t f = v->getFrameForX(x);
             if ((f / increment) * increment == f) {
                 if (rightCropFrame == -1) rightCropFrame = f;
-                else if (x > x0 + w + 2) { rightBoundaryFrame = f; break; }
+                else if (x > x0 + repaintWidth + 2) {
+                    rightBoundaryFrame = f;
+                    break;
+                }
             }
         }
 #ifdef DEBUG_SPECTROGRAM_REPAINT
@@ -2221,7 +1905,7 @@
 
     } else {
         
-        bufwid = w;
+        bufwid = repaintWidth;
     }
 
     vector<int> binforx(bufwid);
@@ -2229,10 +1913,9 @@
     
     bool usePeaksCache = false;
 
-    if (bufferBinResolution) {
+    if (bufferIsBinResolution) {
         for (int x = 0; x < bufwid; ++x) {
             binforx[x] = int(leftBoundaryFrame / increment) + x;
-//            cerr << "binforx[" << x << "] = " << binforx[x] << endl;
         }
         m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8);
     } else {
@@ -2244,21 +1927,47 @@
                 binforx[x] = -1; //???
             }
         }
-        if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() < h) {
+        if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() != h) {
             m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8);
         }
         usePeaksCache = (increment * 8) < zoomLevel;
         if (m_colourScale == PhaseColourScale) usePeaksCache = false;
     }
 
-// No longer exists in Qt5:    m_drawBuffer.setNumColors(256);
     for (int pixel = 0; pixel < 256; ++pixel) {
         m_drawBuffer.setColor((unsigned char)pixel,
                               m_palette.getColour((unsigned char)pixel).rgb());
     }
 
     m_drawBuffer.fill(0);
-    
+    int attainedBufwid = bufwid;
+
+    double softTimeLimit;
+
+    if (m_synchronous) {
+
+        // must paint the whole thing for synchronous mode, so give
+        // "no timeout"
+        softTimeLimit = 0.0;
+        
+    } else if (bufferIsBinResolution) {
+        
+        // calculating boundaries later will be too fiddly for partial
+        // paints, and painting should be fast anyway when this is the
+        // case because it means we're well zoomed in
+        softTimeLimit = 0.0;
+
+    } else {
+
+        // neither limitation applies, so use a short soft limit
+
+        if (m_binDisplay == PeakFrequencies) {
+            softTimeLimit = 0.15;
+        } else {
+            softTimeLimit = 0.1;
+        }
+    }
+
     if (m_binDisplay != PeakFrequencies) {
 
         for (int y = 0; y < h; ++y) {
@@ -2267,53 +1976,60 @@
                 binfory[y] = -1;
             } else {
                 binfory[y] = q0;
-//                cerr << "binfory[" << y << "] = " << binfory[y] << endl;
             }
         }
 
-        paintDrawBuffer(v, bufwid, h, binforx, binfory, usePeaksCache,
-                        overallMag, overallMagChanged);
+        attainedBufwid = 
+            paintDrawBuffer(v, bufwid, h, binforx, binfory,
+                            usePeaksCache,
+                            overallMag, overallMagChanged,
+                            rightToLeft,
+                            softTimeLimit);
 
     } else {
 
-        paintDrawBufferPeakFrequencies(v, bufwid, h, binforx,
-                                       minbin, maxbin,
-                                       displayMinFreq, displayMaxFreq,
-                                       logarithmic,
-                                       overallMag, overallMagChanged);
+        attainedBufwid = 
+            paintDrawBufferPeakFrequencies(v, bufwid, h, binforx,
+                                           minbin, maxbin,
+                                           displayMinFreq, displayMaxFreq,
+                                           logarithmic,
+                                           overallMag, overallMagChanged,
+                                           rightToLeft,
+                                           softTimeLimit);
     }
 
-/*
-    for (int x = 0; x < w / xPixelRatio; ++x) {
-
-        Profiler innerprof("SpectrogramLayer::paint: 1 pixel column");
-
-        runOutOfData = !paintColumnValues(v, fft, x0, x,
-                                          minbin, maxbin,
-                                          displayMinFreq, displayMaxFreq,
-                                          xPixelRatio,
-                                          h, yforbin);
-
-        if (runOutOfData) {
+    int failedToRepaint = bufwid - attainedBufwid;
+
+    int paintedLeft = x0;
+    int paintedWidth = x1 - x0;
+    
+    if (failedToRepaint > 0) {
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            cerr << "Run out of data -- dropping out of loop" << endl;
+        cerr << "SpectrogramLayer::paint(): Failed to repaint " << failedToRepaint << " of " << bufwid
+             << " columns in time (so managed to repaint " << bufwid - failedToRepaint << ")" << endl;
 #endif
-            break;
+
+        if (rightToLeft) {
+            paintedLeft += failedToRepaint;
         }
+
+        paintedWidth -= failedToRepaint;
+
+        if (paintedWidth < 0) {
+            paintedWidth = 0;
+        }
+        
+    } else if (failedToRepaint < 0) {
+        cerr << "WARNING: failedToRepaint < 0 (= " << failedToRepaint << ")"
+             << endl;
+        failedToRepaint = 0;
     }
-*/
+
+    if (overallMagChanged) {
+        m_viewMags[v->getId()] = overallMag;
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-//    cerr << pixels << " pixels drawn" << endl;
-#endif
-
-    if (overallMagChanged) {
-        m_viewMags[v] = overallMag;
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl;
-#endif
-    } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl;
+        cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "] - will be updating" << endl;
 #endif
     }
 
@@ -2321,129 +2037,153 @@
 
     Profiler profiler2("SpectrogramLayer::paint: draw image");
 
-    if (recreateWholeImageCache) {
+    if (paintedWidth > 0) {
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "Recreating image cache: width = " << v->width()
-                  << ", height = " << h << endl;
-#endif
-	cache.image = QImage(v->width(), h, QImage::Format_ARGB32_Premultiplied);
-    }
-
-    if (w > 0) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        SVDEBUG << "Painting " << w << "x" << h
-                  << " from draw buffer at " << 0 << "," << 0
-                  << " to " << w << "x" << h << " on cache at "
+        cerr << "SpectrogramLayer: Copying " << paintedWidth << "x" << h
+                  << " from draw buffer at " << paintedLeft - x0 << "," << 0
+                  << " to " << paintedWidth << "x" << h << " on cache at "
                   << x0 << "," << 0 << endl;
 #endif
 
-        QPainter cachePainter(&cache.image);
-
-        if (bufferBinResolution) {
+        if (bufferIsBinResolution) {
+
             int scaledLeft = v->getXForFrame(leftBoundaryFrame);
             int scaledRight = v->getXForFrame(rightBoundaryFrame);
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "Rescaling image from " << bufwid
+            cerr << "SpectrogramLayer: Rescaling image from " << bufwid
                  << "x" << h << " to "
                  << scaledRight-scaledLeft << "x" << h << endl;
 #endif
+
             Preferences::SpectrogramXSmoothing xsmoothing = 
                 Preferences::getInstance()->getSpectrogramXSmoothing();
-//            SVDEBUG << "xsmoothing == " << xsmoothing << endl;
+
             QImage scaled = m_drawBuffer.scaled
                 (scaledRight - scaledLeft, h,
                  Qt::IgnoreAspectRatio,
                  ((xsmoothing == Preferences::SpectrogramXInterpolated) ?
                   Qt::SmoothTransformation : Qt::FastTransformation));
+            
             int scaledLeftCrop = v->getXForFrame(leftCropFrame);
             int scaledRightCrop = v->getXForFrame(rightCropFrame);
+
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            SVDEBUG << "Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to "
+            cerr << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to "
                  << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl;
 #endif
-            cachePainter.drawImage
-                (QRect(scaledLeftCrop, 0,
-                       scaledRightCrop - scaledLeftCrop, h),
-                 scaled,
-                 QRect(scaledLeftCrop - scaledLeft, 0,
-                       scaledRightCrop - scaledLeftCrop, h));
+
+            int targetLeft = scaledLeftCrop;
+            if (targetLeft < 0) {
+                targetLeft = 0;
+            }
+
+            int targetWidth = scaledRightCrop - targetLeft;
+            if (targetLeft + targetWidth > cache.getSize().width()) {
+                targetWidth = cache.getSize().width() - targetLeft;
+            }
+            
+            int sourceLeft = targetLeft - scaledLeft;
+            if (sourceLeft < 0) {
+                sourceLeft = 0;
+            }
+            
+            int sourceWidth = targetWidth;
+
+            if (targetWidth > 0) {
+                cache.drawImage
+                    (targetLeft,
+                     targetWidth,
+                     scaled,
+                     sourceLeft,
+                     sourceWidth);
+            }
+
         } else {
-            cachePainter.drawImage(QRect(x0, 0, w, h),
-                                   m_drawBuffer,
-                                   QRect(0, 0, w, h));
+
+            cache.drawImage(paintedLeft, paintedWidth,
+                            m_drawBuffer,
+                            paintedLeft - x0, paintedWidth);
         }
-
-        cachePainter.end();
     }
 
-    QRect pr = rect & cache.validArea;
-
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "Painting " << pr.width() << "x" << pr.height()
+    cerr << "SpectrogramLayer: Cache valid area now from " << cache.getValidLeft()
+         << " width " << cache.getValidWidth() << ", height "
+         << cache.getSize().height() << endl;
+#endif
+
+    QRect pr = rect & cache.getValidArea();
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    cerr << "SpectrogramLayer: Copying " << pr.width() << "x" << pr.height()
               << " from cache at " << pr.x() << "," << pr.y()
               << " to window" << endl;
 #endif
 
-    paint.drawImage(pr.x(), pr.y(), cache.image,
+    paint.drawImage(pr.x(), pr.y(), cache.getImage(),
                     pr.x(), pr.y(), pr.width(), pr.height());
-    //!!!
-//    paint.drawImage(v->rect(), cache.image,
-//                    QRect(QPoint(0, 0), cache.image.size()));
-
-    cache.startFrame = startFrame;
-    cache.zoomLevel = zoomLevel;
 
     if (!m_synchronous) {
 
-        if (!m_normalizeVisibleArea || !overallMagChanged) {
-    
-            if (cache.validArea.x() > 0) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                SVDEBUG << "SpectrogramLayer::paint() updating left (0, "
-                          << cache.validArea.x() << ")" << endl;
-#endif
-                v->update(0, 0, cache.validArea.x(), h);
+        if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) {
+
+            QRect areaLeft(0, 0, cache.getValidLeft(), h);
+            QRect areaRight(cache.getValidRight(), 0,
+                            cache.getSize().width() - cache.getValidRight(), h);
+
+            bool haveSpaceLeft = (areaLeft.width() > 0);
+            bool haveSpaceRight = (areaRight.width() > 0);
+
+            bool updateLeft = haveSpaceLeft;
+            bool updateRight = haveSpaceRight;
+            
+            if (updateLeft && updateRight) {
+                if (rightToLeft) {
+                    // we just did something adjoining the cache on
+                    // its left side, so now do something on its right
+                    updateLeft = false;
+                } else {
+                    updateRight = false;
+                }
             }
             
-            if (cache.validArea.x() + cache.validArea.width() <
-                cache.image.width()) {
+            if (updateLeft) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-                SVDEBUG << "SpectrogramLayer::paint() updating right ("
-                          << cache.validArea.x() + cache.validArea.width()
-                          << ", "
-                          << cache.image.width() - (cache.validArea.x() +
-                                                     cache.validArea.width())
-                          << ")" << endl;
+                cerr << "SpectrogramLayer::paint() updating left ("
+                     << areaLeft.x() << ", "
+                     << areaLeft.width() << ")" << endl;
 #endif
-                v->update(cache.validArea.x() + cache.validArea.width(),
-                          0,
-                          cache.image.width() - (cache.validArea.x() +
-                                                  cache.validArea.width()),
-                          h);
+                v->updatePaintRect(areaLeft);
             }
+            
+            if (updateRight) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                cerr << "SpectrogramLayer::paint() updating right ("
+                     << areaRight.x() << ", "
+                     << areaRight.width() << ")" << endl;
+#endif
+                v->updatePaintRect(areaRight);
+            }
+            
         } else {
             // overallMagChanged
             cerr << "\noverallMagChanged - updating all\n" << endl;
-            cache.validArea = QRect();
-            v->update();
+            cache.invalidate();
+            v->updatePaintRect(v->getPaintRect());
         }
     }
 
     illuminateLocalFeatures(v, paint);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::paint() returning" << endl;
+    cerr << "SpectrogramLayer::paint() returning" << endl;
 #endif
-
-    if (!m_synchronous) {
-        m_lastPaintBlockWidth = paintBlockWidth;
-        (void)gettimeofday(&tv, 0);
-        m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart;
-    }
 }
 
-bool
-SpectrogramLayer::paintDrawBufferPeakFrequencies(View *v,
+int
+SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v,
                                                  int w,
                                                  int h,
                                                  const vector<int> &binforx,
@@ -2453,18 +2193,20 @@
                                                  double displayMaxFreq,
                                                  bool logarithmic,
                                                  MagnitudeRange &overallMag,
-                                                 bool &overallMagChanged) const
+                                                 bool &overallMagChanged,
+                                                 bool rightToLeft,
+                                                 double softTimeLimit) const
 {
     Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies");
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
+    cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
 #endif
     if (minbin < 0) minbin = 0;
     if (maxbin < 0) maxbin = minbin+1;
 
     FFTModel *fft = getFFTModel(v);
-    if (!fft) return false;
+    if (!fft) return 0;
 
     FFTModel::PeakSet peakfreqs;
 
@@ -2476,7 +2218,27 @@
     float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float));
 #endif
 
-    for (int x = 0; x < w; ++x) {
+    int minColumns = 4;
+    bool haveTimeLimits = (softTimeLimit > 0.0);
+    double hardTimeLimit = softTimeLimit * 2.0;
+    bool overridingSoftLimit = false;
+    auto startTime = chrono::steady_clock::now();
+    
+    int start = 0;
+    int finish = w;
+    int step = 1;
+
+    if (rightToLeft) {
+        start = w-1;
+        finish = -1;
+        step = -1;
+    }
+    
+    int columnCount = 0;
+    
+    for (int x = start; x != finish; x += step) {
+        
+        ++columnCount;
         
         if (binforx[x] < 0) continue;
 
@@ -2491,15 +2253,6 @@
 
             if (sx < 0 || sx >= int(fft->getWidth())) continue;
 
-            if (!m_synchronous) {
-                if (!fft->isColumnAvailable(sx)) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "Met unavailable column at col " << sx << endl;
-#endif
-                    return false;
-                }
-            }
-
             MagnitudeRange mag;
 
             if (sx != psx) {
@@ -2507,14 +2260,14 @@
                                                     minbin, maxbin - 1);
                 if (m_colourScale == PhaseColourScale) {
                     fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1);
-                } else if (m_normalizeColumns) {
+                } else if (m_normalization == NormalizeColumns) {
                     fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
-                } else if (m_normalizeHybrid) {
-                    fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
-                    double max = fft->getMaximumMagnitudeAt(sx);
+                } else if (m_normalization == NormalizeHybrid) {
+                    float max = fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
                     if (max > 0.f) {
                         for (int i = minbin; i <= maxbin; ++i) {
-                            values[i - minbin] = float(values[i - minbin] * log10(max));
+                            values[i - minbin] = float(values[i - minbin] *
+                                                       log10f(max));
                         }
                     }
                 } else {
@@ -2535,7 +2288,7 @@
                 double value = values[bin - minbin];
 
                 if (m_colourScale != PhaseColourScale) {
-                    if (!m_normalizeColumns && !m_normalizeHybrid) {
+                    if (m_normalization != NormalizeColumns) {
                         value /= (m_fftSize/2.0);
                     }
                     mag.sample(float(value));
@@ -2565,20 +2318,50 @@
                 }
             }
         }
+
+        if (haveTimeLimits) {
+            if (columnCount >= minColumns) {
+                auto t = chrono::steady_clock::now();
+                double diff = chrono::duration<double>(t - startTime).count();
+                if (diff > hardTimeLimit) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                    cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: hard limit " << hardTimeLimit << " sec exceeded after "
+                         << columnCount << " columns with time " << diff << endl;
+#endif
+                    return columnCount;
+                } else if (diff > softTimeLimit && !overridingSoftLimit) {
+                    // If we're more than half way through by the time
+                    // we reach the soft limit, ignore it (though
+                    // still respect the hard limit, above). Otherwise
+                    // respect the soft limit and return now.
+                    if (columnCount > w/2) {
+                        overridingSoftLimit = true;
+                    } else {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                        cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: soft limit " << softTimeLimit << " sec exceeded after "
+                             << columnCount << " columns with time " << diff << endl;
+#endif
+                        return columnCount;
+                    }
+                }                        
+            }
+        }
     }
 
-    return true;
+    return columnCount;
 }
 
-bool
-SpectrogramLayer::paintDrawBuffer(View *v,
+int
+SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v,
                                   int w,
                                   int h,
                                   const vector<int> &binforx,
                                   const vector<double> &binfory,
                                   bool usePeaksCache,
                                   MagnitudeRange &overallMag,
-                                  bool &overallMagChanged) const
+                                  bool &overallMagChanged,
+                                  bool rightToLeft,
+                                  double softTimeLimit) const
 {
     Profiler profiler("SpectrogramLayer::paintDrawBuffer");
 
@@ -2586,7 +2369,7 @@
     int maxbin = int(binfory[h-1]);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
+    cerr << "SpectrogramLayer::paintDrawBuffer: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
 #endif
     if (minbin < 0) minbin = 0;
     if (maxbin < 0) maxbin = minbin+1;
@@ -2595,7 +2378,7 @@
     FFTModel *fft = 0;
     int divisor = 1;
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    cerr << "Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl;
+    cerr << "SpectrogramLayer::paintDrawBuffer: Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl;
 #endif
     if (usePeaksCache) { //!!!
         sourceModel = getPeakCache(v);
@@ -2606,7 +2389,7 @@
         sourceModel = fft = getFFTModel(v);
     }
 
-    if (!sourceModel) return false;
+    if (!sourceModel) return 0;
 
     bool interpolate = false;
     Preferences::SpectrogramSmoothing smoothing = 
@@ -2632,7 +2415,27 @@
     const float *values = autoarray;
     DenseThreeDimensionalModel::Column c;
 
-    for (int x = 0; x < w; ++x) {
+    int minColumns = 4;
+    bool haveTimeLimits = (softTimeLimit > 0.0);
+    double hardTimeLimit = softTimeLimit * 2.0;
+    bool overridingSoftLimit = false;
+    auto startTime = chrono::steady_clock::now();
+    
+    int start = 0;
+    int finish = w;
+    int step = 1;
+
+    if (rightToLeft) {
+        start = w-1;
+        finish = -1;
+        step = -1;
+    }
+
+    int columnCount = 0;
+    
+    for (int x = start; x != finish; x += step) {
+
+        ++columnCount;
         
         if (binforx[x] < 0) continue;
 
@@ -2656,48 +2459,39 @@
 
             if (sx < 0 || sx >= int(sourceModel->getWidth())) continue;
 
-            if (!m_synchronous) {
-                if (!sourceModel->isColumnAvailable(sx)) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    cerr << "Met unavailable column at col " << sx << endl;
-#endif
-                    return false;
-                }
-            }
-
             MagnitudeRange mag;
 
             if (sx != psx) {
                 if (fft) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-                    SVDEBUG << "Retrieving column " << sx << " from fft directly" << endl;
+//                    cerr << "Retrieving column " << sx << " from fft directly" << endl;
 #endif
                     if (m_colourScale == PhaseColourScale) {
                         fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1);
-                    } else if (m_normalizeColumns) {
+                    } else if (m_normalization == NormalizeColumns) {
                         fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
-                    } else if (m_normalizeHybrid) {
-                        fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
-                        double max = fft->getMaximumMagnitudeAt(sx);
+                    } else if (m_normalization == NormalizeHybrid) {
+                        float max = fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
+                        float scale = log10f(max + 1.f);
+//                        cout << "sx = " << sx << ", max = " << max << ", log10(max) = " << log10(max) << ", scale = " << scale << endl;
                         for (int i = minbin; i <= maxbin; ++i) {
-                            if (max > 0.0) {
-                                autoarray[i - minbin] = float(autoarray[i - minbin] * log10(max));
-                            }
+                            autoarray[i - minbin] *= scale;
                         }
                     } else {
                         fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
                     }
                 } else {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-                    SVDEBUG << "Retrieving column " << sx << " from peaks cache" << endl;
+//                    cerr << "Retrieving column " << sx << " from peaks cache" << endl;
 #endif
                     c = sourceModel->getColumn(sx);
-                    if (m_normalizeColumns || m_normalizeHybrid) {
+                    if (m_normalization == NormalizeColumns ||
+                        m_normalization == NormalizeHybrid) {
                         for (int y = 0; y < h; ++y) {
                             if (c[y] > columnMax) columnMax = c[y];
                         }
                     }
-                    values = c.constData() + minbin;
+                    values = c.data() + minbin;
                 }
                 psx = sx;
             }
@@ -2735,7 +2529,8 @@
                     value = prop * v0 + (1.0 - prop) * v1;
 
                     if (m_colourScale != PhaseColourScale) {
-                        if (!m_normalizeColumns) {
+                        if (m_normalization != NormalizeColumns &&
+                            m_normalization != NormalizeHybrid) {
                             value /= (m_fftSize/2.0);
                         }
                         mag.sample(float(value));
@@ -2760,7 +2555,8 @@
                         }
 
                         if (m_colourScale != PhaseColourScale) {
-                            if (!m_normalizeColumns) {
+                            if (m_normalization != NormalizeColumns &&
+                                m_normalization != NormalizeHybrid) {
                                 value /= (m_fftSize/2.0);
                             }
                             mag.sample(float(value));
@@ -2794,11 +2590,12 @@
             double peak = peaks[y];
             
             if (m_colourScale != PhaseColourScale &&
-                (m_normalizeColumns || m_normalizeHybrid) && 
+                (m_normalization == NormalizeColumns ||
+                 m_normalization == NormalizeHybrid) &&
                 columnMax > 0.f) {
                 peak /= columnMax;
-                if (m_normalizeHybrid) {
-                    peak *= log10(columnMax);
+                if (m_normalization == NormalizeHybrid) {
+                    peak *= log10(columnMax + 1.f);
                 }
             }
             
@@ -2806,13 +2603,41 @@
 
             m_drawBuffer.setPixel(x, h-y-1, peakpix);
         }
+
+        if (haveTimeLimits) {
+            if (columnCount >= minColumns) {
+                auto t = chrono::steady_clock::now();
+                double diff = chrono::duration<double>(t - startTime).count();
+                if (diff > hardTimeLimit) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                    cerr << "SpectrogramLayer::paintDrawBuffer: hard limit " << hardTimeLimit << " sec exceeded after "
+                         << columnCount << " columns with time " << diff << endl;
+#endif
+                    return columnCount;
+                } else if (diff > softTimeLimit && !overridingSoftLimit) {
+                    // If we're more than half way through by the time
+                    // we reach the soft limit, ignore it (though
+                    // still respect the hard limit, above). Otherwise
+                    // respect the soft limit and return now.
+                    if (columnCount > w/2) {
+                        overridingSoftLimit = true;
+                    } else {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                        cerr << "SpectrogramLayer::paintDrawBuffer: soft limit " << softTimeLimit << " sec exceeded after "
+                             << columnCount << " columns with time " << diff << endl;
+#endif
+                        return columnCount;
+                    }
+                }                        
+            }
+        }
     }
 
-    return true;
+    return columnCount;
 }
 
 void
-SpectrogramLayer::illuminateLocalFeatures(View *v, QPainter &paint) const
+SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const
 {
     Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
 
@@ -2851,7 +2676,7 @@
 }
 
 double
-SpectrogramLayer::getYForFrequency(const View *v, double frequency) const
+SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const
 {
     return v->getYForFrequency(frequency,
 			       getEffectiveMinFrequency(),
@@ -2860,7 +2685,7 @@
 }
 
 double
-SpectrogramLayer::getFrequencyForY(const View *v, int y) const
+SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const
 {
     return v->getFrequencyForY(y,
 			       getEffectiveMinFrequency(),
@@ -2869,23 +2694,25 @@
 }
 
 int
-SpectrogramLayer::getCompletion(View *v) const
+SpectrogramLayer::getCompletion(LayerGeometryProvider *v) const
 {
-    if (m_updateTimer == 0) return 100;
-    if (m_fftModels.find(v) == m_fftModels.end()) return 100;
-
-    int completion = m_fftModels[v].first->getCompletion();
+    const View *view = v->getView();
+    
+    if (m_fftModels.find(view->getId()) == m_fftModels.end()) return 100;
+
+    int completion = m_fftModels[view->getId()]->getCompletion();
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    SVDEBUG << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
+    cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
 #endif
     return completion;
 }
 
 QString
-SpectrogramLayer::getError(View *v) const
+SpectrogramLayer::getError(LayerGeometryProvider *v) const
 {
-    if (m_fftModels.find(v) == m_fftModels.end()) return "";
-    return m_fftModels[v].first->getError();
+    const View *view = v->getView();
+    if (m_fftModels.find(view->getId()) == m_fftModels.end()) return "";
+    return m_fftModels[view->getId()]->getError();
 }
 
 bool
@@ -2946,7 +2773,7 @@
 }
 
 bool
-SpectrogramLayer::getYScaleValue(const View *v, int y,
+SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
                                  double &value, QString &unit) const
 {
     value = getFrequencyForY(v, y);
@@ -2955,7 +2782,7 @@
 }
 
 bool
-SpectrogramLayer::snapToFeatureFrame(View *,
+SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *,
                                      sv_frame_t &frame,
 				     int &resolution,
 				     SnapType snap) const
@@ -2978,14 +2805,15 @@
 } 
 
 void
-SpectrogramLayer::measureDoubleClick(View *v, QMouseEvent *e)
+SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    ImageCache &cache = m_imageCaches[v];
-
-    cerr << "cache width: " << cache.image.width() << ", height: "
-              << cache.image.height() << endl;
-
-    QImage image = cache.image;
+    const View *view = v->getView();
+    ScrollableImageCache &cache = getImageCacheReference(view);
+
+    cerr << "cache width: " << cache.getSize().width() << ", height: "
+         << cache.getSize().height() << endl;
+
+    QImage image = cache.getImage();
 
     ImageRegionFinder finder;
     QRect rect = finder.findRegionExtents(&image, e->pos());
@@ -2998,11 +2826,11 @@
 }
 
 bool
-SpectrogramLayer::getCrosshairExtents(View *v, QPainter &paint,
+SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
                                       QPoint cursorPos,
-                                      std::vector<QRect> &extents) const
+                                      vector<QRect> &extents) const
 {
-    QRect vertical(cursorPos.x() - 12, 0, 12, v->height());
+    QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
     extents.push_back(vertical);
 
     QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1);
@@ -3021,14 +2849,14 @@
     extents.push_back(pitch);
 
     QRect rt(cursorPos.x(),
-             v->height() - paint.fontMetrics().height() - 2,
+             v->getPaintHeight() - paint.fontMetrics().height() - 2,
              paint.fontMetrics().width("1234.567 s"),
              paint.fontMetrics().height());
     extents.push_back(rt);
 
     int w(paint.fontMetrics().width("1234567890") + 2);
     QRect frame(cursorPos.x() - w - 2,
-                v->height() - paint.fontMetrics().height() - 2,
+                v->getPaintHeight() - paint.fontMetrics().height() - 2,
                 w,
                 paint.fontMetrics().height());
     extents.push_back(frame);
@@ -3037,7 +2865,7 @@
 }
 
 void
-SpectrogramLayer::paintCrosshairs(View *v, QPainter &paint,
+SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                   QPoint cursorPos) const
 {
     paint.save();
@@ -3052,7 +2880,7 @@
     paint.setPen(m_crosshairColour);
 
     paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y());
-    paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->height());
+    paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight());
     
     double fundamental = getFrequencyForY(v, cursorPos.y());
 
@@ -3077,12 +2905,12 @@
     QString frameLabel = QString("%1").arg(frame);
     v->drawVisibleText(paint,
                        cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
-                       v->height() - 2,
+                       v->getPaintHeight() - 2,
                        frameLabel,
                        View::OutlinedText);
     v->drawVisibleText(paint,
                        cursorPos.x() + 2,
-                       v->height() - 2,
+                       v->getPaintHeight() - 2,
                        rtLabel,
                        View::OutlinedText);
 
@@ -3091,7 +2919,7 @@
     while (harmonic < 100) {
 
         int hy = int(lrint(getYForFrequency(v, fundamental * harmonic)));
-        if (hy < 0 || hy > v->height()) break;
+        if (hy < 0 || hy > v->getPaintHeight()) break;
         
         int len = 7;
 
@@ -3115,7 +2943,7 @@
 }
 
 QString
-SpectrogramLayer::getFeatureDescription(View *v, QPoint &pos) const
+SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
     int y = pos.y();
@@ -3237,7 +3065,7 @@
 }
 
 int
-SpectrogramLayer::getVerticalScaleWidth(View *, bool detailed, QPainter &paint) const
+SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
 {
     if (!m_model || !m_model->isOK()) return 0;
 
@@ -3258,7 +3086,7 @@
 }
 
 void
-SpectrogramLayer::paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const
+SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) {
 	return;
@@ -3300,12 +3128,18 @@
 	paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
 
 	QString top, bottom;
-        double min = m_viewMags[v].getMin();
-        double max = m_viewMags[v].getMax();
+        double min = m_viewMags[v->getId()].getMin();
+        double max = m_viewMags[v->getId()].getMax();
 
         double dBmin = AudioLevel::multiplier_to_dB(min);
         double dBmax = AudioLevel::multiplier_to_dB(max);
 
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        cerr << "paintVerticalScale: for view id " << v->getId()
+             << ": min = " << min << ", max = " << max
+             << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl;
+#endif
+        
         if (dBmax < -60.f) dBmax = -60.f;
         else top = QString("%1").arg(lrint(dBmax));
 
@@ -3372,10 +3206,10 @@
 
     int bin = -1;
 
-    for (int y = 0; y < v->height(); ++y) {
+    for (int y = 0; y < v->getPaintHeight(); ++y) {
 
 	double q0, q1;
-	if (!getYBinRange(v, v->height() - y, q0, q1)) continue;
+	if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue;
 
 	int vy;
 
@@ -3400,7 +3234,7 @@
 	paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
 
 	if (h - vy - textHeight >= -2) {
-	    int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw);
+	    int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw);
 	    paint.drawText(tx, h - vy + toff, text);
 	}
 
@@ -3589,7 +3423,7 @@
 }
 
 void
-SpectrogramLayer::updateMeasureRectYCoords(View *v, const MeasureRect &r) const
+SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
 {
     int y0 = 0;
     if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY));
@@ -3603,7 +3437,7 @@
 }
 
 void
-SpectrogramLayer::setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const
+SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
 {
     if (start) {
         r.startY = getFrequencyForY(v, y);
@@ -3647,13 +3481,29 @@
 	.arg(m_frequencyScale)
 	.arg(m_binDisplay);
 
-    s += QString("normalizeColumns=\"%1\" "
-                 "normalizeVisibleArea=\"%2\" "
-                 "normalizeHybrid=\"%3\" ")
-	.arg(m_normalizeColumns ? "true" : "false")
-        .arg(m_normalizeVisibleArea ? "true" : "false")
-        .arg(m_normalizeHybrid ? "true" : "false");
-
+    // New-style normalization attributes, allowing for more types of
+    // normalization in future: write out the column normalization
+    // type separately, and then whether we are normalizing visible
+    // area as well afterwards
+    
+    s += QString("columnNormalization=\"%1\" ")
+        .arg(m_normalization == NormalizeColumns ? "peak" :
+             m_normalization == NormalizeHybrid ? "hybrid" : "none");
+
+    // Old-style normalization attribute. We *don't* write out
+    // normalizeHybrid here because the only release that would accept
+    // it (Tony v1.0) has a totally different scale factor for
+    // it. We'll just have to accept that session files from Tony
+    // v2.0+ will look odd in Tony v1.0
+    
+    s += QString("normalizeColumns=\"%1\" ")
+	.arg(m_normalization == NormalizeColumns ? "true" : "false");
+
+    // And this applies to both old- and new-style attributes
+    
+    s += QString("normalizeVisibleArea=\"%1\" ")
+        .arg(m_normalization == NormalizeVisibleArea ? "true" : "false");
+    
     Layer::toXml(stream, indent, extraAttributes + " " + s);
 }
 
@@ -3718,16 +3568,54 @@
 	attributes.value("binDisplay").toInt(&ok);
     if (ok) setBinDisplay(binDisplay);
 
-    bool normalizeColumns =
-	(attributes.value("normalizeColumns").trimmed() == "true");
-    setNormalizeColumns(normalizeColumns);
+    bool haveNewStyleNormalization = false;
+    
+    QString columnNormalization = attributes.value("columnNormalization");
+
+    if (columnNormalization != "") {
+
+        haveNewStyleNormalization = true;
+
+        if (columnNormalization == "peak") {
+            setNormalization(NormalizeColumns);
+        } else if (columnNormalization == "hybrid") {
+            setNormalization(NormalizeHybrid);
+        } else if (columnNormalization == "none") {
+            // do nothing
+        } else {
+            cerr << "NOTE: Unknown or unsupported columnNormalization attribute \""
+                 << columnNormalization << "\"" << endl;
+        }
+    }
+
+    if (!haveNewStyleNormalization) {
+
+        bool normalizeColumns =
+            (attributes.value("normalizeColumns").trimmed() == "true");
+        if (normalizeColumns) {
+            setNormalization(NormalizeColumns);
+        }
+
+        bool normalizeHybrid =
+            (attributes.value("normalizeHybrid").trimmed() == "true");
+        if (normalizeHybrid) {
+            setNormalization(NormalizeHybrid);
+        }
+    }
 
     bool normalizeVisibleArea =
 	(attributes.value("normalizeVisibleArea").trimmed() == "true");
-    setNormalizeVisibleArea(normalizeVisibleArea);
-
-    bool normalizeHybrid =
-	(attributes.value("normalizeHybrid").trimmed() == "true");
-    setNormalizeHybrid(normalizeHybrid);
+    if (normalizeVisibleArea) {
+        setNormalization(NormalizeVisibleArea);
+    }
+
+    if (!haveNewStyleNormalization && m_normalization == NormalizeHybrid) {
+        // Tony v1.0 is (and hopefully will remain!) the only released
+        // SV-a-like to use old-style attributes when saving sessions
+        // that ask for hybrid normalization. It saves them with the
+        // wrong gain factor, so hack in a fix for that here -- this
+        // gives us backward but not forward compatibility.
+        setGain(m_gain / float(m_fftSize / 2));
+    }
 }
     
--- a/layer/SpectrogramLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SpectrogramLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _SPECTROGRAM_LAYER_H_
-#define _SPECTROGRAM_LAYER_H_
+#ifndef SPECTROGRAM_LAYER_H
+#define SPECTROGRAM_LAYER_H
 
 #include "SliceableLayer.h"
 #include "base/Window.h"
@@ -25,6 +25,8 @@
 #include "data/model/DenseTimeValueModel.h"
 #include "data/model/FFTModel.h"
 
+#include "ScrollableImageCache.h"
+
 #include <QMutex>
 #include <QWaitCondition>
 #include <QImage>
@@ -57,23 +59,23 @@
 
     virtual const ZoomConstraint *getZoomConstraint() const { return this; }
     virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
     virtual void setSynchronousPainting(bool synchronous);
 
-    virtual int getVerticalScaleWidth(View *v, bool detailed, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const;
 
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos,
+    virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos,
                                      std::vector<QRect> &extents) const;
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const;
+    virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void measureDoubleClick(View *, QMouseEvent *);
+    virtual void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *);
 
     virtual bool hasLightBackground() const;
 
@@ -88,6 +90,8 @@
                                          int *min, int *max, int *deflt) const;
     virtual QString getPropertyValueLabel(const PropertyName &,
 					  int value) const;
+    virtual QString getPropertyValueIconName(const PropertyName &,
+                                             int value) const;
     virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const;
     virtual void setProperty(const PropertyName &, int value);
 
@@ -170,27 +174,24 @@
      */
     void setBinDisplay(BinDisplay);
     BinDisplay getBinDisplay() const;
- 
-    /**
-     * Normalize each column to its maximum value, independent of its
-     * neighbours.
-     */
-    void setNormalizeColumns(bool n);
-    bool getNormalizeColumns() const;
+
+    enum Normalization {
+        NoNormalization,
+        NormalizeColumns,
+        NormalizeVisibleArea,
+        NormalizeHybrid
+    };
 
     /**
-     * Normalize each value against the maximum in the visible region.
+     * Specify the normalization mode for bin values.
      */
-    void setNormalizeVisibleArea(bool n);
-    bool getNormalizeVisibleArea() const;
+    void setNormalization(Normalization);
+    Normalization getNormalization() const;
 
     /**
-     * Normalize each column to its maximum value, and then scale by
-     * the log of the (absolute) maximum value.
+     * Specify the colour map. See ColourMapper for the colour map
+     * values.
      */
-    void setNormalizeHybrid(bool n);
-    bool getNormalizeHybrid() const;
-    
     void setColourMap(int map);
     int getColourMap() const;
 
@@ -210,11 +211,11 @@
         return ColourHasMeaningfulValue;
     }
 
-    double getYForFrequency(const View *v, double frequency) const;
-    double getFrequencyForY(const View *v, int y) const;
+    double getYForFrequency(const LayerGeometryProvider *v, double frequency) const;
+    double getFrequencyForY(const LayerGeometryProvider *v, int y) const;
 
-    virtual int getCompletion(View *v) const;
-    virtual QString getError(View *v) const;
+    virtual int getCompletion(LayerGeometryProvider *v) const;
+    virtual QString getError(LayerGeometryProvider *v) const;
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
@@ -223,16 +224,16 @@
 
     virtual bool setDisplayExtents(double min, double max);
 
-    virtual bool getYScaleValue(const View *, int, double &, QString &) const;
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int, double &, QString &) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
 
     void setProperties(const QXmlAttributes &attributes);
 
-    virtual void setLayerDormant(const View *v, bool dormant);
+    virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant);
 
-    virtual bool isLayerScrollable(const View *) const { return false; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; }
 
     virtual int getVerticalZoomSteps(int &defaultStep) const;
     virtual int getCurrentVerticalZoomStep() const;
@@ -247,40 +248,34 @@
     
     void preferenceChanged(PropertyContainer::PropertyName name);
 
-    void fillTimerTimedOut();
-
 protected:
     const DenseTimeValueModel *m_model; // I do not own this
 
     int                 m_channel;
-    int              m_windowSize;
+    int                 m_windowSize;
     WindowType          m_windowType;
-    int              m_windowHopLevel;
-    int              m_zeroPadLevel;
-    int              m_fftSize;
+    int                 m_windowHopLevel;
+    int                 m_zeroPadLevel;
+    int                 m_fftSize;
     float               m_gain;
     float               m_initialGain;
     float               m_threshold;
     float               m_initialThreshold;
     int                 m_colourRotation;
     int                 m_initialRotation;
-    int              m_minFrequency;
-    int              m_maxFrequency;
-    int              m_initialMaxFrequency;
+    int                 m_minFrequency;
+    int                 m_maxFrequency;
+    int                 m_initialMaxFrequency;
     ColourScale         m_colourScale;
     int                 m_colourMap;
     QColor              m_crosshairColour;
     FrequencyScale      m_frequencyScale;
     BinDisplay          m_binDisplay;
-    bool                m_normalizeColumns;
-    bool                m_normalizeVisibleArea;
-    bool                m_normalizeHybrid;
+    Normalization       m_normalization;
     int                 m_lastEmittedZoomStep;
     bool                m_synchronous;
 
     mutable bool        m_haveDetailedScale;
-    mutable int         m_lastPaintBlockWidth;
-    mutable RealTime    m_lastPaintTime;
 
     enum { NO_VALUE = 0 }; // colour index for unused pixels
 
@@ -301,22 +296,10 @@
 
     Palette m_palette;
 
-    /**
-     * ImageCache covers the area of the view, at view resolution.
-     * Not all of it is necessarily valid at once (it is refreshed
-     * in parts when scrolling, for example).
-     */
-    struct ImageCache
-    {
-        QImage image;
-        QRect validArea;
-        sv_frame_t startFrame;
-        int zoomLevel;
-    };
-    typedef std::map<const View *, ImageCache> ViewImageCache;
+    typedef std::map<int, ScrollableImageCache> ViewImageCache; // key is view id
     void invalidateImageCaches();
-    void invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame);
     mutable ViewImageCache m_imageCaches;
+    ScrollableImageCache &getImageCacheReference(const LayerGeometryProvider *) const;
 
     /**
      * When painting, we draw directly onto the draw buffer and then
@@ -326,19 +309,16 @@
      */
     mutable QImage m_drawBuffer;
 
-    mutable QTimer *m_updateTimer;
-
-    mutable sv_frame_t m_candidateFillStartFrame;
     bool m_exiting;
 
     void initialisePalette();
     void rotatePalette(int distance);
 
-    unsigned char getDisplayValue(View *v, double input) const;
+    unsigned char getDisplayValue(LayerGeometryProvider *v, double input) const;
 
     int getColourScaleWidth(QPainter &) const;
 
-    void illuminateLocalFeatures(View *v, QPainter &painter) const;
+    void illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &painter) const;
 
     double getEffectiveMinFrequency() const;
     double getEffectiveMaxFrequency() const;
@@ -349,16 +329,16 @@
     // position, if the spectrogram has oversampling smoothing.  Use
     // getSmoothedYBinRange to obtain that.
 
-    bool getXBinRange(View *v, int x, double &windowMin, double &windowMax) const;
-    bool getYBinRange(View *v, int y, double &freqBinMin, double &freqBinMax) const;
-    bool getSmoothedYBinRange(View *v, int y, double &freqBinMin, double &freqBinMax) const;
+    bool getXBinRange(LayerGeometryProvider *v, int x, double &windowMin, double &windowMax) const;
+    bool getYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const;
+    bool getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const;
 
-    bool getYBinSourceRange(View *v, int y, double &freqMin, double &freqMax) const;
-    bool getAdjustedYBinSourceRange(View *v, int x, int y,
+    bool getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const;
+    bool getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
 				    double &freqMin, double &freqMax,
 				    double &adjFreqMin, double &adjFreqMax) const;
-    bool getXBinSourceRange(View *v, int x, RealTime &timeMin, RealTime &timeMax) const;
-    bool getXYBinSourceRange(View *v, int x, int y, double &min, double &max,
+    bool getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &timeMin, RealTime &timeMax) const;
+    bool getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &min, double &max,
 			     double &phaseMin, double &phaseMax) const;
 
     int getWindowIncrement() const {
@@ -367,15 +347,14 @@
         else return m_windowSize / (1 << (m_windowHopLevel - 1));
     }
 
-    int getZeroPadLevel(const View *v) const;
-    int getFFTSize(const View *v) const;
-    FFTModel *getFFTModel(const View *v) const;
-    Dense3DModelPeakCache *getPeakCache(const View *v) const;
+    int getZeroPadLevel(const LayerGeometryProvider *v) const;
+    int getFFTSize(const LayerGeometryProvider *v) const;
+    FFTModel *getFFTModel(const LayerGeometryProvider *v) const;
+    Dense3DModelPeakCache *getPeakCache(const LayerGeometryProvider *v) const;
     void invalidateFFTModels();
 
-    typedef std::pair<FFTModel *, sv_frame_t> FFTFillPair; // model, last fill
-    typedef std::map<const View *, FFTFillPair> ViewFFTMap;
-    typedef std::map<const View *, Dense3DModelPeakCache *> PeakCacheMap;
+    typedef std::map<int, FFTModel *> ViewFFTMap; // key is view id
+    typedef std::map<int, Dense3DModelPeakCache *> PeakCacheMap; // key is view id
     mutable ViewFFTMap m_fftModels;
     mutable PeakCacheMap m_peakCaches;
     mutable Model *m_sliceableModel;
@@ -422,29 +401,33 @@
         float m_max;
     };
 
-    typedef std::map<const View *, MagnitudeRange> ViewMagMap;
+    typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id
     mutable ViewMagMap m_viewMags;
     mutable std::vector<MagnitudeRange> m_columnMags;
     void invalidateMagnitudes();
-    bool updateViewMagnitudes(View *v) const;
-    bool paintDrawBuffer(View *v, int w, int h,
-                         const std::vector<int> &binforx,
-                         const std::vector<double> &binfory,
-                         bool usePeaksCache,
-                         MagnitudeRange &overallMag,
-                         bool &overallMagChanged) const;
-    bool paintDrawBufferPeakFrequencies(View *v, int w, int h,
-                                        const std::vector<int> &binforx,
-                                        int minbin,
-                                        int maxbin,
-                                        double displayMinFreq,
-                                        double displayMaxFreq,
-                                        bool logarithmic,
-                                        MagnitudeRange &overallMag,
-                                        bool &overallMagChanged) const;
+    bool updateViewMagnitudes(LayerGeometryProvider *v) const;
+    int paintDrawBuffer(LayerGeometryProvider *v, int w, int h,
+                        const std::vector<int> &binforx,
+                        const std::vector<double> &binfory,
+                        bool usePeaksCache,
+                        MagnitudeRange &overallMag,
+                        bool &overallMagChanged,
+                        bool rightToLeft,
+                        double softTimeLimit) const;
+    int paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h,
+                                       const std::vector<int> &binforx,
+                                       int minbin,
+                                       int maxbin,
+                                       double displayMinFreq,
+                                       double displayMaxFreq,
+                                       bool logarithmic,
+                                       MagnitudeRange &overallMag,
+                                       bool &overallMagChanged,
+                                       bool rightToLeft,
+                                       double softTimeLimit) const;
 
-    virtual void updateMeasureRectYCoords(View *v, const MeasureRect &r) const;
-    virtual void setMeasureRectYCoord(View *v, MeasureRect &r, bool start, int y) const;
+    virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const;
+    virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const;
 };
 
 #endif
--- a/layer/SpectrumLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SpectrumLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -380,18 +380,18 @@
 }
 
 bool
-SpectrumLayer::getXScaleValue(const View *v, int x, 
+SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, 
                               double &value, QString &unit) const
 {
     if (m_xorigins.find(v) == m_xorigins.end()) return false;
     int xorigin = m_xorigins.find(v)->second;
-    value = getFrequencyForX(x - xorigin, v->width() - xorigin - 1);
+    value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1);
     unit = "Hz";
     return true;
 }
 
 bool
-SpectrumLayer::getYScaleValue(const View *v, int y,
+SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
                               double &value, QString &unit) const
 {
     value = getValueForY(y, v);
@@ -413,7 +413,7 @@
 }
 
 bool
-SpectrumLayer::getYScaleDifference(const View *v, int y0, int y1,
+SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
                                    double &diff, QString &unit) const
 {
     bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
@@ -423,14 +423,14 @@
 
 
 bool
-SpectrumLayer::getCrosshairExtents(View *v, QPainter &paint,
+SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
                                    QPoint cursorPos,
                                    std::vector<QRect> &extents) const
 {
-    QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->height() - cursorPos.y());
+    QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
     extents.push_back(vertical);
 
-    QRect horizontal(0, cursorPos.y(), v->width(), 12);
+    QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
     extents.push_back(horizontal);
 
     int hoffset = 2;
@@ -449,14 +449,14 @@
     extents.push_back(log);
 
     QRect freq(cursorPos.x(),
-               v->height() - paint.fontMetrics().height() - hoffset,
+               v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
                paint.fontMetrics().width("123456 Hz") + 2,
                paint.fontMetrics().height());
     extents.push_back(freq);
 
     int w(paint.fontMetrics().width("C#10+50c") + 2);
     QRect pitch(cursorPos.x() - w,
-                v->height() - paint.fontMetrics().height() - hoffset,
+                v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
                 w,
                 paint.fontMetrics().height());
     extents.push_back(pitch);
@@ -465,7 +465,7 @@
 }
 
 void
-SpectrumLayer::paintCrosshairs(View *v, QPainter &paint,
+SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                QPoint cursorPos) const
 {
     if (!m_sliceableModel) return;
@@ -481,10 +481,10 @@
     paint.setPen(mapper.getContrastingColour());
 
     int xorigin = m_xorigins[v];
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
     
-    paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y());
-    paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height());
+    paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
+    paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
     
     double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
 
@@ -493,7 +493,7 @@
 
     v->drawVisibleText(paint,
                        cursorPos.x() + 2,
-                       v->height() - 2 - hoffset,
+                       v->getPaintHeight() - 2 - hoffset,
                        QString("%1 Hz").arg(fundamental),
                        View::OutlinedText);
 
@@ -501,7 +501,7 @@
         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
         v->drawVisibleText(paint,
                            cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
-                           v->height() - 2 - hoffset,
+                           v->getPaintHeight() - 2 - hoffset,
                            pitchLabel,
                            View::OutlinedText);
     }
@@ -531,7 +531,7 @@
         int hx = int(lrint(getXForFrequency(fundamental * harmonic, w)));
         hx += xorigin;
 
-        if (hx < xorigin || hx > v->width()) break;
+        if (hx < xorigin || hx > v->getPaintWidth()) break;
         
         int len = 7;
 
@@ -555,7 +555,7 @@
 }
 
 QString
-SpectrumLayer::getFeatureDescription(View *v, QPoint &p) const
+SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
 {
     if (!m_sliceableModel) return "";
 
@@ -644,7 +644,7 @@
 }
 
 void
-SpectrumLayer::paint(View *v, QPainter &paint, QRect rect) const
+SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_originModel || !m_originModel->isOK() ||
         !m_originModel->isReady()) {
@@ -663,7 +663,7 @@
     double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
 
     int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
-    int w = v->width() - xorigin - 1;
+    int w = v->getPaintWidth() - xorigin - 1;
 
     int pkh = 0;
 //!!!    if (m_binScale == LogBins) {
@@ -723,7 +723,7 @@
             (void)getYForValue(values[bin], v, norm); // don't need return value, need norm
 
             paint.setPen(mapper.map(norm));
-            paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1);
+            paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
         }
 
         paint.restore();
@@ -743,7 +743,7 @@
 //    if (m_binScale == LogBins) {
 
 //        int pkh = 10;
-        int h = v->height();
+        int h = v->getPaintHeight();
 
         // piano keyboard
         //!!! should be in a new paintHorizontalScale()?
--- a/layer/SpectrumLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/SpectrumLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -39,13 +39,13 @@
     void setModel(DenseTimeValueModel *model);
     virtual const Model *getModel() const { return m_originModel; }
 
-    virtual bool getCrosshairExtents(View *, QPainter &, QPoint cursorPos,
+    virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos,
                                      std::vector<QRect> &extents) const;
-    virtual void paintCrosshairs(View *, QPainter &, QPoint) const;
+    virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
     virtual VerticalPosition getPreferredFrameCountPosition() const {
 	return PositionTop;
@@ -67,16 +67,16 @@
     virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual bool getXScaleValue(const View *v, int x,
+    virtual bool getXScaleValue(const LayerGeometryProvider *v, int x,
                                 double &value, QString &unit) const;
 
-    virtual bool getYScaleValue(const View *, int y,
+    virtual bool getYScaleValue(const LayerGeometryProvider *, int y,
                                 double &value, QString &unit) const;
 
-    virtual bool getYScaleDifference(const View *, int y0, int y1,
+    virtual bool getYScaleDifference(const LayerGeometryProvider *, int y0, int y1,
                                      double &diff, QString &unit) const;
 
-    virtual bool isLayerScrollable(const View *) const { return false; }
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const { return false; }
 
     void setChannel(int);
     int getChannel() const { return m_channel; }
@@ -93,7 +93,7 @@
     void setShowPeaks(bool);
     bool getShowPeaks() const { return m_showPeaks; }
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
--- a/layer/TextLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TextLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -102,7 +102,7 @@
 }
 
 bool
-TextLayer::isLayerScrollable(const View *v) const
+TextLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
@@ -110,12 +110,12 @@
 
 
 TextModel::PointList
-TextLayer::getLocalPoints(View *v, int x, int y) const
+TextLayer::getLocalPoints(LayerGeometryProvider *v, int x, int y) const
 {
     if (!m_model) return TextModel::PointList();
 
     sv_frame_t frame0 = v->getFrameForX(-150);
-    sv_frame_t frame1 = v->getFrameForX(v->width() + 150);
+    sv_frame_t frame1 = v->getFrameForX(v->getPaintWidth() + 150);
     
     TextModel::PointList points(m_model->getPoints(frame0, frame1));
 
@@ -139,9 +139,9 @@
 	    (QRect(0, 0, 150, 200),
 	     Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
 
-	if (py + rect.height() > v->height()) {
-	    if (rect.height() > v->height()) py = 0;
-	    else py = v->height() - rect.height() - 1;
+	if (py + rect.height() > v->getPaintHeight()) {
+	    if (rect.height() > v->getPaintHeight()) py = 0;
+	    else py = v->getPaintHeight() - rect.height() - 1;
 	}
 
 	if (x >= px && x < px + rect.width() &&
@@ -154,7 +154,7 @@
 }
 
 bool
-TextLayer::getPointToDrag(View *v, int x, int y, TextModel::Point &p) const
+TextLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, TextModel::Point &p) const
 {
     if (!m_model) return false;
 
@@ -182,7 +182,7 @@
 }
 
 QString
-TextLayer::getFeatureDescription(View *v, QPoint &pos) const
+TextLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -220,7 +220,7 @@
 //!!! too much overlap with TimeValueLayer/TimeInstantLayer
 
 bool
-TextLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+TextLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 			      int &resolution,
 			      SnapType snap) const
 {
@@ -292,21 +292,21 @@
 }
 
 int
-TextLayer::getYForHeight(View *v, double height) const
+TextLayer::getYForHeight(LayerGeometryProvider *v, double height) const
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
     return h - int(height * h);
 }
 
 double
-TextLayer::getHeightForY(View *v, int y) const
+TextLayer::getHeightForY(LayerGeometryProvider *v, int y) const
 {
-    int h = v->height();
+    int h = v->getPaintHeight();
     return double(h - y) / h;
 }
 
 void
-TextLayer::paint(View *v, QPainter &paint, QRect rect) const
+TextLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -347,7 +347,7 @@
     int boxMaxHeight = 200;
 
     paint.save();
-    paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->height());
+    paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->getPaintHeight());
     
     for (TextModel::PointList::const_iterator i = points.begin();
 	 i != points.end(); ++i) {
@@ -380,9 +380,9 @@
 	QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
 	boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
 
-	if (y + boxRect.height() > v->height()) {
-	    if (boxRect.height() > v->height()) y = 0;
-	    else y = v->height() - boxRect.height() - 1;
+	if (y + boxRect.height() > v->getPaintHeight()) {
+	    if (boxRect.height() > v->getPaintHeight()) y = 0;
+	    else y = v->getPaintHeight() - boxRect.height() - 1;
 	}
 
 	boxRect = QRect(x, y, boxRect.width(), boxRect.height());
@@ -411,7 +411,7 @@
 }
 
 void
-TextLayer::drawStart(View *v, QMouseEvent *e)
+TextLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -437,7 +437,7 @@
 }
 
 void
-TextLayer::drawDrag(View *v, QMouseEvent *e)
+TextLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -456,13 +456,13 @@
 }
 
 void
-TextLayer::drawEnd(View *v, QMouseEvent *)
+TextLayer::drawEnd(LayerGeometryProvider *v, QMouseEvent *)
 {
 //    SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
 
     bool ok = false;
-    QString label = QInputDialog::getText(v, tr("Enter label"),
+    QString label = QInputDialog::getText(v->getView(), tr("Enter label"),
 					  tr("Please enter a new label:"),
 					  QLineEdit::Normal, "", &ok);
 
@@ -480,7 +480,7 @@
 }
 
 void
-TextLayer::eraseStart(View *v, QMouseEvent *e)
+TextLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -495,12 +495,12 @@
 }
 
 void
-TextLayer::eraseDrag(View *, QMouseEvent *)
+TextLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-TextLayer::eraseEnd(View *v, QMouseEvent *e)
+TextLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -521,7 +521,7 @@
 }
 
 void
-TextLayer::editStart(View *v, QMouseEvent *e)
+TextLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 //    SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
@@ -543,7 +543,7 @@
 }
 
 void
-TextLayer::editDrag(View *v, QMouseEvent *e)
+TextLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -570,7 +570,7 @@
 }
 
 void
-TextLayer::editEnd(View *, QMouseEvent *)
+TextLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
     if (!m_model || !m_editing) return;
@@ -598,7 +598,7 @@
 }
 
 bool
-TextLayer::editOpen(View *v, QMouseEvent *e)
+TextLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -608,7 +608,7 @@
     QString label = text.label;
 
     bool ok = false;
-    label = QInputDialog::getText(v, tr("Enter label"),
+    label = QInputDialog::getText(v->getView(), tr("Enter label"),
 				  tr("Please enter a new label:"),
 				  QLineEdit::Normal, label, &ok);
     if (ok && label != text.label) {
@@ -699,7 +699,7 @@
 }
 
 void
-TextLayer::copy(View *v, Selection s, Clipboard &to)
+TextLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -717,7 +717,7 @@
 }
 
 bool
-TextLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
+TextLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
     if (!m_model) return false;
 
@@ -728,7 +728,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), 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);
--- a/layer/TextLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TextLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -32,35 +32,35 @@
 public:
     TextLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
-    virtual bool editOpen(View *, QMouseEvent *); // on double-click
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *); // on double-click
 
     virtual const Model *getModel() const { return m_model; }
     void setModel(TextModel *model);
@@ -74,16 +74,16 @@
 					  int value) const;
     virtual void setProperty(const PropertyName &, int value);
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const;
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
@@ -91,14 +91,14 @@
     void setProperties(const QXmlAttributes &attributes);
 
 protected:
-    int getYForHeight(View *v, double height) const;
-    double getHeightForY(View *v, int y) const;
+    int getYForHeight(LayerGeometryProvider *v, double height) const;
+    double getHeightForY(LayerGeometryProvider *v, int y) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    TextModel::PointList getLocalPoints(View *v, int x, int y) const;
+    TextModel::PointList getLocalPoints(LayerGeometryProvider *v, int x, int y) const;
 
-    bool getPointToDrag(View *v, int x, int y, TextModel::Point &) const;
+    bool getPointToDrag(LayerGeometryProvider *v, int x, int y, TextModel::Point &) const;
 
     TextModel *m_model;
     bool m_editing;
--- a/layer/TimeInstantLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TimeInstantLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -147,14 +147,14 @@
 }
 
 bool
-TimeInstantLayer::isLayerScrollable(const View *v) const
+TimeInstantLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
 }
 
 SparseOneDimensionalModel::PointList
-TimeInstantLayer::getLocalPoints(View *v, int x) const
+TimeInstantLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     // Return a set of points that all have the same frame number, the
     // nearest to the given x coordinate, and that are within a
@@ -162,7 +162,7 @@
 
     if (!m_model) return SparseOneDimensionalModel::PointList();
 
-    long frame = v->getFrameForX(x);
+    sv_frame_t frame = v->getFrameForX(x);
 
     SparseOneDimensionalModel::PointList onPoints =
 	m_model->getPoints(frame);
@@ -213,7 +213,7 @@
 }
 
 QString
-TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const
+TimeInstantLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -229,7 +229,7 @@
 	}
     }
 
-    long useFrame = points.begin()->frame;
+    sv_frame_t useFrame = points.begin()->frame;
 
     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
     
@@ -249,7 +249,7 @@
 }
 
 bool
-TimeInstantLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+TimeInstantLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				     int &resolution,
 				     SnapType snap) const
 {
@@ -321,7 +321,7 @@
 }
 
 void
-TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const
+TimeInstantLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -329,8 +329,8 @@
 
     int x0 = rect.left(), x1 = rect.right();
 
-    long frame0 = v->getFrameForX(x0);
-    long frame1 = v->getFrameForX(x1);
+    sv_frame_t frame0 = v->getFrameForX(x0);
+    sv_frame_t frame1 = v->getFrameForX(x1);
 
     SparseOneDimensionalModel::PointList points(m_model->getPoints
 						(frame0, frame1));
@@ -367,7 +367,7 @@
 //	      << m_model->getResolution() << " frames" << endl;
 
     QPoint localPos;
-    long illuminateFrame = -1;
+    sv_frame_t illuminateFrame = -1;
 
     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
 	SparseOneDimensionalModel::PointList localPoints =
@@ -403,16 +403,16 @@
 	}
 		
 	if (p.frame == illuminateFrame) {
-	    paint.setPen(getForegroundQColor(v));
+	    paint.setPen(getForegroundQColor(v->getView()));
 	} else {
 	    paint.setPen(brushColour);
 	}
 
 	if (m_plotStyle == PlotInstants) {
 	    if (iw > 1) {
-		paint.drawRect(x, 0, iw - 1, v->height() - 1);
+		paint.drawRect(x, 0, iw - 1, v->getPaintHeight() - 1);
 	    } else {
-		paint.drawLine(x, 0, x, v->height() - 1);
+		paint.drawLine(x, 0, x, v->getPaintHeight() - 1);
 	    }
 	} else {
 
@@ -431,11 +431,11 @@
 	    if (nx >= x) {
 		
 		if (illuminateFrame != p.frame &&
-		    (nx < x + 5 || x >= v->width() - 1)) {
+		    (nx < x + 5 || x >= v->getPaintWidth() - 1)) {
 		    paint.setPen(Qt::NoPen);
 		}
 
-                paint.drawRect(x, -1, nx - x, v->height() + 1);
+                paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
 	    }
 
 	    odd = !odd;
@@ -466,7 +466,7 @@
 }
 
 void
-TimeInstantLayer::drawStart(View *v, QMouseEvent *e)
+TimeInstantLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl;
@@ -474,7 +474,7 @@
 
     if (!m_model) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -489,7 +489,7 @@
 }
 
 void
-TimeInstantLayer::drawDrag(View *v, QMouseEvent *e)
+TimeInstantLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl;
@@ -497,7 +497,7 @@
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
     m_editingCommand->deletePoint(m_editingPoint);
@@ -506,7 +506,7 @@
 }
 
 void
-TimeInstantLayer::drawEnd(View *, QMouseEvent *)
+TimeInstantLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl;
@@ -523,7 +523,7 @@
 }
 
 void
-TimeInstantLayer::eraseStart(View *v, QMouseEvent *e)
+TimeInstantLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -541,12 +541,12 @@
 }
 
 void
-TimeInstantLayer::eraseDrag(View *, QMouseEvent *)
+TimeInstantLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-TimeInstantLayer::eraseEnd(View *v, QMouseEvent *e)
+TimeInstantLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -567,7 +567,7 @@
 }
 
 void
-TimeInstantLayer::editStart(View *v, QMouseEvent *e)
+TimeInstantLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl;
@@ -589,7 +589,7 @@
 }
 
 void
-TimeInstantLayer::editDrag(View *v, QMouseEvent *e)
+TimeInstantLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl;
@@ -597,7 +597,7 @@
 
     if (!m_model || !m_editing) return;
 
-    long frame = v->getFrameForX(e->x());
+    sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
     frame = frame / m_model->getResolution() * m_model->getResolution();
 
@@ -612,7 +612,7 @@
 }
 
 void
-TimeInstantLayer::editEnd(View *, QMouseEvent *)
+TimeInstantLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl;
@@ -631,7 +631,7 @@
 }
 
 bool
-TimeInstantLayer::editOpen(View *v, QMouseEvent *e)
+TimeInstantLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -747,7 +747,7 @@
 }
 
 void
-TimeInstantLayer::copy(View *v, Selection s, Clipboard &to)
+TimeInstantLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -765,7 +765,7 @@
 }
 
 bool
-TimeInstantLayer::paste(View *v, const Clipboard &from, sv_frame_t frameOffset, bool)
+TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool)
 {
     if (!m_model) return false;
 
@@ -776,7 +776,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted instants?"),
+            QMessageBox::question(v->getView(), 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);
--- a/layer/TimeInstantLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TimeInstantLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -33,35 +33,35 @@
     TimeInstantLayer();
     virtual ~TimeInstantLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
     virtual QString getLabelPreceding(sv_frame_t) const;
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -84,11 +84,11 @@
     void setPlotStyle(PlotStyle style);
     PlotStyle getPlotStyle() const { return m_plotStyle; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool needsTextLabelHeight() const { return m_model->hasTextLabels(); }
 
@@ -109,14 +109,14 @@
         }
     }
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
 protected:
-    SparseOneDimensionalModel::PointList getLocalPoints(View *v, int) const;
+    SparseOneDimensionalModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    bool clipboardAlignmentDiffers(View *v, const Clipboard &) const;
+    bool clipboardAlignmentDiffers(LayerGeometryProvider *v, const Clipboard &) const;
 
     SparseOneDimensionalModel *m_model;
     bool m_editing;
--- a/layer/TimeRulerLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TimeRulerLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -26,12 +26,11 @@
 
 #include <iostream>
 #include <cmath>
+#include <stdexcept>
 
 //#define DEBUG_TIME_RULER_LAYER 1
 
 
-
-
 TimeRulerLayer::TimeRulerLayer() :
     SingleColourLayer(),
     m_model(0),
@@ -49,7 +48,7 @@
 }
 
 bool
-TimeRulerLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
                                    int &resolution, SnapType snap) const
 {
     if (!m_model) {
@@ -88,7 +87,7 @@
         
     case SnapNearest:
     {
-        if (labs(frame - left) > labs(right - frame)) {
+        if (llabs(frame - left) > llabs(right - frame)) {
             frame = right;
         } else {
             frame = left;
@@ -141,7 +140,7 @@
 }
 
 int
-TimeRulerLayer::getMajorTickSpacing(View *v, bool &quarterTicks) const
+TimeRulerLayer::getMajorTickSpacing(LayerGeometryProvider *v, bool &quarterTicks) const
 {
     // return value is in milliseconds
 
@@ -158,7 +157,7 @@
     RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
     RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
 
-    int count = v->width() / minPixelSpacing;
+    int count = v->getPaintWidth() / minPixelSpacing;
     if (count < 1) count = 1;
     RealTime rtGap = (rtEnd - rtStart) / count;
 
@@ -182,6 +181,8 @@
     } else {
 	incms = 1;
 	int ms = rtGap.msec();
+//        cerr << "rtGap.msec = " << ms << ", rtGap = " << rtGap << ", count = " << count << endl;
+//        cerr << "startFrame = " << startFrame << ", endFrame = " << endFrame << " rtStart = " << rtStart << ", rtEnd = " << rtEnd << endl;
 	if (ms > 0) { incms *= 10; ms /= 10; }
 	if (ms > 0) { incms *= 10; ms /= 10; }
 	if (ms > 0) { incms *= 5; ms /= 5; }
@@ -192,7 +193,7 @@
 }
 
 void
-TimeRulerLayer::paint(View *v, QPainter &paint, QRect rect) const
+TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
 #ifdef DEBUG_TIME_RULER_LAYER
     SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y()
@@ -241,6 +242,9 @@
     // time < 0 which would cut it in half
     int minlabel = 1; // ms
 
+    // used for a sanity check
+    sv_frame_t prevframe = 0;
+    
     while (1) {
 
         // frame is used to determine where to draw the lines, so it
@@ -253,10 +257,17 @@
         frame /= v->getZoomLevel();
         frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel
 
+        if (frame == prevframe && prevframe != 0) {
+            cerr << "ERROR: frame == prevframe (== " << frame
+                 << ") in TimeRulerLayer::paint" << endl;
+            throw std::logic_error("frame == prevframe in TimeRulerLayer::paint");
+        }
+        prevframe = frame;
+        
         int x = v->getXForFrame(frame);
 
 #ifdef DEBUG_TIME_RULER_LAYER
-        SVDEBUG << "Considering frame = " << frame << ", x = " << x << endl;
+        cerr << "Considering frame = " << frame << ", x = " << x << endl;
 #endif
 
         if (x >= rect.x() + rect.width() + 50) {
@@ -287,11 +298,11 @@
             }
 
             paint.setPen(greyColour);
-            paint.drawLine(x, 0, x, v->height());
+            paint.drawLine(x, 0, x, v->getPaintHeight());
 
             paint.setPen(getBaseQColor());
             paint.drawLine(x, 0, x, 5);
-            paint.drawLine(x, v->height() - 6, x, v->height() - 1);
+            paint.drawLine(x, v->getPaintHeight() - 6, x, v->getPaintHeight() - 1);
 
             int y;
             switch (m_labelHeight) {
@@ -300,16 +311,16 @@
                 y = 6 + metrics.ascent();
                 break;
             case LabelMiddle:
-                y = v->height() / 2 - metrics.height() / 2 + metrics.ascent();
+                y = v->getPaintHeight() / 2 - metrics.height() / 2 + metrics.ascent();
                 break;
             case LabelBottom:
-                y = v->height() - metrics.height() + metrics.ascent() - 6;
+                y = v->getPaintHeight() - metrics.height() + metrics.ascent() - 6;
             }
 
             if (v->getViewManager() && v->getViewManager()->getOverlayMode() !=
                 ViewManager::NoOverlays) {
 
-                if (v->getLayer(0) == this) {
+                if (v->getView()->getLayer(0) == this) {
                     // backmost layer, don't worry about outlining the text
                     paint.drawText(x+2 - tw/2, y, text);
                 } else {
@@ -344,14 +355,14 @@
 	    if (ticks == 10) {
 		if ((i % 2) == 1) {
 		    if (i == 5) {
-			paint.drawLine(x, 0, x, v->height());
+			paint.drawLine(x, 0, x, v->getPaintHeight());
 		    } else sz = 3;
 		} else {
 		    sz = 7;
 		}
 	    }
 	    paint.drawLine(x, 0, x, sz);
-	    paint.drawLine(x, v->height() - sz - 1, x, v->height() - 1);
+	    paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1);
 	}
 
 	ms += incms;
--- a/layer/TimeRulerLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TimeRulerLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -32,7 +32,7 @@
 public:
     TimeRulerLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
     void setModel(Model *);
     virtual const Model *getModel() const { return m_model; }
@@ -41,7 +41,7 @@
     void setLabelHeight(LabelHeight h) { m_labelHeight = h; }
     LabelHeight getLabelHeight() const { return m_labelHeight; }
 
-    virtual bool snapToFeatureFrame(View *, sv_frame_t &, int &, SnapType) const;
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *, sv_frame_t &, int &, SnapType) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourIrrelevant;
@@ -53,7 +53,7 @@
 
     virtual QString getLayerPresentationName() const;
 
-    virtual int getVerticalScaleWidth(View *, bool, QPainter &) const { return 0; }
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const { return 0; }
 
     virtual void toXml(QTextStream &stream, QString indent = "",
                        QString extraAttributes = "") const;
@@ -68,7 +68,7 @@
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
-    int getMajorTickSpacing(View *, bool &quarterTicks) const;
+    int getMajorTickSpacing(LayerGeometryProvider *, bool &quarterTicks) const;
 };
 
 #endif
--- a/layer/TimeValueLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TimeValueLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -316,7 +316,7 @@
 }
 
 bool
-TimeValueLayer::isLayerScrollable(const View *v) const
+TimeValueLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     // We don't illuminate sections in the line or curve modes, so
     // they're always scrollable
@@ -530,7 +530,7 @@
 }
 
 SparseTimeValueModel::PointList
-TimeValueLayer::getLocalPoints(View *v, int x) const
+TimeValueLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
     if (!m_model) return SparseTimeValueModel::PointList();
 
@@ -587,7 +587,7 @@
 }
 
 QString
-TimeValueLayer::getFeatureDescription(View *v, QPoint &pos) const
+TimeValueLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -641,7 +641,7 @@
 }
 
 bool
-TimeValueLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
+TimeValueLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				   int &resolution,
 				   SnapType snap) const
 {
@@ -713,7 +713,7 @@
 }
 
 bool
-TimeValueLayer::snapToSimilarFeature(View *v, sv_frame_t &frame,
+TimeValueLayer::snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
                                      int &resolution,
                                      SnapType snap) const
 {
@@ -794,7 +794,7 @@
 }
 
 void
-TimeValueLayer::getScaleExtents(View *v, double &min, double &max, bool &log) const
+TimeValueLayer::getScaleExtents(LayerGeometryProvider *v, double &min, double &max, bool &log) const
 {
     min = 0.0;
     max = 0.0;
@@ -830,11 +830,11 @@
 }
 
 int
-TimeValueLayer::getYForValue(View *v, double val) const
+TimeValueLayer::getYForValue(LayerGeometryProvider *v, double val) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -851,11 +851,11 @@
 }
 
 double
-TimeValueLayer::getValueForY(View *v, int y) const
+TimeValueLayer::getValueForY(LayerGeometryProvider *v, int y) const
 {
     double min = 0.0, max = 0.0;
     bool logarithmic = false;
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     getScaleExtents(v, min, max, logarithmic);
 
@@ -877,7 +877,7 @@
 }
 
 QColor
-TimeValueLayer::getColourForValue(View *v, double val) const
+TimeValueLayer::getColourForValue(LayerGeometryProvider *v, double val) const
 {
     double min, max;
     bool log;
@@ -908,7 +908,7 @@
 }
 
 void
-TimeValueLayer::paint(View *v, QPainter &paint, QRect rect) const
+TimeValueLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) return;
 
@@ -943,8 +943,8 @@
     double max = m_model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
-    int origin = int(nearbyint(v->height() -
-			       (-min * v->height()) / (max - min)));
+    int origin = int(nearbyint(v->getPaintHeight() -
+			       (-min * v->getPaintHeight()) / (max - min)));
 
     QPoint localPos;
     sv_frame_t illuminateFrame = -1;
@@ -978,7 +978,7 @@
         textY = v->getTextLabelHeight(this, paint);
     } else {
         int originY = getYForValue(v, 0.f);
-        if (originY > 0 && originY < v->height()) {
+        if (originY > 0 && originY < v->getPaintHeight()) {
             paint.save();
             paint.setPen(getPartialShades(v)[1]);
             paint.drawLine(x0, originY, x1, originY);
@@ -1183,12 +1183,12 @@
             if (!illuminate) {
                 if (!m_drawSegmentDivisions ||
                     nx < x + 5 ||
-                    x >= v->width() - 1) {
+                    x >= v->getPaintWidth() - 1) {
                     paint.setPen(Qt::NoPen);
                 }
 	    }
 
-	    paint.drawRect(x, -1, nx - x, v->height() + 1);
+	    paint.drawRect(x, -1, nx - x, v->getPaintHeight() + 1);
 	}
 
         if (v->shouldShowFeatureLabels()) {
@@ -1231,7 +1231,7 @@
 	paint.drawPath(path);
     } else if ((m_plotStyle == PlotCurve || m_plotStyle == PlotLines)
                && !path.isEmpty()) {
-	paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->width());
+	paint.setRenderHint(QPainter::Antialiasing, pointCount <= v->getPaintWidth());
 	paint.drawPath(path);
     }
 
@@ -1242,7 +1242,7 @@
 }
 
 int
-TimeValueLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+TimeValueLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
     if (!m_model || shouldAutoAlign()) {
         return 0;
@@ -1262,7 +1262,7 @@
 }
 
 void
-TimeValueLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+TimeValueLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
     if (!m_model || m_model->getPoints().empty()) return;
 
@@ -1271,7 +1271,7 @@
     bool logarithmic;
 
     int w = getVerticalScaleWidth(v, false, paint);
-    int h = v->height();
+    int h = v->getPaintHeight();
 
     if (m_plotStyle == PlotSegmentation) {
 
@@ -1314,7 +1314,7 @@
 }
 
 void
-TimeValueLayer::drawStart(View *v, QMouseEvent *e)
+TimeValueLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
@@ -1364,7 +1364,7 @@
 }
 
 void
-TimeValueLayer::drawDrag(View *v, QMouseEvent *e)
+TimeValueLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
@@ -1426,7 +1426,7 @@
 }
 
 void
-TimeValueLayer::drawEnd(View *, QMouseEvent *)
+TimeValueLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawEnd" << endl;
@@ -1438,7 +1438,7 @@
 }
 
 void
-TimeValueLayer::eraseStart(View *v, QMouseEvent *e)
+TimeValueLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return;
 
@@ -1456,12 +1456,12 @@
 }
 
 void
-TimeValueLayer::eraseDrag(View *, QMouseEvent *)
+TimeValueLayer::eraseDrag(LayerGeometryProvider *, QMouseEvent *)
 {
 }
 
 void
-TimeValueLayer::eraseEnd(View *v, QMouseEvent *e)
+TimeValueLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model || !m_editing) return;
 
@@ -1483,7 +1483,7 @@
 }
 
 void
-TimeValueLayer::editStart(View *v, QMouseEvent *e)
+TimeValueLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
@@ -1506,7 +1506,7 @@
 }
 
 void
-TimeValueLayer::editDrag(View *v, QMouseEvent *e)
+TimeValueLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
@@ -1532,7 +1532,7 @@
 }
 
 void
-TimeValueLayer::editEnd(View *, QMouseEvent *)
+TimeValueLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editEnd" << endl;
@@ -1562,7 +1562,7 @@
 }
 
 bool
-TimeValueLayer::editOpen(View *v, QMouseEvent *e)
+TimeValueLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     if (!m_model) return false;
 
@@ -1685,7 +1685,7 @@
 }    
 
 void
-TimeValueLayer::copy(View *v, Selection s, Clipboard &to)
+TimeValueLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
     if (!m_model) return;
 
@@ -1703,7 +1703,7 @@
 }
 
 bool
-TimeValueLayer::paste(View *v, const Clipboard &from, sv_frame_t /* frameOffset */,
+TimeValueLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */,
                       bool interactive)
 {
     if (!m_model) return false;
@@ -1715,7 +1715,7 @@
     if (clipboardHasDifferentAlignment(v, from)) {
 
         QMessageBox::StandardButton button =
-            QMessageBox::question(v, tr("Re-align pasted items?"),
+            QMessageBox::question(v->getView(), 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);
--- a/layer/TimeValueLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/TimeValueLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -37,41 +37,41 @@
 public:
     TimeValueLayer();
 
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
     virtual QString getLabelPreceding(sv_frame_t) const;
 
-    virtual bool snapToFeatureFrame(View *v, sv_frame_t &frame,
+    virtual bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
 				    int &resolution,
 				    SnapType snap) const;
-    virtual bool snapToSimilarFeature(View *v, sv_frame_t &frame,
+    virtual bool snapToSimilarFeature(LayerGeometryProvider *v, sv_frame_t &frame,
                                       int &resolution,
                                       SnapType snap) const;
 
-    virtual void drawStart(View *v, QMouseEvent *);
-    virtual void drawDrag(View *v, QMouseEvent *);
-    virtual void drawEnd(View *v, QMouseEvent *);
+    virtual void drawStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void drawEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void eraseStart(View *v, QMouseEvent *);
-    virtual void eraseDrag(View *v, QMouseEvent *);
-    virtual void eraseEnd(View *v, QMouseEvent *);
+    virtual void eraseStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void eraseEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual void editStart(View *v, QMouseEvent *);
-    virtual void editDrag(View *v, QMouseEvent *);
-    virtual void editEnd(View *v, QMouseEvent *);
+    virtual void editStart(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editDrag(LayerGeometryProvider *v, QMouseEvent *);
+    virtual void editEnd(LayerGeometryProvider *v, QMouseEvent *);
 
-    virtual bool editOpen(View *v, QMouseEvent *);
+    virtual bool editOpen(LayerGeometryProvider *v, QMouseEvent *);
 
     virtual void moveSelection(Selection s, sv_frame_t newStartFrame);
     virtual void resizeSelection(Selection s, Selection newSize);
     virtual void deleteSelection(Selection s);
 
-    virtual void copy(View *v, Selection s, Clipboard &to);
-    virtual bool paste(View *v, const Clipboard &from, sv_frame_t frameOffset,
+    virtual void copy(LayerGeometryProvider *v, Selection s, Clipboard &to);
+    virtual bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive);
 
     virtual const Model *getModel() const { return m_model; }
@@ -120,11 +120,11 @@
     void setShowDerivative(bool);
     bool getShowDerivative() const { return m_derivative; }
 
-    virtual bool isLayerScrollable(const View *v) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *v) const;
 
     virtual bool isLayerEditable() const { return true; }
 
-    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+    virtual int getCompletion(LayerGeometryProvider *) const { return m_model->getCompletion(); }
 
     virtual bool needsTextLabelHeight() const {
         return m_plotStyle == PlotSegmentation && m_model->hasTextLabels();
@@ -155,16 +155,16 @@
     }
 
     /// VerticalScaleLayer and ColourScaleLayer methods
-    virtual int getYForValue(View *, double value) const;
-    virtual double getValueForY(View *, int y) const;
+    virtual int getYForValue(LayerGeometryProvider *, double value) const;
+    virtual double getValueForY(LayerGeometryProvider *, int y) const;
     virtual QString getScaleUnits() const;
-    virtual QColor getColourForValue(View *v, double value) const;
+    virtual QColor getColourForValue(LayerGeometryProvider *v, double value) const;
 
 protected:
-    void getScaleExtents(View *, double &min, double &max, bool &log) const;
+    void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
     bool shouldAutoAlign() const;
 
-    SparseTimeValueModel::PointList getLocalPoints(View *v, int) const;
+    SparseTimeValueModel::PointList getLocalPoints(LayerGeometryProvider *v, int) const;
 
     virtual int getDefaultColourHint(bool dark, bool &impose);
 
--- a/layer/VerticalScaleLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/VerticalScaleLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,8 +19,8 @@
 class VerticalScaleLayer
 {
 public:
-    virtual int getYForValue(View *, double value) const = 0;
-    virtual double getValueForY(View *, int y) const = 0;
+    virtual int getYForValue(LayerGeometryProvider *, double value) const = 0;
+    virtual double getValueForY(LayerGeometryProvider *, int y) const = 0;
     virtual QString getScaleUnits() const = 0;
 };
 
--- a/layer/WaveformLayer.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/WaveformLayer.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -326,7 +326,7 @@
 }
 
 int
-WaveformLayer::getCompletion(View *) const
+WaveformLayer::getCompletion(LayerGeometryProvider *) const
 {
     int completion = 100;
     if (!m_model || !m_model->isOK()) return completion;
@@ -399,7 +399,7 @@
 }    
 
 bool
-WaveformLayer::isLayerScrollable(const View *) const
+WaveformLayer::isLayerScrollable(const LayerGeometryProvider *) const
 {
     return !m_autoNormalize;
 }
@@ -408,7 +408,7 @@
                             -5, -3, -2, -1, -0.5, 0 };
 
 bool
-WaveformLayer::getSourceFramesForX(View *v, int x, int modelZoomLevel,
+WaveformLayer::getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel,
                                    sv_frame_t &f0, sv_frame_t &f1) const
 {
     sv_frame_t viewFrame = v->getFrameForX(x);
@@ -433,7 +433,7 @@
 }
 
 float
-WaveformLayer::getNormalizeGain(View *v, int channel) const
+WaveformLayer::getNormalizeGain(LayerGeometryProvider *v, int channel) const
 {
     sv_frame_t startFrame = v->getStartFrame();
     sv_frame_t endFrame = v->getEndFrame();
@@ -473,7 +473,7 @@
 }
 
 void
-WaveformLayer::paint(View *v, QPainter &viewPainter, QRect rect) const
+WaveformLayer::paint(LayerGeometryProvider *v, QPainter &viewPainter, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) {
 	return;
@@ -494,8 +494,8 @@
                                      mergingChannels, mixingChannels);
     if (channels == 0) return;
 
-    int w = v->width();
-    int h = v->height();
+    int w = v->getPaintWidth();
+    int h = v->getPaintHeight();
 
     bool ready = m_model->isReady();
     QPainter *paint;
@@ -559,7 +559,7 @@
     y1 = rect.bottom();
 
     if (x0 > 0) --x0;
-    if (x1 < v->width()) ++x1;
+    if (x1 < w) ++x1;
 
     // Our zoom level may differ from that at which the underlying
     // model has its blocks.
@@ -730,14 +730,17 @@
                 cerr << "WaveformLayer::paint: ERROR: i1 " << i1 << " > i0 " << i0 << " plus one (zoom = " << zoomLevel << ", model zoom = " << modelZoomLevel << ")" << endl;
             }
 
-	    if (ranges && i0 < (int)ranges->size()) {
+	    if (ranges && i0 < (sv_frame_t)ranges->size()) {
 
-		range = (*ranges)[i0];
+		range = (*ranges)[size_t(i0)];
 
 		if (i1 > i0 && i1 < (int)ranges->size()) {
-		    range.setMax(std::max(range.max(), (*ranges)[i1].max()));
-		    range.setMin(std::min(range.min(), (*ranges)[i1].min()));
-		    range.setAbsmean((range.absmean() + (*ranges)[i1].absmean()) / 2);
+		    range.setMax(std::max(range.max(),
+                                          (*ranges)[size_t(i1)].max()));
+		    range.setMin(std::min(range.min(),
+                                          (*ranges)[size_t(i1)].min()));
+		    range.setAbsmean((range.absmean()
+                                      + (*ranges)[size_t(i1)].absmean()) / 2);
 		}
 
 	    } else {
@@ -751,30 +754,33 @@
 
 	    if (mergingChannels) {
 
-		if (otherChannelRanges && i0 < (int)otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < (sv_frame_t)otherChannelRanges->size()) {
 
 		    range.setMax(fabsf(range.max()));
-		    range.setMin(-fabsf((*otherChannelRanges)[i0].max()));
+		    range.setMin(-fabsf((*otherChannelRanges)[size_t(i0)].max()));
 		    range.setAbsmean
                         ((range.absmean() +
-                          (*otherChannelRanges)[i0].absmean()) / 2);
+                          (*otherChannelRanges)[size_t(i0)].absmean()) / 2);
 
-		    if (i1 > i0 && i1 < (int)otherChannelRanges->size()) {
+		    if (i1 > i0 && i1 < (sv_frame_t)otherChannelRanges->size()) {
 			// let's not concern ourselves about the mean
 			range.setMin
                             (std::min
                              (range.min(),
-                              -fabsf((*otherChannelRanges)[i1].max())));
+                              -fabsf((*otherChannelRanges)[size_t(i1)].max())));
 		    }
 		}
 
 	    } else if (mixingChannels) {
 
-		if (otherChannelRanges && i0 < (int)otherChannelRanges->size()) {
+		if (otherChannelRanges && i0 < (sv_frame_t)otherChannelRanges->size()) {
 
-                    range.setMax((range.max() + (*otherChannelRanges)[i0].max()) / 2);
-                    range.setMin((range.min() + (*otherChannelRanges)[i0].min()) / 2);
-                    range.setAbsmean((range.absmean() + (*otherChannelRanges)[i0].absmean()) / 2);
+                    range.setMax((range.max()
+                                  + (*otherChannelRanges)[size_t(i0)].max()) / 2);
+                    range.setMin((range.min()
+                                  + (*otherChannelRanges)[size_t(i0)].min()) / 2);
+                    range.setAbsmean((range.absmean()
+                                      + (*otherChannelRanges)[size_t(i0)].absmean()) / 2);
                 }
             }
 
@@ -939,7 +945,7 @@
 
     if (m_aggressive) {
 
-	if (ready && rect == v->rect()) {
+	if (ready && rect == v->getPaintRect()) {
 	    m_cacheValid = true;
 	    m_cacheZoomLevel = zoomLevel;
 	}
@@ -953,7 +959,7 @@
 }
 
 QString
-WaveformLayer::getFeatureDescription(View *v, QPoint &pos) const
+WaveformLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
     int x = pos.x();
 
@@ -1036,7 +1042,7 @@
 }
 
 int
-WaveformLayer::getYForValue(const View *v, double value, int channel) const
+WaveformLayer::getYForValue(const LayerGeometryProvider *v, double value, int channel) const
 {
     int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
@@ -1046,7 +1052,7 @@
     if (channels == 0) return 0;
     if (maxChannel < minChannel || channel < minChannel) return 0;
 
-    int h = v->height();
+    int h = v->getPaintHeight();
     int m = (h / channels) / 2;
 	
     if ((m_scale == dBScale || m_scale == MeterScale) &&
@@ -1079,7 +1085,7 @@
 }
 
 double
-WaveformLayer::getValueForY(const View *v, int y, int &channel) const
+WaveformLayer::getValueForY(const LayerGeometryProvider *v, int y, int &channel) const
 {
     int channels = 0, minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
@@ -1089,7 +1095,7 @@
     if (channels == 0) return 0;
     if (maxChannel < minChannel) return 0;
 
-    int h = v->height();
+    int h = v->getPaintHeight();
     int m = (h / channels) / 2;
 
     if ((m_scale == dBScale || m_scale == MeterScale) &&
@@ -1125,7 +1131,7 @@
 }
 
 bool
-WaveformLayer::getYScaleValue(const View *v, int y,
+WaveformLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
                               double &value, QString &unit) const
 {
     int channel;
@@ -1151,7 +1157,7 @@
 }
 
 bool
-WaveformLayer::getYScaleDifference(const View *v, int y0, int y1,
+WaveformLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
                                    double &diff, QString &unit) const
 {
     int c0, c1;
@@ -1189,7 +1195,7 @@
 }
 
 int
-WaveformLayer::getVerticalScaleWidth(View *, bool, QPainter &paint) const
+WaveformLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
     if (m_scale == LinearScale) {
 	return paint.fontMetrics().width("0.0") + 13;
@@ -1200,7 +1206,7 @@
 }
 
 void
-WaveformLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const
+WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
     if (!m_model || !m_model->isOK()) {
 	return;
--- a/layer/WaveformLayer.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/layer/WaveformLayer.h	Wed Mar 02 17:25:27 2016 +0000
@@ -38,16 +38,16 @@
         return m_model ? m_model->getZoomConstraint() : 0;
     }
     virtual const Model *getModel() const { return m_model; }
-    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+    virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const;
 
-    virtual QString getFeatureDescription(View *v, QPoint &) const;
+    virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const;
 
     virtual ColourSignificance getLayerColourSignificance() const {
         return ColourAndBackgroundSignificant;
     }
 
-    virtual int getVerticalScaleWidth(View *v, bool detailed, QPainter &) const;
-    virtual void paintVerticalScale(View *v, bool detailed, QPainter &paint, QRect rect) const;
+    virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const;
+    virtual void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const;
 
     void setModel(const RangeSummarisableTimeValueModel *model);
 
@@ -180,17 +180,17 @@
     void setAggressiveCacheing(bool);
     bool getAggressiveCacheing() const { return m_aggressive; }
 
-    virtual bool isLayerScrollable(const View *) const;
+    virtual bool isLayerScrollable(const LayerGeometryProvider *) const;
 
-    virtual int getCompletion(View *) const;
+    virtual int getCompletion(LayerGeometryProvider *) const;
 
     virtual bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const;
 
-    virtual bool getYScaleValue(const View *v, int y,
+    virtual bool getYScaleValue(const LayerGeometryProvider *v, int y,
                                 double &value, QString &unit) const;
     
-    virtual bool getYScaleDifference(const View *v, int y0, int y1,
+    virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
                                      double &diff, QString &unit) const;
 
     virtual void toXml(QTextStream &stream, QString indent = "",
@@ -213,14 +213,14 @@
     int getChannelArrangement(int &min, int &max,
                                  bool &merging, bool &mixing) const;
 
-    int getYForValue(const View *v, double value, int channel) const;
+    int getYForValue(const LayerGeometryProvider *v, double value, int channel) const;
 
-    double getValueForY(const View *v, int y, int &channel) const;
+    double getValueForY(const LayerGeometryProvider *v, int y, int &channel) const;
 
-    bool getSourceFramesForX(View *v, int x, int modelZoomLevel,
+    bool getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel,
                              sv_frame_t &f0, sv_frame_t &f1) const;
 
-    float getNormalizeGain(View *v, int channel) const;
+    float getNormalizeGain(LayerGeometryProvider *v, int channel) const;
 
     virtual void flagBaseColourChanged() { m_cacheValid = false; }
 
--- a/svgui.pro	Mon Jun 15 09:15:55 2015 +0100
+++ b/svgui.pro	Wed Mar 02 17:25:27 2016 +0000
@@ -26,7 +26,7 @@
 }
 
 CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11
-QT += network xml gui widgets
+QT += network xml gui widgets svg
 
 TARGET = svgui
 
@@ -52,6 +52,7 @@
            layer/PaintAssistant.h \
            layer/PianoScale.h \
            layer/RegionLayer.h \
+           layer/ScrollableImageCache.h \
            layer/SingleColourLayer.h \
            layer/SliceableLayer.h \
            layer/SliceLayer.h \
@@ -79,6 +80,7 @@
            layer/PaintAssistant.cpp \
            layer/PianoScale.cpp \
            layer/RegionLayer.cpp \
+           layer/ScrollableImageCache.cpp \
            layer/SingleColourLayer.cpp \
            layer/SliceLayer.cpp \
            layer/SpectrogramLayer.cpp \
@@ -94,7 +96,9 @@
            view/Pane.h \
            view/PaneStack.h \
            view/View.h \
-           view/ViewManager.h
+           view/ViewManager.h \
+           view/ViewProxy.h
+           
 SOURCES += view/AlignmentView.cpp \
            view/Overview.cpp \
            view/Pane.cpp \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/LayerGeometryProvider.h	Wed Mar 02 17:25:27 2016 +0000
@@ -0,0 +1,174 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef LAYER_GEOMETRY_PROVIDER_H
+#define LAYER_GEOMETRY_PROVIDER_H
+
+#include "base/BaseTypes.h"
+
+#include <QMutex>
+#include <QMutexLocker>
+#include <QPainter>
+
+class ViewManager;
+class View;
+class Layer;
+
+class LayerGeometryProvider
+{
+protected:
+    static int getNextId() {
+        static QMutex idMutex;
+        static int nextId = 1;
+        static int maxId = INT_MAX;
+        QMutexLocker locker(&idMutex);
+        int id = nextId;
+        if (nextId == maxId) {
+            // we don't expect this to happen in the lifetime of a
+            // process, but it would be undefined behaviour if it did
+            // since we're using a signed int, so we should really
+            // guard for it...
+            nextId = 1;
+        } else {
+            nextId++;
+        }
+        return id;
+    }            
+    
+public:
+    LayerGeometryProvider() { }
+    
+    /**
+     * Retrieve the id of this object.
+     */
+    virtual int getId() const = 0;
+
+    /**
+     * Retrieve the first visible sample frame on the widget.
+     * This is a calculated value based on the centre-frame, widget
+     * width and zoom level.  The result may be negative.
+     */
+    virtual sv_frame_t getStartFrame() const = 0;
+
+    /**
+     * Return the centre frame of the visible widget.  This is an
+     * exact value that does not depend on the zoom block size.  Other
+     * frame values (start, end) are calculated from this based on the
+     * zoom and other factors.
+     */
+    virtual sv_frame_t getCentreFrame() const = 0;
+
+    /**
+     * Retrieve the last visible sample frame on the widget.
+     * This is a calculated value based on the centre-frame, widget
+     * width and zoom level.
+     */
+    virtual sv_frame_t getEndFrame() const = 0;
+
+    /**
+     * Return the pixel x-coordinate corresponding to a given sample
+     * frame (which may be negative).
+     */
+    virtual int getXForFrame(sv_frame_t frame) const = 0;
+
+    /**
+     * Return the closest frame to the given pixel x-coordinate.
+     */
+    virtual sv_frame_t getFrameForX(int x) const = 0;
+
+    virtual sv_frame_t getModelsStartFrame() const = 0;
+    virtual sv_frame_t getModelsEndFrame() const = 0;
+
+    /**
+     * Return the closest pixel x-coordinate corresponding to a given
+     * view x-coordinate.
+     */
+    virtual int getXForViewX(int viewx) const = 0;
+    
+    /**
+     * Return the closest view x-coordinate corresponding to a given
+     * pixel x-coordinate.
+     */
+    virtual int getViewXForX(int x) const = 0;
+    
+    /**
+     * Return the pixel y-coordinate corresponding to a given
+     * frequency, if the frequency range is as specified.  This does
+     * not imply any policy about layer frequency ranges, but it might
+     * be useful for layers to match theirs up if desired.
+     *
+     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
+     */
+    virtual double getYForFrequency(double frequency, double minFreq, double maxFreq, 
+                                    bool logarithmic) const = 0;
+
+    /**
+     * Return the closest frequency to the given pixel y-coordinate,
+     * if the frequency range is as specified.
+     *
+     * Not thread-safe in logarithmic mode.  Call only from GUI thread.
+     */
+    virtual double getFrequencyForY(int y, double minFreq, double maxFreq,
+			   bool logarithmic) const = 0;
+
+    virtual int getTextLabelHeight(const Layer *layer, QPainter &) const = 0;
+
+    virtual bool getValueExtents(QString unit, double &min, double &max,
+                                 bool &log) const = 0;
+
+    /**
+     * Return the zoom level, i.e. the number of frames per pixel
+     */
+    virtual int getZoomLevel() const = 0;
+
+    /**
+     * To be called from a layer, to obtain the extent of the surface
+     * that the layer is currently painting to. This may be the extent
+     * of the view (if 1x display scaling is in effect) or of a larger
+     * cached pixmap (if greater display scaling is in effect).
+     */
+    virtual QRect getPaintRect() const = 0;
+
+    virtual QSize getPaintSize() const { return getPaintRect().size(); }
+    virtual int getPaintWidth() const { return getPaintRect().width(); }
+    virtual int getPaintHeight() const { return getPaintRect().height(); }
+
+    virtual bool hasLightBackground() const = 0;
+    virtual QColor getForeground() const = 0;
+    virtual QColor getBackground() const = 0;
+
+    virtual ViewManager *getViewManager() const = 0;
+
+    virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const = 0;
+    virtual bool shouldShowFeatureLabels() const = 0;
+
+    enum TextStyle {
+	BoxedText,
+	OutlinedText,
+        OutlinedItalicText
+    };
+
+    virtual void drawVisibleText(QPainter &p, int x, int y,
+				 QString text, TextStyle style) const = 0;
+
+    virtual void drawMeasurementRect(QPainter &p, const Layer *,
+                                     QRect rect, bool focus) const = 0;
+
+    virtual void updatePaintRect(QRect r) = 0;
+    
+    virtual View *getView() = 0;
+    virtual const View *getView() const = 0;
+};
+
+#endif
--- a/view/View.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/view/View.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -20,6 +20,7 @@
 #include "base/Profiler.h"
 #include "base/Pitch.h"
 #include "base/Preferences.h"
+#include "ViewProxy.h"
 
 #include "layer/TimeRulerLayer.h"
 #include "layer/SingleColourLayer.h"
@@ -37,6 +38,7 @@
 #include <QFont>
 #include <QMessageBox>
 #include <QPushButton>
+#include <QSettings>
 
 #include <iostream>
 #include <cassert>
@@ -47,9 +49,9 @@
 //#define DEBUG_VIEW 1
 //#define DEBUG_VIEW_WIDGET_PAINT 1
 
-
 View::View(QWidget *w, bool showProgress) :
     QFrame(w),
+    m_id(getNextId()),
     m_centreFrame(0),
     m_zoomLevel(1024),
     m_followPan(true),
@@ -59,6 +61,7 @@
     m_playPointerFrame(0),
     m_showProgress(showProgress),
     m_cache(0),
+    m_buffer(0),
     m_cacheCentreFrame(0),
     m_cacheZoomLevel(1024),
     m_selectionCached(false),
@@ -362,15 +365,17 @@
 sv_frame_t
 View::getFrameForX(int x) const
 {
-    int z = m_zoomLevel;
+    sv_frame_t z = m_zoomLevel; // nb not just int, or multiplication may overflow
     sv_frame_t frame = m_centreFrame - (width()/2) * z;
 
+    frame = (frame / z) * z; // this is start frame
+    frame = frame + x * z;
+
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-    SVDEBUG << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl;
+    cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl;
 #endif
 
-    frame = (frame / z) * z; // this is start frame
-    return frame + x * z;
+    return frame;
 }
 
 double
@@ -448,9 +453,30 @@
     return m_zoomLevel;
 }
 
+int
+View::effectiveDevicePixelRatio() const
+{
+#ifdef Q_OS_MAC
+    int dpratio = devicePixelRatio();
+    if (dpratio > 1) {
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        if (!settings.value("scaledHiDpi", true).toBool()) {
+            dpratio = 1;
+        }
+        settings.endGroup();
+    }
+    return dpratio;
+#else
+    return 1;
+#endif
+}
+
 void
 View::setZoomLevel(int z)
 {
+    int dpratio = effectiveDevicePixelRatio();
+    if (z < dpratio) return;
     if (z < 1) z = 1;
     if (m_zoomLevel != int(z)) {
 	m_zoomLevel = z;
@@ -1027,7 +1053,7 @@
     f = getAlignedPlaybackFrame();
 
 #ifdef DEBUG_VIEW
-    cerr << " -> aligned frame = " << af << endl;
+    cerr << " -> aligned frame = " << f << endl;
 #endif
 
     movePlayPointer(f);
@@ -1653,11 +1679,27 @@
 void
 View::setPaintFont(QPainter &paint)
 {
+    int scaleFactor = 1;
+    int dpratio = effectiveDevicePixelRatio();
+    if (dpratio > 1) {
+        QPaintDevice *dev = paint.device();
+        if (dynamic_cast<QPixmap *>(dev) || dynamic_cast<QImage *>(dev)) {
+            scaleFactor = dpratio;
+        }
+    }
+
     QFont font(paint.font());
-    font.setPointSize(Preferences::getInstance()->getViewFontSize());
+    font.setPointSize(Preferences::getInstance()->getViewFontSize()
+                      * scaleFactor);
     paint.setFont(font);
 }
 
+QRect
+View::getPaintRect() const
+{
+    return rect();
+}
+
 void
 View::paintEvent(QPaintEvent *e)
 {
@@ -1694,6 +1736,8 @@
 
     QRect nonCacheRect(cacheRect);
 
+    int dpratio = effectiveDevicePixelRatio();
+
     // If not all layers are scrollable, but some of the back layers
     // are, we should store only those in the cache.
 
@@ -1705,25 +1749,34 @@
 
     // If all the non-scrollable layers are non-opaque, then we draw
     // the selection rectangle behind them and cache it.  If any are
-    // opaque, however, we can't cache.
+    // opaque, however, or if our device-pixel ratio is not 1 (so we
+    // need to paint direct to the widget), then we can't cache.
     //
-    if (!selectionCacheable) {
-	selectionCacheable = true;
-	for (LayerList::const_iterator i = nonScrollables.begin();
-	     i != nonScrollables.end(); ++i) {
-	    if ((*i)->isLayerOpaque()) {
-		selectionCacheable = false;
-		break;
-	    }
-	}
-    }
-
-    if (selectionCacheable) {
-	QPoint localPos;
-	bool closeToLeft, closeToRight;
-	if (shouldIlluminateLocalSelection(localPos, closeToLeft, closeToRight)) {
-	    selectionCacheable = false;
-	}
+    if (dpratio == 1) {
+
+        if (!selectionCacheable) {
+            selectionCacheable = true;
+            for (LayerList::const_iterator i = nonScrollables.begin();
+                 i != nonScrollables.end(); ++i) {
+                if ((*i)->isLayerOpaque()) {
+                    selectionCacheable = false;
+                    break;
+                }
+            }
+        }
+
+        if (selectionCacheable) {
+            QPoint localPos;
+            bool closeToLeft, closeToRight;
+            if (shouldIlluminateLocalSelection
+                (localPos, closeToLeft, closeToRight)) {
+                selectionCacheable = false;
+            }
+        }
+
+    } else {
+
+        selectionCacheable = false;
     }
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
@@ -1741,6 +1794,14 @@
 	m_selectionCached = false;
     }
 
+    QSize scaledCacheSize(scaledSize(size(), dpratio));
+    QRect scaledCacheRect(scaledRect(cacheRect, dpratio));
+
+    if (!m_buffer || scaledCacheSize != m_buffer->size()) {
+        delete m_buffer;
+        m_buffer = new QPixmap(scaledCacheSize);
+    }
+    
     if (!scrollables.empty()) {
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
@@ -1750,8 +1811,7 @@
 
 	if (!m_cache ||
 	    m_cacheZoomLevel != m_zoomLevel ||
-	    width() != m_cache->width() ||
-	    height() != m_cache->height()) {
+            scaledCacheSize != m_cache->size()) {
 
 	    // cache is not valid
 
@@ -1763,7 +1823,7 @@
 #endif
 	    } else {
 		delete m_cache;
-		m_cache = new QPixmap(width(), height());
+		m_cache = new QPixmap(scaledCacheSize);
 #ifdef DEBUG_VIEW_WIDGET_PAINT
 		cerr << "View(" << this << ")::paintEvent: recreated cache" << endl;
 #endif
@@ -1778,24 +1838,10 @@
 		getXForFrame(m_centreFrame);
 
 	    if (dx > -width() && dx < width()) {
-#ifdef PIXMAP_COPY_TO_SELF
-                // This is not normally defined. Copying a pixmap to
-		// itself doesn't work properly on Windows, Mac, or
-		// X11 with the raster backend (it only works when
-		// moving in one direction and then presumably only by
-		// accident).  It does actually seem to be fine on X11
-		// with the native backend, but we prefer not to use
-		// that anyway
-		paint.begin(m_cache);
-		paint.drawPixmap(dx, 0, *m_cache);
-		paint.end();
-#else
 		static QPixmap *tmpPixmap = 0;
-		if (!tmpPixmap ||
-		    tmpPixmap->width() != width() ||
-		    tmpPixmap->height() != height()) {
+		if (!tmpPixmap || tmpPixmap->size() != scaledCacheSize) {
 		    delete tmpPixmap;
-		    tmpPixmap = new QPixmap(width(), height());
+		    tmpPixmap = new QPixmap(scaledCacheSize);
 		}
 		paint.begin(tmpPixmap);
 		paint.drawPixmap(0, 0, *m_cache);
@@ -1803,7 +1849,6 @@
 		paint.begin(m_cache);
 		paint.drawPixmap(dx, 0, *tmpPixmap);
 		paint.end();
-#endif
 		if (dx < 0) {
 		    cacheRect = QRect(width() + dx, 0, -dx, height());
 		} else {
@@ -1824,8 +1869,8 @@
 #ifdef DEBUG_VIEW_WIDGET_PAINT
 	    cerr << "View(" << this << ")::paintEvent: cache is good" << endl;
 #endif
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+	    paint.begin(m_buffer);
+	    paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
 	    paint.end();
 	    QFrame::paintEvent(e);
 	    paintedCacheRect = true;
@@ -1841,16 +1886,26 @@
 
     // Scrollable (cacheable) items first
 
+    ViewProxy proxy(this, dpratio);
+    
     if (!paintedCacheRect) {
 
-	if (repaintCache) paint.begin(m_cache);
-	else paint.begin(this);
+        QRect rectToPaint;
+
+	if (repaintCache) {
+            paint.begin(m_cache);
+            rectToPaint = scaledCacheRect;
+        } else {
+            paint.begin(m_buffer);
+            rectToPaint = scaledCacheRect;
+        }
+
         setPaintFont(paint);
-	paint.setClipRect(cacheRect);
+	paint.setClipRect(rectToPaint);
 
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-	paint.drawRect(cacheRect);
+	paint.drawRect(rectToPaint);
 
 	paint.setPen(getForeground());
 	paint.setBrush(Qt::NoBrush);
@@ -1858,7 +1913,10 @@
 	for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
 	    paint.setRenderHint(QPainter::Antialiasing, false);
 	    paint.save();
-	    (*i)->paint(this, paint, cacheRect);
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+            cerr << "Painting scrollable layer " << *i << " using proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << rectToPaint.x() << "," << rectToPaint.y() << " " << rectToPaint.width() << "x" << rectToPaint.height() << endl;
+#endif
+            (*i)->paint(&proxy, paint, rectToPaint);
 	    paint.restore();
 	}
 
@@ -1871,8 +1929,9 @@
 
 	if (repaintCache) {
 	    cacheRect |= (e ? e->rect() : rect());
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+            scaledCacheRect = scaledRect(cacheRect, dpratio);
+	    paint.begin(m_buffer);
+	    paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
 	    paint.end();
 	}
     }
@@ -1883,13 +1942,15 @@
 
     nonCacheRect |= cacheRect;
 
-    paint.begin(this);
-    paint.setClipRect(nonCacheRect);
+    QRect scaledNonCacheRect = scaledRect(nonCacheRect, dpratio);
+    
+    paint.begin(m_buffer);
+    paint.setClipRect(scaledNonCacheRect);
     setPaintFont(paint);
     if (scrollables.empty()) {
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-	paint.drawRect(nonCacheRect);
+	paint.drawRect(scaledNonCacheRect);
     }
 	
     paint.setPen(getForeground());
@@ -1897,10 +1958,18 @@
 	
     for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
 //        Profiler profiler2("View::paintEvent non-cacheable");
-	(*i)->paint(this, paint, nonCacheRect);
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+        cerr << "Painting non-scrollable layer " << *i << " without proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << nonCacheRect.x() << "," << nonCacheRect.y() << " " << nonCacheRect.width() << "x" << nonCacheRect.height() << endl;
+#endif
+	(*i)->paint(&proxy, paint, scaledNonCacheRect);
     }
 	
     paint.end();
+    
+    paint.begin(this);
+    QRect finalPaintRect = e ? e->rect() : rect();
+    paint.drawPixmap(finalPaintRect, *m_buffer, scaledRect(finalPaintRect, dpratio));
+    paint.end();
 
     paint.begin(this);
     setPaintFont(paint);
@@ -1925,7 +1994,7 @@
             showPlayPointer = false;
         }
     }
-
+    
     if (showPlayPointer) {
 
 	paint.begin(this);
@@ -2412,23 +2481,23 @@
 
 	for (LayerList::iterator i = m_layerStack.begin();
              i != m_layerStack.end(); ++i) {
-		if(!((*i)->isLayerDormant(this))){
-
-		    paint.setRenderHint(QPainter::Antialiasing, false);
-
-		    paint.save();
-	            paint.translate(xorigin + x, 0);
-
-	            cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl;
-
-	            (*i)->setSynchronousPainting(true);
-
-		    (*i)->paint(this, paint, chunk);
-
-	            (*i)->setSynchronousPainting(false);
-
-		    paint.restore();
-		}
+            if (!((*i)->isLayerDormant(this))){
+
+                paint.setRenderHint(QPainter::Antialiasing, false);
+
+                paint.save();
+                paint.translate(xorigin + x, 0);
+
+                cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl;
+
+                (*i)->setSynchronousPainting(true);
+
+                (*i)->paint(this, paint, chunk);
+
+                (*i)->setSynchronousPainting(false);
+
+                paint.restore();
+            }
 	}
     }
 
--- a/view/View.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/view/View.h	Wed Mar 02 17:25:27 2016 +0000
@@ -19,6 +19,8 @@
 #include <QFrame>
 #include <QProgressBar>
 
+#include "LayerGeometryProvider.h"
+
 #include "base/ZoomConstraint.h"
 #include "base/PropertyContainer.h"
 #include "ViewManager.h"
@@ -49,7 +51,8 @@
  */
 
 class View : public QFrame,
-	     public XmlExportable
+	     public XmlExportable,
+             public LayerGeometryProvider
 {
     Q_OBJECT
 
@@ -61,6 +64,12 @@
     virtual ~View();
 
     /**
+     * Retrieve the id of this object. Views have their own unique
+     * ids, but ViewProxy objects share the id of their View.
+     */
+    int getId() const { return m_id; }
+    
+    /**
      * Retrieve the first visible sample frame on the widget.
      * This is a calculated value based on the centre-frame, widget
      * width and zoom level.  The result may be negative.
@@ -105,6 +114,20 @@
     sv_frame_t getFrameForX(int x) const;
 
     /**
+     * Return the closest pixel x-coordinate corresponding to a given
+     * view x-coordinate. Default is no scaling, ViewProxy handles
+     * scaling case.
+     */
+    int getXForViewX(int viewx) const { return viewx; }
+
+    /**
+     * Return the closest view x-coordinate corresponding to a given
+     * pixel x-coordinate. Default is no scaling, ViewProxy handles
+     * scaling case.
+     */
+    int getViewXForX(int x) const { return x; }
+
+    /**
      * Return the pixel y-coordinate corresponding to a given
      * frequency, if the frequency range is as specified.  This does
      * not imply any policy about layer frequency ranges, but it might
@@ -243,12 +266,6 @@
     virtual QColor getForeground() const;
     virtual QColor getBackground() const;
 
-    enum TextStyle {
-	BoxedText,
-	OutlinedText,
-        OutlinedItalicText
-    };
-
     virtual void drawVisibleText(QPainter &p, int x, int y,
 				 QString text, TextStyle style) const;
 
@@ -315,6 +332,18 @@
     sv_frame_t getModelsStartFrame() const;
     sv_frame_t getModelsEndFrame() const;
 
+    /**
+     * To be called from a layer, to obtain the extent of the surface
+     * that the layer is currently painting to. This may be the extent
+     * of the view (if 1x display scaling is in effect) or of a larger
+     * cached pixmap (if greater display scaling is in effect).
+     */
+    QRect getPaintRect() const;
+
+    QSize getPaintSize() const { return getPaintRect().size(); }
+    int getPaintWidth() const { return getPaintRect().width(); }
+    int getPaintHeight() const { return getPaintRect().height(); }
+
     typedef std::set<Model *> ModelSet;
     ModelSet getModels();
 
@@ -324,6 +353,11 @@
     sv_frame_t alignToReference(sv_frame_t) const;
     sv_frame_t getAlignedPlaybackFrame() const;
 
+    void updatePaintRect(QRect r) { update(r); }
+    
+    View *getView() { return this; } 
+    const View *getView() const { return this; } 
+    
 signals:
     void propertyContainerAdded(PropertyContainer *pc);
     void propertyContainerRemoved(PropertyContainer *pc);
@@ -372,11 +406,22 @@
 
 protected:
     View(QWidget *, bool showProgress);
+
+    int m_id;
+    
     virtual void paintEvent(QPaintEvent *e);
     virtual void drawSelections(QPainter &);
     virtual bool shouldLabelSelections() const { return true; }
     virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1);
     virtual void setPaintFont(QPainter &paint);
+
+    QSize scaledSize(const QSize &s, int factor) {
+        return QSize(s.width() * factor, s.height() * factor);
+    }
+    QRect scaledRect(const QRect &r, int factor) {
+        return QRect(r.x() * factor, r.y() * factor,
+                     r.width() * factor, r.height() * factor);
+    }
     
     typedef std::vector<Layer *> LayerList;
 
@@ -406,6 +451,8 @@
     void checkProgress(void *object);
     int getProgressBarWidth() const; // if visible
 
+    int effectiveDevicePixelRatio() const;
+
     sv_frame_t          m_centreFrame;
     int                 m_zoomLevel;
     bool                m_followPan;
@@ -417,6 +464,7 @@
     bool                m_showProgress;
 
     QPixmap            *m_cache;
+    QPixmap            *m_buffer;
     sv_frame_t          m_cacheCentreFrame;
     int                 m_cacheZoomLevel;
     bool                m_selectionCached;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/view/ViewProxy.h	Wed Mar 02 17:25:27 2016 +0000
@@ -0,0 +1,156 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef VIEW_PROXY_H
+#define VIEW_PROXY_H
+
+#include "LayerGeometryProvider.h"
+
+class ViewProxy : public LayerGeometryProvider
+{
+public:
+    ViewProxy(View *view, int scaleFactor) :
+	m_view(view), m_scaleFactor(scaleFactor) { }
+
+    virtual int getId() const {
+        return m_view->getId();
+    }
+    virtual sv_frame_t getStartFrame() const {
+	return m_view->getStartFrame();
+    }
+    virtual sv_frame_t getCentreFrame() const {
+	return m_view->getCentreFrame();
+    }
+    virtual sv_frame_t getEndFrame() const {
+	return m_view->getEndFrame();
+    }
+    virtual int getXForFrame(sv_frame_t frame) const {
+        //!!! not actually correct, if frame lies between view's pixels
+	return m_scaleFactor * m_view->getXForFrame(frame);
+    }
+    virtual sv_frame_t getFrameForX(int x) const {
+        sv_frame_t f0 = m_view->getFrameForX(x / m_scaleFactor);
+        if (m_scaleFactor == 1) return f0;
+        sv_frame_t f1 = m_view->getFrameForX((x / m_scaleFactor) + 1);
+        return f0 + ((f1 - f0) * (x % m_scaleFactor)) / m_scaleFactor;
+    }
+    virtual int getXForViewX(int viewx) const {
+        return viewx * m_scaleFactor;
+    }
+    virtual int getViewXForX(int x) const {
+        return x / m_scaleFactor;
+    }
+    virtual sv_frame_t getModelsStartFrame() const {
+	return m_view->getModelsStartFrame();
+    }
+    virtual sv_frame_t getModelsEndFrame() const {
+	return m_view->getModelsEndFrame();
+    }
+    virtual double getYForFrequency(double frequency,
+				    double minFreq, double maxFreq, 
+                                    bool logarithmic) const {
+	return m_scaleFactor *
+	    m_view->getYForFrequency(frequency, minFreq, maxFreq, logarithmic);
+    }
+    virtual double getFrequencyForY(int y, double minFreq, double maxFreq,
+				    bool logarithmic) const {
+        double f0 = m_view->getFrequencyForY
+            (y / m_scaleFactor, minFreq, maxFreq, logarithmic);
+        if (m_scaleFactor == 1) return f0;
+        double f1 = m_view->getFrequencyForY
+            ((y / m_scaleFactor) + 1, minFreq, maxFreq, logarithmic);
+        return f0 + ((f1 - f0) * (y % m_scaleFactor)) / m_scaleFactor;
+    }
+    virtual int getTextLabelHeight(const Layer *layer, QPainter &paint) const {
+	return m_scaleFactor * m_view->getTextLabelHeight(layer, paint);
+    }
+    virtual bool getValueExtents(QString unit, double &min, double &max,
+                                 bool &log) const {
+	return m_view->getValueExtents(unit, min, max, log);
+    }
+    virtual int getZoomLevel() const {
+	int z = m_view->getZoomLevel();
+//	cerr << "getZoomLevel: from " << z << " to ";
+	z = z / m_scaleFactor;
+//	cerr << z << endl;
+        if (z < 1) z = 1;
+	return z;
+    }
+    virtual QRect getPaintRect() const {
+	QRect r = m_view->getPaintRect();
+	return QRect(r.x() * m_scaleFactor,
+		     r.y() * m_scaleFactor,
+		     r.width() * m_scaleFactor,
+		     r.height() * m_scaleFactor);
+    }
+    virtual QSize getPaintSize() const {
+        return getPaintRect().size();
+    }
+    virtual int getPaintWidth() const { 
+        return getPaintRect().width();
+    }
+    virtual int getPaintHeight() const { 
+        return getPaintRect().height();
+    }
+    virtual bool hasLightBackground() const {
+	return m_view->hasLightBackground();
+    }
+    virtual QColor getForeground() const {
+	return m_view->getForeground();
+    }
+    virtual QColor getBackground() const {
+	return m_view->getBackground();
+    }
+    virtual ViewManager *getViewManager() const {
+	return m_view->getViewManager();
+    }
+	
+    virtual bool shouldIlluminateLocalFeatures(const Layer *layer,
+                                               QPoint &point) const {
+        QPoint p;
+	bool should = m_view->shouldIlluminateLocalFeatures(layer, p);
+        point = QPoint(p.x() * m_scaleFactor, p.y() * m_scaleFactor);
+        return should;
+    }
+
+    virtual bool shouldShowFeatureLabels() const {
+	return m_view->shouldShowFeatureLabels();
+    }
+
+    virtual void drawVisibleText(QPainter &p, int x, int y,
+				 QString text, TextStyle style) const {
+	m_view->drawVisibleText(p, x, y, text, style);
+    }
+
+    virtual void drawMeasurementRect(QPainter &p, const Layer *layer,
+                                     QRect rect, bool focus) const {
+	m_view->drawMeasurementRect(p, layer, rect, focus);
+    }
+
+    virtual void updatePaintRect(QRect r) {
+        m_view->update(r.x() / m_scaleFactor,
+                       r.y() / m_scaleFactor,
+                       r.width() / m_scaleFactor,
+                       r.height() / m_scaleFactor);
+    }
+    
+    virtual View *getView() { return m_view; }
+    virtual const View *getView() const { return m_view; }
+
+private:
+    View *m_view;
+    int m_scaleFactor;
+};
+
+#endif
--- a/widgets/CSVFormatDialog.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/CSVFormatDialog.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -254,8 +254,12 @@
 {
     // First check if we already have any. NB there may be fewer than
     // m_format.getColumnCount() elements in m_columnPurposeCombos
-    // (because of the fuzzy column behaviour)
+    // (because of the fuzzy column behaviour). Note also that the
+    // fuzzy column (which is the one just showing how many more
+    // columns there are) has a different combo with only two items
+    // (ignore or Values)
     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        if (i == m_fuzzyColumn) continue;
         QComboBox *cb = m_columnPurposeCombos[i];
         if (cb->currentIndex() == int(CSVFormat::ColumnStartTime)) {
             return;
@@ -263,6 +267,7 @@
     }
     // and if not, select one
     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        if (i == m_fuzzyColumn) continue;
         QComboBox *cb = m_columnPurposeCombos[i];
         if (cb->currentIndex() == int(CSVFormat::ColumnValue)) {
             cb->setCurrentIndex(int(CSVFormat::ColumnStartTime));
@@ -278,6 +283,7 @@
     // in m_columnPurposeCombos (because of the fuzzy column
     // behaviour)
     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
+        if (i == m_fuzzyColumn) continue;
         QComboBox *cb = m_columnPurposeCombos[i];
         if (cb->currentIndex() == int(CSVFormat::ColumnStartTime)) {
             cb->setCurrentIndex(int(CSVFormat::ColumnValue));
@@ -345,6 +351,11 @@
     
     for (int i = 0; i < m_columnPurposeCombos.size(); ++i) {
 
+        // The fuzzy column combo only has the entries <ignore> or
+        // Values, so it can't affect the timing type and none of this
+        // logic affects it
+        if (i == m_fuzzyColumn) continue;
+
         QComboBox *thisCombo = m_columnPurposeCombos[i];
         
         CSVFormat::ColumnPurpose cp = (CSVFormat::ColumnPurpose)
@@ -353,8 +364,6 @@
         
         if (!thisChanged) {
 
-            if (i == m_fuzzyColumn) continue;
-
             // We can only have one ColumnStartTime column, and only
             // one of either ColumnDuration or ColumnEndTime
 
@@ -442,24 +451,8 @@
         CSVFormat::ColumnPurpose purpose = (CSVFormat::ColumnPurpose)
             (thisCombo->currentIndex());
 
-        if (purpose == CSVFormat::ColumnStartTime) {
-            haveStartTime = true;
-        }
-        if (purpose == CSVFormat::ColumnEndTime ||
-            purpose == CSVFormat::ColumnDuration) {
-            haveDuration = true;
-        }
-        if (purpose == CSVFormat::ColumnPitch) {
-            havePitch = true;
-        }
-        if (purpose == CSVFormat::ColumnValue) {
-            ++valueCount;
-        }
-
-        m_format.setColumnPurpose(i, purpose);
-
         if (i == m_fuzzyColumn) {
-            for (int j = i + 1; j < m_format.getColumnCount(); ++j) {
+            for (int j = i; j < m_format.getColumnCount(); ++j) {
                 if (purpose == CSVFormat::ColumnUnknown) {
                     m_format.setColumnPurpose(j, CSVFormat::ColumnUnknown);
                 } else { // Value
@@ -467,6 +460,23 @@
                     ++valueCount;
                 }
             }
+        } else {
+        
+            if (purpose == CSVFormat::ColumnStartTime) {
+                haveStartTime = true;
+            }
+            if (purpose == CSVFormat::ColumnEndTime ||
+                purpose == CSVFormat::ColumnDuration) {
+                haveDuration = true;
+            }
+            if (purpose == CSVFormat::ColumnPitch) {
+                havePitch = true;
+            }
+            if (purpose == CSVFormat::ColumnValue) {
+                ++valueCount;
+            }
+
+            m_format.setColumnPurpose(i, purpose);
         }
     }
 
--- a/widgets/CommandHistory.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/CommandHistory.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -26,6 +26,8 @@
 
 #include "base/Command.h"
 
+#include "IconLoader.h"
+
 #include <QRegExp>
 #include <QMenu>
 #include <QToolBar>
@@ -53,12 +55,16 @@
     m_bundleTimer(0),
     m_bundleTimeout(3000)
 {
-    m_undoAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
+    IconLoader loader;
+    QIcon undoIcon(loader.load("undo"));
+    QIcon redoIcon(loader.load("redo"));
+    
+    m_undoAction = new QAction(undoIcon, ("&Undo"), this);
     m_undoAction->setShortcut(tr("Ctrl+Z"));
     m_undoAction->setStatusTip(tr("Undo the last editing operation"));
     connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
     
-    m_undoMenuAction = new QAction(QIcon(":/icons/undo.png"), tr("&Undo"), this);
+    m_undoMenuAction = new QAction(undoIcon, tr("&Undo"), this);
     connect(m_undoMenuAction, SIGNAL(triggered()), this, SLOT(undo()));
     
     m_undoMenu = new QMenu(tr("&Undo"));
@@ -66,12 +72,12 @@
     connect(m_undoMenu, SIGNAL(triggered(QAction *)),
 	    this, SLOT(undoActivated(QAction*)));
 
-    m_redoAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
+    m_redoAction = new QAction(redoIcon, tr("Re&do"), this);
     m_redoAction->setShortcut(tr("Ctrl+Shift+Z"));
     m_redoAction->setStatusTip(tr("Redo the last operation that was undone"));
     connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
     
-    m_redoMenuAction = new QAction(QIcon(":/icons/redo.png"), tr("Re&do"), this);
+    m_redoMenuAction = new QAction(redoIcon, tr("Re&do"), this);
     connect(m_redoMenuAction, SIGNAL(triggered()), this, SLOT(redo()));
 
     m_redoMenu = new QMenu(tr("Re&do"));
--- a/widgets/IconLoader.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/IconLoader.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -19,8 +19,17 @@
 #include <QApplication>
 #include <QPainter>
 #include <QPalette>
+#include <QFile>
+#include <QSvgRenderer>
 
-static const char *autoInvertExceptions[] = {
+#include <vector>
+#include <set>
+
+#include "base/Debug.h"
+
+using namespace std;
+
+static set<QString> autoInvertExceptions {
     // These are the icons that look OK in their default colours, even
     // in a colour scheme with a black background.  (They may also be
     // icons that would look worse if we tried to auto-invert them.)
@@ -29,16 +38,13 @@
     // supply inverted versions -- the loader will load xx_inverse.png
     // in preference to xx.png if a dark background is found.)
     "fileclose",
-    "filenew-22",
     "filenew",
-    "fileopen-22",
     "fileopen",
     "fileopenaudio",
     "fileopensession",
-    "filesave-22",
     "filesave",
-    "filesaveas-22",
     "filesaveas",
+    "filesaveas-sv",
     "help",
     "editcut",
     "editcopy",
@@ -51,42 +57,121 @@
     "zoom"
 };
 
+static vector<int> sizes { 0, 16, 22, 24, 32, 48, 64, 128 };
+
 QIcon
 IconLoader::load(QString name)
 {
-    QPixmap pmap(loadPixmap(name));
-    if (pmap.isNull()) return QIcon();
-    else return QIcon(pmap);
+    QIcon icon;
+    for (int sz: sizes) {
+        QPixmap pmap(loadPixmap(name, sz));
+        if (!pmap.isNull()) icon.addPixmap(pmap);
+    }
+    return icon;
+}
+
+bool
+IconLoader::shouldInvert() const
+{
+    QColor bg = QApplication::palette().window().color();
+    bool darkBackground = (bg.red() + bg.green() + bg.blue() <= 384);
+    return darkBackground;
+}
+
+bool
+IconLoader::shouldAutoInvert(QString name) const
+{
+    if (shouldInvert()) {
+        return (autoInvertExceptions.find(name) == autoInvertExceptions.end());
+    } else {
+        return false;
+    }
 }
 
 QPixmap
-IconLoader::loadPixmap(QString name)
+IconLoader::loadPixmap(QString name, int size)
 {
-    QColor bg = QApplication::palette().window().color();
-    if (bg.red() + bg.green() + bg.blue() > 384) { // light background
-        QPixmap pmap(QString(":icons/%1").arg(name));
-        if (pmap.isNull()) {
-            pmap = QPixmap(QString(":icons/%1.png").arg(name));
-        }
-        return pmap;
+    bool invert = shouldInvert();
+
+    QString scalableName, nonScalableName;
+    QPixmap pmap;
+
+    nonScalableName = makeNonScalableFilename(name, size, invert);
+    pmap = QPixmap(nonScalableName);
+    if (!pmap.isNull()) return pmap;
+
+    if (size > 0) {
+        scalableName = makeScalableFilename(name, invert);
+        pmap = loadScalable(scalableName, size);
+        if (!pmap.isNull()) return pmap;
     }
 
-    QPixmap pmap(QString(":icons/%1").arg(name));
-    if (pmap.isNull()) {
-        pmap = QPixmap(QString(":icons/%1_inverse.png").arg(name));
-        if (pmap.isNull()) {
-            pmap = QPixmap(QString(":icons/%1.png").arg(name));
-        }
-    }
-    if (pmap.isNull()) return pmap;
+    if (invert && shouldAutoInvert(name)) {
 
-    for (int i = 0; i < int(sizeof(autoInvertExceptions)/
-                            sizeof(autoInvertExceptions[0])); ++i) {
-        if (autoInvertExceptions[i] == name) {
-            return pmap;
+        nonScalableName = makeNonScalableFilename(name, size, false);
+        pmap = QPixmap(nonScalableName);
+        if (!pmap.isNull()) return invertPixmap(pmap);
+
+        if (size > 0) {
+            scalableName = makeScalableFilename(name, false);
+            pmap = loadScalable(scalableName, size);
+            if (!pmap.isNull()) return invertPixmap(pmap);
         }
     }
 
+    return QPixmap();
+}
+
+QPixmap
+IconLoader::loadScalable(QString name, int size)
+{
+    if (!QFile(name).exists()) {
+//        cerr << "loadScalable: no such file as: \"" << name << "\"" << endl;
+        return QPixmap();
+    }
+    QPixmap pmap(size, size);
+    pmap.fill(Qt::transparent);
+    QSvgRenderer renderer(name);
+    QPainter painter;
+    painter.begin(&pmap);
+//    cerr << "calling renderer for " << name << " at size " << size << "..." << endl;
+    renderer.render(&painter);
+//    cerr << "renderer completed" << endl;
+    painter.end();
+    return pmap;
+}
+
+QString
+IconLoader::makeNonScalableFilename(QString name, int size, bool invert)
+{
+    if (invert) {
+        if (size == 0) {
+            return QString(":icons/%1_inverse.png").arg(name);
+        } else {
+            return QString(":icons/%1-%2_inverse.png").arg(name).arg(size);
+        }
+    } else {
+        if (size == 0) {
+            return QString(":icons/%1.png").arg(name);
+        } else {
+            return QString(":icons/%1-%2.png").arg(name).arg(size);
+        }
+    }
+}
+
+QString
+IconLoader::makeScalableFilename(QString name, bool invert)
+{
+    if (invert) {
+        return QString(":icons/scalable/%1_inverse.svg").arg(name);
+    } else {
+        return QString(":icons/scalable/%1.svg").arg(name);
+    }
+}
+
+QPixmap
+IconLoader::invertPixmap(QPixmap pmap)
+{
     // No suitable inverted icon found for black background; try to
     // auto-invert the default one
 
--- a/widgets/IconLoader.h	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/IconLoader.h	Wed Mar 02 17:25:27 2016 +0000
@@ -25,7 +25,15 @@
     virtual ~IconLoader() { }
 
     QIcon load(QString name);
-    QPixmap loadPixmap(QString name);
+
+private:
+    bool shouldInvert() const;
+    bool shouldAutoInvert(QString) const;
+    QPixmap loadPixmap(QString, int);
+    QPixmap loadScalable(QString, int);
+    QPixmap invertPixmap(QPixmap);
+    QString makeScalableFilename(QString, bool);
+    QString makeNonScalableFilename(QString, int, bool);
 };
 
 #endif
--- a/widgets/LEDButton.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/LEDButton.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -38,8 +38,6 @@
 
     int dark_factor;
     QColor offcolor;
-    QPixmap *off_map;
-    QPixmap *on_map;
 };
 
 
@@ -51,8 +49,6 @@
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
     d->offcolor = col.dark(300);
-    d->off_map = 0;
-    d->on_map = 0;
     
     setColor(col);
 }
@@ -65,8 +61,6 @@
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
     d->offcolor = col.dark(300);
-    d->off_map = 0;
-    d->on_map = 0;
 
     setColor(col);
 }
@@ -78,16 +72,12 @@
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
     d->offcolor = col.dark(300);
-    d->off_map = 0;
-    d->on_map = 0;
 
     setColor(col);
 }
 
 LEDButton::~LEDButton()
 {
-    delete d->off_map;
-    delete d->on_map;
     delete d;
 }
 
@@ -135,40 +125,7 @@
     if (width < 0) 
 	width = 0;
 
-    QPixmap *tmpMap = 0;
-
-    if (led_state) {
-	if (d->on_map) {
-            if (d->on_map->size() == size()) {
-                paint.begin(this);
-                paint.drawPixmap(0, 0, *d->on_map);
-                paint.end();
-                return;
-            } else {
-                delete d->on_map;
-                d->on_map = 0;
-            }
-	}
-    } else {
-	if (d->off_map) {
-            if (d->off_map->size() == size()) {
-                paint.begin(this);
-                paint.drawPixmap(0, 0, *d->off_map);
-                paint.end();
-                return;
-            } else {
-                delete d->off_map;
-                d->off_map = 0;
-            }
-	}
-    }
-
-    int scale = 1;
-    width *= scale;
-
-    tmpMap = new QPixmap(width, width);
-    tmpMap->fill(palette().background().color());
-    paint.begin(tmpMap);
+    paint.begin(this);
 
     paint.setRenderHint(QPainter::Antialiasing, true);
 
@@ -182,14 +139,14 @@
     paint.setBrush(brush);
 
     // Draws a "flat" LED with the given color:
-    paint.drawEllipse( scale, scale, width - scale*2, width - scale*2 );
+    paint.drawEllipse( 1, 1, width - 2, width - 2 );
 
     // Draw the bright light spot of the LED now, using modified "old"
     // painter routine taken from KDEUI´s LEDButton widget:
 
     // Setting the new width of the pen is essential to avoid "pixelized"
     // shadow like it can be observed with the old LED code
-    pen.setWidth( 2 * scale );
+    pen.setWidth( 2 );
 
     // shrink the light on the LED to a size about 2/3 of the complete LED
     int pos = width/5 + 1;
@@ -217,12 +174,13 @@
 	pos++; light_width--;
     }
 
+    paint.drawPoint(pos, pos);
+
     // Drawing of bright spot finished, now draw a thin border
     // around the LED which resembles a shadow with light coming
     // from the upper left.
 
-//    pen.setWidth( 2 * scale + 1 ); // ### shouldn't this value be smaller for smaller LEDs?
-    pen.setWidth(2 * scale);
+    pen.setWidth(2);
     brush.setStyle(Qt::NoBrush);
     paint.setBrush(brush); // This avoids filling of the ellipse
 
@@ -235,36 +193,13 @@
     for (int arc = 120; arc < 2880; arc += 240) {
 	pen.setColor(color);
 	paint.setPen(pen);
-	int w = width - pen.width()/2 - scale + 1;
+	int w = width - pen.width()/2;
 	paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle + arc, 240);
 	paint.drawArc(pen.width()/2 + 1, pen.width()/2 + 1, w - 2, w - 2, angle - arc, 240);
 	color = color.dark(110); //FIXME: this should somehow use the contrast value
     }	// end for ( angle = 720; angle < 6480; angle += 160 )
 
     paint.end();
-    //
-    // painting done
-
-    QPixmap *&dest = led_state ? d->on_map : d->off_map;
-
-    if (scale > 1) {
-
-	QImage i = tmpMap->toImage();
-	width /= scale;
-	delete tmpMap;
-	dest = new QPixmap(QPixmap::fromImage
-			   (i.scaled(width, width, 
-				     Qt::KeepAspectRatio,
-				     Qt::SmoothTransformation)));
-
-    } else {
-
-	dest = tmpMap;
-    }
-
-    paint.begin(this);
-    paint.drawPixmap(0, 0, *dest);
-    paint.end();
 }
 
 bool
@@ -303,10 +238,6 @@
     if(led_color!=col) {
 	led_color = col;
 	d->offcolor = col.dark(d->dark_factor);
-	delete d->on_map;
-	d->on_map = 0;
-	delete d->off_map;
-	d->off_map = 0;
 	update();
     }
 }
--- a/widgets/PropertyBox.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/PropertyBox.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -63,6 +63,12 @@
     m_mainBox = new QVBoxLayout;
     setLayout(m_mainBox);
 
+#ifdef Q_OS_MAC
+    QMargins mm = m_mainBox->contentsMargins();
+    QMargins mmhalf(mm.left()/2, mm.top()/3, mm.right()/2, mm.bottom()/3);
+    m_mainBox->setContentsMargins(mmhalf);
+#endif
+
 //    m_nameWidget = new QLabel;
 //    m_mainBox->addWidget(m_nameWidget);
 //    m_nameWidget->setText(container->objectName());
@@ -443,7 +449,16 @@
             if (type == PropertyContainer::ValueProperty) {
 
                 for (int i = min; i <= max; ++i) {
-                    cb->addItem(m_container->getPropertyValueLabel(name, i));
+
+                    QString label = m_container->getPropertyValueLabel(name, i);
+                    QString iname = m_container->getPropertyValueIconName(name, i);
+
+                    if (iname != "") {
+                        QIcon icon(IconLoader().load(iname));
+                        cb->addItem(icon, label);
+                    } else {
+                        cb->addItem(label);
+                    }
                 }
 
             } else if (type == PropertyContainer::UnitsProperty) {
@@ -512,8 +527,10 @@
         cb->blockSignals(false);
 
 #ifdef Q_OS_MAC
-	// Crashes on startup without this, for some reason
-	cb->setMinimumSize(QSize(10, 10));
+	// Crashes on startup without this, for some reason; also
+	// prevents combo boxes from getting weirdly squished
+	// vertically
+	cb->setMinimumSize(QSize(10, cb->font().pixelSize() * 2));
 #endif
 
 	break;
--- a/widgets/PropertyStack.cpp	Mon Jun 15 09:15:55 2015 +0100
+++ b/widgets/PropertyStack.cpp	Wed Mar 02 17:25:27 2016 +0000
@@ -50,7 +50,7 @@
     tabBar()->setUsesScrollButtons(true); 
     tabBar()->setIconSize(QSize(16, 16));
 #endif
-    
+
     repopulate();
 
     connect(this, SIGNAL(currentChanged(int)),