changeset 1486:ac0a8addabcf

Merge from branch by-id
author Chris Cannam
date Wed, 17 Jul 2019 14:25:16 +0100
parents de41a11cabc2 (current diff) 0e971e3d93e2 (diff)
children c3cc36c014b8
files
diffstat 65 files changed, 2074 insertions(+), 1465 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -43,7 +43,6 @@
 
 
 Colour3DPlotLayer::Colour3DPlotLayer() :
-    m_model(nullptr),
     m_colourScale(ColourScaleType::Linear),
     m_colourScaleSet(false),
     m_colourMap(0),
@@ -59,20 +58,26 @@
     m_miny(0),
     m_maxy(0),
     m_synchronous(false),
-    m_peakCache(nullptr),
     m_peakCacheDivisor(8)
 {
     QSettings settings;
     settings.beginGroup("Preferences");
-    setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt());
+    setColourMap(settings.value("colour-3d-plot-colour",
+                                ColourMapper::Green).toInt());
     settings.endGroup();
 }
 
 Colour3DPlotLayer::~Colour3DPlotLayer()
 {
     invalidateRenderers();
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    delete m_peakCache;
+}
+
+const ZoomConstraint *
+Colour3DPlotLayer::getZoomConstraint() const 
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getZoomConstraint();
+    else return nullptr;
 }
 
 ColourScaleType
@@ -136,35 +141,38 @@
 }
 
 void
-Colour3DPlotLayer::setModel(const DenseThreeDimensionalModel *model)
+Colour3DPlotLayer::setModel(ModelId modelId)
 {
-    SVDEBUG << "Colour3DPlotLayer::setModel(" << model << ")" << endl;
+    auto newModel = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
     
-    if (m_model == model) return;
-    const DenseThreeDimensionalModel *oldModel = m_model;
-    m_model = model;
-    if (!m_model || !m_model->isOK()) return;
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a DenseThreeDimensionalModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+        connectSignals(m_model);
 
-    connect(m_model, SIGNAL(modelChanged()),
-            this, SLOT(handleModelChanged()));
-    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-            this, SLOT(handleModelChangedWithin(sv_frame_t, sv_frame_t)));
+        connect(newModel.get(), SIGNAL(modelChanged(ModelId)),
+                this, SLOT(handleModelChanged(ModelId)));
+        connect(newModel.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+                this, SLOT(handleModelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
 
-    m_peakResolution = 256;
-    if (model->getResolution() > 512) {
-        m_peakResolution = 16;
-    } else if (model->getResolution() > 128) {
-        m_peakResolution = 64;
-    } else if (model->getResolution() > 2) {
-        m_peakResolution = 128;
+        m_peakResolution = 256;
+        if (newModel->getResolution() > 512) {
+            m_peakResolution = 16;
+        } else if (newModel->getResolution() > 128) {
+            m_peakResolution = 64;
+        } else if (newModel->getResolution() > 2) {
+            m_peakResolution = 128;
+        }
     }
-
+    
     invalidatePeakCache();
 
     emit modelReplaced();
-    emit sliceableModelReplaced(oldModel, model);
 }
 
 void
@@ -173,10 +181,11 @@
     // renderers use the peak cache, so we must invalidate those too
     invalidateRenderers();
     invalidateMagnitudes();
-    
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    delete m_peakCache;
-    m_peakCache = nullptr;
+
+    if (!m_peakCache.isNone()) {
+        ModelById::release(m_peakCache);
+        m_peakCache = {};
+    }
 }
 
 void
@@ -198,21 +207,24 @@
     m_viewMags.clear();
 }
 
-Dense3DModelPeakCache *
+ModelId
 Colour3DPlotLayer::getPeakCache() const
 {
-    if (!m_peakCache) {
-        m_peakCache = new Dense3DModelPeakCache(m_model, m_peakCacheDivisor);
+    if (m_peakCache.isNone()) {
+        auto peakCache = std::make_shared<Dense3DModelPeakCache>
+            (m_model, m_peakCacheDivisor);
+        m_peakCache = ModelById::add(peakCache);
     }
     return m_peakCache;
 }
 
 void
-Colour3DPlotLayer::handleModelChanged()
+Colour3DPlotLayer::handleModelChanged(ModelId modelId)
 {
     if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) {
-        if (m_model) {
-            if (m_model->shouldUseLogValueScale()) {
+        auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+        if (model) {
+            if (model->shouldUseLogValueScale()) {
                 setColourScale(ColourScaleType::Log);
             } else {
                 m_colourScaleSet = true;
@@ -220,22 +232,25 @@
         }
     }
     invalidatePeakCache();
-    emit modelChanged();
+    emit modelChanged(modelId);
 }
 
 void
-Colour3DPlotLayer::handleModelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
+Colour3DPlotLayer::handleModelChangedWithin(ModelId modelId,
+                                            sv_frame_t startFrame,
+                                            sv_frame_t endFrame)
 {
     if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) {
-        if (m_model && m_model->getWidth() > 50) {
-            if (m_model->shouldUseLogValueScale()) {
+        auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+        if (model && model->getWidth() > 50) {
+            if (model->shouldUseLogValueScale()) {
                 setColourScale(ColourScaleType::Log);
             } else {
                 m_colourScaleSet = true;
             }
         }
     }
-    emit modelChangedWithin(startFrame, endFrame);
+    emit modelChangedWithin(modelId, startFrame, endFrame);
 }
 
 Layer::PropertyList
@@ -649,14 +664,23 @@
     return false;
 }
 
+int
+Colour3DPlotLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 bool
 Colour3DPlotLayer::getValueExtents(double &min, double &max,
                                    bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return false;
 
     min = 0;
-    max = double(m_model->getHeight());
+    max = double(model->getHeight());
 
     logarithmic = (m_binScale == BinScale::Log);
     unit = "";
@@ -667,9 +691,10 @@
 bool
 Colour3DPlotLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return false;
 
-    double hmax = double(m_model->getHeight());
+    double hmax = double(model->getHeight());
     
     min = m_miny;
     max = m_maxy;
@@ -686,8 +711,6 @@
 bool
 Colour3DPlotLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
-
     m_miny = int(lrint(min));
     m_maxy = int(lrint(max));
     
@@ -707,37 +730,40 @@
 int
 Colour3DPlotLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return 0;
 
     defaultStep = 0;
-    int h = m_model->getHeight();
+    int h = model->getHeight();
     return h;
 }
 
 int
 Colour3DPlotLayer::getCurrentVerticalZoomStep() const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return 0;
 
     double min, max;
     getDisplayExtents(min, max);
-    return m_model->getHeight() - int(lrint(max - min));
+    return model->getHeight() - int(lrint(max - min));
 }
 
 void
 Colour3DPlotLayer::setVerticalZoomStep(int step)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return;
 
 //    SVDEBUG << "Colour3DPlotLayer::setVerticalZoomStep(" <<step <<"): before: miny = " << m_miny << ", maxy = " << m_maxy << endl;
 
-    int dist = m_model->getHeight() - step;
+    int dist = model->getHeight() - step;
     if (dist < 1) dist = 1;
     double centre = m_miny + (m_maxy - m_miny) / 2.0;
     m_miny = int(lrint(centre - dist/2.0));
     if (m_miny < 0) m_miny = 0;
     m_maxy = m_miny + dist;
-    if (m_maxy > m_model->getHeight()) m_maxy = m_model->getHeight();
+    if (m_maxy > model->getHeight()) m_maxy = model->getHeight();
 
     invalidateRenderers();
     
@@ -749,18 +775,23 @@
 RangeMapper *
 Colour3DPlotLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    //!!! most of our uses of model in these functions is just to
+    //!!! retrieve the model's height - perhaps we should cache it
+    
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return nullptr;
 
-    return new LinearRangeMapper(0, m_model->getHeight(),
-                                 0, m_model->getHeight(), "");
+    return new LinearRangeMapper(0, model->getHeight(),
+                                 0, model->getHeight(), "");
 }
 
 double
 Colour3DPlotLayer::getYForBin(const LayerGeometryProvider *v, double bin) const
 {
     double y = bin;
-    if (!m_model) return y;
-    double mn = 0, mx = m_model->getHeight();
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return y;
+    double mn = 0, mx = model->getHeight();
     getDisplayExtents(mn, mx);
     double h = v->getPaintHeight();
     if (m_binScale == BinScale::Linear) {
@@ -777,8 +808,9 @@
 Colour3DPlotLayer::getBinForY(const LayerGeometryProvider *v, double y) const
 {
     double bin = y;
-    if (!m_model) return bin;
-    double mn = 0, mx = m_model->getHeight();
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return bin;
+    double mn = 0, mx = model->getHeight();
     getDisplayExtents(mn, mx);
     double h = v->getPaintHeight();
     if (m_binScale == BinScale::Linear) {
@@ -798,17 +830,18 @@
 QString
 Colour3DPlotLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
 {
-    if (!m_model) return "";
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return "";
 
     int x = pos.x();
     int y = pos.y();
 
-    sv_frame_t modelStart = m_model->getStartFrame();
-    int modelResolution = m_model->getResolution();
+    sv_frame_t modelStart = model->getStartFrame();
+    int modelResolution = model->getResolution();
 
     double srRatio =
         v->getViewManager()->getMainModelSampleRate() /
-        m_model->getSampleRate();
+        model->getSampleRate();
 
     int sx0 = int((double(v->getFrameForX(x)) / srRatio - double(modelStart)) /
                   modelResolution);
@@ -816,7 +849,7 @@
     int f0 = sx0 * modelResolution;
     int f1 =  f0 + modelResolution;
 
-    int sh = m_model->getHeight();
+    int sh = model->getHeight();
 
     int symin = m_miny;
     int symax = m_maxy;
@@ -832,26 +865,26 @@
 
     int sy = getIBinForY(v, y);
 
-    if (sy < 0 || sy >= m_model->getHeight()) {
+    if (sy < 0 || sy >= model->getHeight()) {
         return "";
     }
 
     if (m_invertVertical) {
-        sy = m_model->getHeight() - sy - 1;
+        sy = model->getHeight() - sy - 1;
     }
 
-    float value = m_model->getValueAt(sx0, sy);
+    float value = model->getValueAt(sx0, sy);
 
 //    cerr << "bin value (" << sx0 << "," << sy << ") is " << value << endl;
     
-    QString binName = m_model->getBinName(sy);
+    QString binName = model->getBinName(sy);
     if (binName == "") binName = QString("[%1]").arg(sy + 1);
     else binName = QString("%1 [%2]").arg(binName).arg(sy + 1);
 
     QString text = tr("Time:\t%1 - %2\nBin:\t%3\nValue:\t%4")
-        .arg(RealTime::frame2RealTime(f0, m_model->getSampleRate())
+        .arg(RealTime::frame2RealTime(f0, model->getSampleRate())
              .toText(true).c_str())
-        .arg(RealTime::frame2RealTime(f1, m_model->getSampleRate())
+        .arg(RealTime::frame2RealTime(f1, model->getSampleRate())
              .toText(true).c_str())
         .arg(binName)
         .arg(value);
@@ -870,15 +903,21 @@
 int
 Colour3DPlotLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return 0;
 
-    QString sampleText = QString("[%1]").arg(m_model->getHeight());
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11 which
+    // is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+    QString sampleText = QString("[%1]").arg(model->getHeight());
     int tw = paint.fontMetrics().width(sampleText);
     bool another = false;
 
-    for (int i = 0; i < m_model->getHeight(); ++i) {
-        if (m_model->getBinName(i).length() > sampleText.length()) {
-            sampleText = m_model->getBinName(i);
+    for (int i = 0; i < model->getHeight(); ++i) {
+        if (model->getBinName(i).length() > sampleText.length()) {
+            sampleText = model->getBinName(i);
             another = true;
         }
     }
@@ -892,7 +931,8 @@
 void
 Colour3DPlotLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return;
 
     int h = rect.height(), w = rect.width();
 
@@ -930,25 +970,27 @@
 
         int msw = paint.fontMetrics().width(maxstr);
 
-        QMatrix m;
+        QTransform m;
         m.translate(cw - 6, ch + 10);
         m.rotate(-90);
 
-        paint.setWorldMatrix(m);
+        paint.setWorldTransform(m);
 
-        PaintAssistant::drawVisibleText(v, paint, 2, 0, minstr, PaintAssistant::OutlinedText);
+        PaintAssistant::drawVisibleText(v, paint, 2, 0, minstr,
+                                        PaintAssistant::OutlinedText);
 
         m.translate(ch - msw - 2, 0);
-        paint.setWorldMatrix(m);
+        paint.setWorldTransform(m);
 
-        PaintAssistant::drawVisibleText(v, paint, 0, 0, maxstr, PaintAssistant::OutlinedText);
+        PaintAssistant::drawVisibleText(v, paint, 0, 0, maxstr,
+                                        PaintAssistant::OutlinedText);
 
         paint.restore();
     }
 
     paint.setPen(v->getForeground());
 
-    int sh = m_model->getHeight();
+    int sh = model->getHeight();
 
     int symin = m_miny;
     int symax = m_maxy;
@@ -994,10 +1036,10 @@
 
             int idx = i - 1;
             if (m_invertVertical) {
-                idx = m_model->getHeight() - idx - 1;
+                idx = model->getHeight() - idx - 1;
             }
 
-            QString text = m_model->getBinName(idx);
+            QString text = model->getBinName(idx);
             if (text == "") text = QString("[%1]").arg(idx + 1);
 
             int ty = y0 + (h/2) - (paint.fontMetrics().height()/2) +
@@ -1013,13 +1055,15 @@
 Colour3DPlotRenderer *
 Colour3DPlotLayer::getRenderer(const LayerGeometryProvider *v) const
 {
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) return nullptr;
+    
     int viewId = v->getId();
     
     if (m_renderers.find(viewId) == m_renderers.end()) {
 
         Colour3DPlotRenderer::Sources sources;
         sources.verticalBinLayer = this;
-        sources.fft = nullptr;
         sources.source = m_model;
         sources.peakCaches.push_back(getPeakCache());
 
@@ -1037,16 +1081,16 @@
             maxValue = m_viewMags[viewId].getMax();
         } else if (m_normalization == ColumnNormalization::Hybrid) {
             minValue = 0;
-            maxValue = log10(m_model->getMaximumLevel() + 1.0);
+            maxValue = log10(model->getMaximumLevel() + 1.0);
         } else if (m_normalization == ColumnNormalization::None) {
-            minValue = m_model->getMinimumLevel();
-            maxValue = m_model->getMaximumLevel();
+            minValue = model->getMinimumLevel();
+            maxValue = model->getMaximumLevel();
         }
 
         SVDEBUG << "Colour3DPlotLayer: rebuilding renderer, value range is "
                 << minValue << " -> " << maxValue
-                << " (model min = " << m_model->getMinimumLevel()
-                << ", max = " << m_model->getMaximumLevel() << ")"
+                << " (model min = " << model->getMinimumLevel()
+                << ", max = " << model->getMaximumLevel() << ")"
                 << endl;
 
         if (maxValue <= minValue) {
@@ -1139,9 +1183,10 @@
 void
 Colour3DPlotLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
 /*
-    if (m_model) {
-        SVDEBUG << "Colour3DPlotLayer::paint: model says shouldUseLogValueScale = " << m_model->shouldUseLogValueScale() << endl;
+    if (model) {
+        SVDEBUG << "Colour3DPlotLayer::paint: model says shouldUseLogValueScale = " << model->shouldUseLogValueScale() << endl;
     }
 */
     Profiler profiler("Colour3DPlotLayer::paint");
@@ -1150,7 +1195,7 @@
 #endif
 
     int completion = 0;
-    if (!m_model || !m_model->isOK() || !m_model->isReady(&completion)) {
+    if (!model || !model->isOK() || !model->isReady(&completion)) {
         if (completion > 0) {
             paint.fillRect(0, 10, v->getPaintWidth() * completion / 100,
                            10, QColor(120, 120, 120));
@@ -1158,7 +1203,7 @@
         return;
     }
 
-    if (m_model->getWidth() == 0) {
+    if (model->getWidth() == 0) {
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
         SVDEBUG << "Colour3DPlotLayer::paint(): model width == 0, "
              << "nothing to paint (yet)" << endl;
@@ -1174,11 +1219,12 @@
                                       int &resolution,
                                       SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
     sv_frame_t left = (frame / resolution) * resolution;
     sv_frame_t right = left + resolution;
 
--- a/layer/Colour3DPlotLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -46,23 +46,28 @@
     Colour3DPlotLayer();
     ~Colour3DPlotLayer();
 
-    const ZoomConstraint *getZoomConstraint() const override {
-        return m_model ? m_model->getZoomConstraint() : 0;
-    }
-    const Model *getModel() const override { return m_model; }
-    void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override;
+    ModelId getModel() const override { return m_model; }
+
+    const ZoomConstraint *getZoomConstraint() const override;
+    
+    void paint(LayerGeometryProvider *v,
+               QPainter &paint, QRect rect) const override;
     void setSynchronousPainting(bool synchronous) override;
 
-    int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override;
-    void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override;
+    int getVerticalScaleWidth(LayerGeometryProvider *v,
+                              bool, QPainter &) const override;
+    void paintVerticalScale(LayerGeometryProvider *v,
+                            bool, QPainter &paint, QRect rect) const override;
 
-    QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override;
+    QString getFeatureDescription(LayerGeometryProvider *v,
+                                  QPoint &) const override;
 
     bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, 
-                                    int &resolution,
-                                    SnapType snap) const override;
+                            int &resolution,
+                            SnapType snap) const override;
 
-    void setLayerDormant(const LayerGeometryProvider *v, bool dormant) override;
+    void setLayerDormant(const LayerGeometryProvider *v,
+                         bool dormant) override;
 
     bool isLayerScrollable(const LayerGeometryProvider *v) const override;
 
@@ -70,9 +75,9 @@
         return ColourHasMeaningfulValue;
     }
 
-    void setModel(const DenseThreeDimensionalModel *model);
+    void setModel(ModelId model); // a DenseThreeDimensionalModel
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     PropertyList getProperties() const override;
     PropertyType getPropertyType(const PropertyName &) const override;
@@ -80,11 +85,11 @@
     QString getPropertyIconName(const PropertyName &) const override;
     QString getPropertyGroupName(const PropertyName &) const override;
     int getPropertyRangeAndValue(const PropertyName &,
-                                         int *min, int *max, int *deflt) const override;
+                                 int *min, int *max, int *deflt) const override;
     QString getPropertyValueLabel(const PropertyName &,
-                                          int value) const override;
+                                  int value) const override;
     QString getPropertyValueIconName(const PropertyName &,
-                                             int value) const override;
+                                     int value) const override;
     RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const override;
     void setProperty(const PropertyName &, int value) override;
     void setProperties(const QXmlAttributes &) override;
@@ -132,30 +137,30 @@
     bool hasLightBackground() const override;
 
     bool getValueExtents(double &min, double &max,
-                                 bool &logarithmic, QString &unit) const override;
+                         bool &logarithmic, QString &unit) const override;
 
     bool getDisplayExtents(double &min, double &max) const override;
     bool setDisplayExtents(double min, double max) override;
 
     bool getYScaleValue(const LayerGeometryProvider *, int /* y */,
-                                double &/* value */, QString &/* unit */) const override;
+                        double &/* value */, QString &/* unit */) const override;
 
     int getVerticalZoomSteps(int &defaultStep) const override;
     int getCurrentVerticalZoomStep() const override;
     void setVerticalZoomStep(int) override;
     RangeMapper *getNewVerticalZoomRangeMapper() const override;
 
-    const Model *getSliceableModel() const override { return m_model; }
+    ModelId getSliceableModel() const override { return m_model; }
 
     void toXml(QTextStream &stream, QString indent = "",
-                       QString extraAttributes = "") const override;
+               QString extraAttributes = "") const override;
 
 protected slots:
-    void handleModelChanged();
-    void handleModelChangedWithin(sv_frame_t, sv_frame_t);
+    void handleModelChanged(ModelId);
+    void handleModelChangedWithin(ModelId, sv_frame_t, sv_frame_t);
 
 protected:
-    const DenseThreeDimensionalModel *m_model; // I do not own this
+    ModelId m_model; // A DenseThreeDimensionalModel
     
     ColourScaleType m_colourScale;
     bool m_colourScaleSet;
@@ -182,10 +187,10 @@
     static std::pair<ColumnNormalization, bool> convertToColumnNorm(int value);
     static int convertFromColumnNorm(ColumnNormalization norm, bool visible);
 
-    mutable Dense3DModelPeakCache *m_peakCache;
+    mutable ModelId m_peakCache;
     const int m_peakCacheDivisor;
-    Dense3DModelPeakCache *getPeakCache() const;
     void invalidatePeakCache();
+    ModelId getPeakCache() const;
 
     typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id
     mutable ViewMagMap m_viewMags;
--- a/layer/Colour3DPlotRenderer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotRenderer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -334,7 +334,7 @@
 Colour3DPlotRenderer::RenderType
 Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const
 {
-    const DenseThreeDimensionalModel *model = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
     if (!model || !v || !(v->getViewManager())) {
         return DrawBufferPixelResolution; // or anything
     }
@@ -398,7 +398,7 @@
     }
 
     if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
-        m_sources.fft) {
+        !m_sources.fft.isNone()) {
         return column;
     } else {
         column = ColumnOp::applyGain(column, m_params.scaleFactor);
@@ -414,27 +414,36 @@
     Profiler profiler("Colour3DPlotRenderer::getColumn");
 
     ColumnOp::Column column;
+    ColumnOp::Column fullColumn;
 
-    if (m_params.colourScale.getScale() == ColourScaleType::Phase &&
-        m_sources.fft) {
-
-        ColumnOp::Column fullColumn = m_sources.fft->getPhases(sx);
-
-        column = vector<float>(fullColumn.data() + minbin,
-                               fullColumn.data() + minbin + nbins);
-
-    } else {
-
-        ColumnOp::Column fullColumn =
-            (peakCacheIndex >= 0 ?
-             m_sources.peakCaches[peakCacheIndex] :
-             m_sources.source)
-            ->getColumn(sx);
-                
-        column = vector<float>(fullColumn.data() + minbin,
-                               fullColumn.data() + minbin + nbins);
+    if (m_params.colourScale.getScale() == ColourScaleType::Phase) {
+        auto fftModel = ModelById::getAs<FFTModel>(m_sources.fft);
+        if (fftModel) {
+            fullColumn = fftModel->getPhases(sx);
+        }
     }
 
+    if (fullColumn.empty()) {
+        
+        if (peakCacheIndex >= 0) {
+            auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
+                (m_sources.peakCaches[peakCacheIndex]);
+            if (!peakCache) {
+                return vector<float>(nbins, 0.f);
+            }                
+            fullColumn = peakCache->getColumn(sx);
+        } else {
+            auto model = ModelById::getAs<DenseThreeDimensionalModel>
+                (m_sources.source);
+            if (!model) {
+                return vector<float>(nbins, 0.f);
+            }
+            fullColumn = model->getColumn(sx);
+        }
+    }
+
+    column = vector<float>(fullColumn.data() + minbin,
+                           fullColumn.data() + minbin + nbins);
     return column;
 }
 
@@ -451,7 +460,8 @@
     bool illuminate = v->shouldIlluminateLocalFeatures
         (m_sources.verticalBinLayer, illuminatePos);
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return magRange;
     
     int x0 = rect.left();
     int x1 = rect.right() + 1;
@@ -526,6 +536,11 @@
         int rw = rx1 - rx0;
         if (rw < 1) rw = 1;
 
+        // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+        // replacement (horizontalAdvance) was only added in Qt 5.11
+        // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+        
         bool showLabel = (rw > 10 &&
                           paint.fontMetrics().width("0.000000") < rw - 3 &&
                           paint.fontMetrics().height() < (h / sh));
@@ -602,7 +617,7 @@
     peakCacheIndex = -1;
     binsPerPeak = -1;
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
     if (!model) return;
     if (m_params.binDisplay == BinDisplay::PeakFrequencies) return;
     if (m_params.colourScale.getScale() == ColourScaleType::Phase) return;
@@ -611,7 +626,10 @@
     int binResolution = model->getResolution();
     
     for (int ix = 0; in_range_for(m_sources.peakCaches, ix); ++ix) {
-        int bpp = m_sources.peakCaches[ix]->getColumnsPerPeak();
+        auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
+            (m_sources.peakCaches[ix]);
+        if (!peakCache) continue;
+        int bpp = peakCache->getColumnsPerPeak();
         ZoomLevel equivZoom(ZoomLevel::FramesPerPixel, binResolution * bpp);
 #ifdef DEBUG_COLOUR_PLOT_REPAINT
         SVDEBUG << "getPreferredPeakCache: zoomLevel = " << zoomLevel
@@ -653,10 +671,8 @@
     // buffer is at the same resolution as the target in the cache, so
     // no extra scaling needed.
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
-    if (!model || !model->isOK() || !model->isReady()) {
-        throw std::logic_error("no source model provided, or model not ready");
-    }
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return;
 
     int h = v->getPaintHeight();
 
@@ -794,10 +810,8 @@
     // buffer is at bin resolution, i.e. buffer x == source column
     // number. We use toolkit smooth scaling for interpolation.
 
-    const DenseThreeDimensionalModel *model = m_sources.source;
-    if (!model || !model->isOK() || !model->isReady()) {
-        throw std::logic_error("no source model provided, or model not ready");
-    }
+    auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_sources.source);
+    if (!model) return;
 
     // The draw buffer will contain a fragment at bin resolution. We
     // need to ensure that it starts and ends at points where a
@@ -950,12 +964,25 @@
     Profiler profiler("Colour3DPlotRenderer::renderDrawBuffer");
     
     int divisor = 1;
-    const DenseThreeDimensionalModel *sourceModel = m_sources.source;
+
+    std::shared_ptr<DenseThreeDimensionalModel> sourceModel;
+
     if (peakCacheIndex >= 0) {
-        divisor = m_sources.peakCaches[peakCacheIndex]->getColumnsPerPeak();
-        sourceModel = m_sources.peakCaches[peakCacheIndex];
+        auto peakCache = ModelById::getAs<Dense3DModelPeakCache>
+            (m_sources.peakCaches[peakCacheIndex]);
+        if (peakCache) {
+            divisor = peakCache->getColumnsPerPeak();
+            sourceModel = peakCache;
+        }
     }
 
+    if (!sourceModel) {
+        sourceModel = ModelById::getAs<DenseThreeDimensionalModel>
+            (m_sources.source);
+    }
+    
+    if (!sourceModel) return 0;
+
 #ifdef DEBUG_COLOUR_PLOT_REPAINT
     SVDEBUG << "renderDrawBuffer: w = " << w << ", h = " << h
             << ", peakCacheIndex = " << peakCacheIndex << " (divisor = "
@@ -1125,7 +1152,8 @@
                       RenderTimer::SlowRender :
                       RenderTimer::NoTimeout);
 
-    const FFTModel *fft = m_sources.fft;
+    auto fft = ModelById::getAs<FFTModel>(m_sources.fft);
+    if (!fft) return 0;
 
     int sh = fft->getHeight();
     
--- a/layer/Colour3DPlotRenderer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Colour3DPlotRenderer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -23,16 +23,16 @@
 #include "base/ColumnOp.h"
 #include "base/MagnitudeRange.h"
 
+#include "data/model/Model.h"
+
 #include <QRect>
 #include <QPainter>
 #include <QImage>
 
 class LayerGeometryProvider;
 class VerticalBinLayer;
-class DenseThreeDimensionalModel;
+class RenderTimer;
 class Dense3DModelPeakCache;
-class FFTModel;
-class RenderTimer;
 
 enum class BinDisplay {
     AllBins,
@@ -49,13 +49,13 @@
 {
 public:
     struct Sources {
-        Sources() : verticalBinLayer(0), source(0), fft(0) { }
+        Sources() : verticalBinLayer(0) { }
         
         // These must all outlive this class
-        const VerticalBinLayer *verticalBinLayer;  // always
-        const DenseThreeDimensionalModel *source;  // always
-        const FFTModel *fft;                       // optionally
-        std::vector<Dense3DModelPeakCache *> peakCaches; // zero or more
+        const VerticalBinLayer *verticalBinLayer; // always
+        ModelId source; // always; a DenseThreeDimensionalModel
+        ModelId fft; // optionally; an FFTModel; used for phase/peak-freq modes
+        std::vector<ModelId> peakCaches; // zero or more
     };        
 
     struct Parameters {
--- a/layer/FlexiNoteLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/FlexiNoteLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -53,7 +53,6 @@
 
 FlexiNoteLayer::FlexiNoteLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_intelligentActions(true),
     m_dragPointX(0),
@@ -73,15 +72,20 @@
 }
 
 void
-FlexiNoteLayer::setModel(NoteModel *model) 
+FlexiNoteLayer::setModel(ModelId modelId) 
 {
-    if (m_model == model) return;
-    m_model = model;
+    auto newModel = ModelById::getAs<NoteModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a NoteModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
-
-    // m_scaleMinimum = 0;
-    // m_scaleMaximum = 0;
+    if (newModel) {
+        connectSignals(m_model);
+    }
 
     emit modelReplaced();
 }
@@ -123,7 +127,8 @@
 QString
 FlexiNoteLayer::getScaleUnits() const
 {
-    if (m_model) return m_model->getScaleUnits();
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (model) return model->getScaleUnits();
     else return "";
 }
 
@@ -144,7 +149,8 @@
     } else if (name == "Scale Units") {
 
         if (deflt) *deflt = 0;
-        if (m_model) {
+        auto model = ModelById::getAs<NoteModel>(m_model);
+        if (model) {
             val = UnitDatabase::getInstance()->getUnitId
                 (getScaleUnits());
         }
@@ -179,10 +185,11 @@
     if (name == "Vertical Scale") {
         setVerticalScale(VerticalScale(value));
     } else if (name == "Scale Units") {
-        if (m_model) {
-            m_model->setScaleUnits
+        auto model = ModelById::getAs<NoteModel>(m_model);
+        if (model) {
+            model->setScaleUnits
                 (UnitDatabase::getInstance()->getUnitById(value));
-            emit modelChanged();
+            emit modelChanged(m_model);
         }
     } else {
         return SingleColourLayer::setProperty(name, value);
@@ -215,13 +222,22 @@
 //    return false;
 }
 
+int
+FlexiNoteLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 bool
 FlexiNoteLayer::getValueExtents(double &min, double &max,
                                 bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
-    min = m_model->getValueMinimum();
-    max = m_model->getValueMaximum();
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
+    min = model->getValueMinimum();
+    max = model->getValueMaximum();
 
     if (shouldConvertMIDIToHz()) {
         unit = "Hz";
@@ -238,7 +254,8 @@
 bool
 FlexiNoteLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model || shouldAutoAlign()) {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || shouldAutoAlign()) {
 //        std::cerr << "No model or shouldAutoAlign()" << std::endl;
         return false;
     }
@@ -250,8 +267,8 @@
     }
 
     if (m_scaleMinimum == m_scaleMaximum) {
-        min = m_model->getValueMinimum();
-        max = m_model->getValueMaximum();
+        min = model->getValueMinimum();
+        max = model->getValueMaximum();
     } else {
         min = m_scaleMinimum;
         max = m_scaleMaximum;
@@ -272,7 +289,8 @@
 bool
 FlexiNoteLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     if (min == max) {
         if (min == 0.f) {
@@ -297,7 +315,8 @@
 FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const
 {
     if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return 0;
 
     defaultStep = 0;
     return 100;
@@ -307,7 +326,8 @@
 FlexiNoteLayer::getCurrentVerticalZoomStep() const
 {
     if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return 0;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return 0;
@@ -328,7 +348,8 @@
 FlexiNoteLayer::setVerticalZoomStep(int step)
 {
     if (shouldAutoAlign()) return;
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return;
@@ -378,7 +399,8 @@
 RangeMapper *
 FlexiNoteLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return nullptr;
     
     RangeMapper *mapper;
 
@@ -401,21 +423,22 @@
 EventVector
 FlexiNoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return {};
     
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector local = m_model->getEventsCovering(frame);
+    EventVector local = model->getEventsCovering(frame);
     if (!local.empty()) return local;
 
     int fuzz = ViewManager::scalePixelSize(2);
     sv_frame_t start = v->getFrameForX(x - fuzz);
     sv_frame_t end = v->getFrameForX(x + fuzz);
 
-    local = m_model->getEventsStartingWithin(frame, end - frame);
+    local = model->getEventsStartingWithin(frame, end - frame);
     if (!local.empty()) return local;
 
-    local = m_model->getEventsSpanning(start, frame - start);
+    local = model->getEventsSpanning(start, frame - start);
     if (!local.empty()) return local;
 
     return {};
@@ -424,11 +447,12 @@
 bool
 FlexiNoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return false;
 
     int nearestDistance = -1;
@@ -448,11 +472,12 @@
 FlexiNoteLayer::getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &point) const
 {
     // GF: find the note that is closest to the cursor
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return false;
 
     int nearestDistance = -1;
@@ -473,12 +498,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return tr("No local points");
@@ -493,9 +519,9 @@
         int y = getYForValue(v, i->getValue());
         int h = NOTE_HEIGHT; // GF: larger notes
 
-        if (m_model->getValueQuantization() != 0.0) {
+        if (model->getValueQuantization() != 0.0) {
             h = y - getYForValue
-                (v, i->getValue() + m_model->getValueQuantization());
+                (v, i->getValue() + model->getValueQuantization());
             if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
         }
 
@@ -509,9 +535,9 @@
     if (i == points.end()) return tr("No local points");
 
     RealTime rt = RealTime::frame2RealTime(note.getFrame(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     RealTime rd = RealTime::frame2RealTime(note.getDuration(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     
     QString pitchText;
 
@@ -562,11 +588,12 @@
                                    int &resolution,
                                    SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
     EventVector points;
 
     if (snap == SnapNeighbouring) {
@@ -577,7 +604,7 @@
         return true;
     }    
 
-    points = m_model->getEventsCovering(frame);
+    points = model->getEventsCovering(frame);
     sv_frame_t snapped = frame;
     bool found = false;
 
@@ -650,8 +677,9 @@
 
         if (!v->getValueExtents(queryUnits, min, max, log)) {
 
-            min = m_model->getValueMinimum();
-            max = m_model->getValueMaximum();
+            auto model = ModelById::getAs<NoteModel>(m_model);
+            min = model->getValueMinimum();
+            max = model->getValueMaximum();
 
             if (shouldConvertMIDIToHz()) {
                 min = Pitch::getFrequencyForPitch(int(lrint(min)));
@@ -752,16 +780,16 @@
 bool
 FlexiNoteLayer::shouldAutoAlign() const
 {
-    if (!m_model) return false;
     return (m_verticalScale == AutoAlignScale);
 }
 
 void
 FlexiNoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("FlexiNoteLayer::paint", true);
@@ -770,7 +798,7 @@
     sv_frame_t frame0 = v->getFrameForX(x0);
     sv_frame_t frame1 = v->getFrameForX(x1);
 
-    EventVector points(m_model->getEventsSpanning(frame0, frame1 - frame0));
+    EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
     if (points.empty()) return;
 
     paint.setPen(getBaseQColor());
@@ -779,10 +807,10 @@
     brushColour.setAlpha(80);
 
 //    SVDEBUG << "FlexiNoteLayer::paint: resolution is "
-//        << m_model->getResolution() << " frames" << endl;
+//        << model->getResolution() << " frames" << endl;
 
-    double min = m_model->getValueMinimum();
-    double max = m_model->getValueMaximum();
+    double min = model->getValueMinimum();
+    double max = model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     QPoint localPos;
@@ -810,8 +838,8 @@
         int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
         int h = NOTE_HEIGHT; //GF: larger notes
     
-        if (m_model->getValueQuantization() != 0.0) {
-            h = y - getYForValue(v, p.getValue() + m_model->getValueQuantization());
+        if (model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, p.getValue() + model->getValueQuantization());
             if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
         }
 
@@ -827,7 +855,7 @@
             paint.setPen(v->getForeground());
         
             QString vlabel = tr("freq: %1%2")
-                .arg(p.getValue()).arg(m_model->getScaleUnits());
+                .arg(p.getValue()).arg(model->getScaleUnits());
             PaintAssistant::drawVisibleText
                 (v, paint, 
                  x,
@@ -837,7 +865,7 @@
 
             QString hlabel = tr("dur: %1")
                 .arg(RealTime::frame2RealTime
-                     (p.getDuration(), m_model->getSampleRate()).toText(true)
+                     (p.getDuration(), model->getSampleRate()).toText(true)
                      .c_str());
             PaintAssistant::drawVisibleText
                 (v, paint, 
@@ -869,7 +897,7 @@
 int
 FlexiNoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
-    if (!m_model || shouldAutoAlign()) {
+    if (shouldAutoAlign()) {
         return 0;
     } else  {
         if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
@@ -883,7 +911,8 @@
 void
 FlexiNoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model || m_model->isEmpty()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || model->isEmpty()) return;
 
     QString unit;
     double min, max;
@@ -923,11 +952,12 @@
 {
 //    SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double value = getValueForY(v, e->y());
 
@@ -935,7 +965,7 @@
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -946,11 +976,12 @@
 {
 //    SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double newValue = getValueForY(v, e->y());
 
@@ -975,7 +1006,8 @@
 FlexiNoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
     finish(m_editingCommand);
     m_editingCommand = nullptr;
     m_editing = false;
@@ -984,7 +1016,8 @@
 void
 FlexiNoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
 
@@ -1004,15 +1037,15 @@
 void
 FlexiNoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
-
+    if (!m_editing) return;
     m_editing = false;
 
     Event p(0);
     if (!getPointToDrag(v, e->x(), e->y(), p)) return;
-    if (p.getFrame() != m_editingPoint.getFrame() || p.getValue() != m_editingPoint.getValue()) return;
+    if (p.getFrame() != m_editingPoint.getFrame() ||
+        p.getValue() != m_editingPoint.getValue()) return;
 
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
     m_editingCommand->remove(m_editingPoint);
     finish(m_editingCommand);
     m_editingCommand = nullptr;
@@ -1025,7 +1058,8 @@
 //    SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
     m_originalPoint = m_editingPoint;
@@ -1056,7 +1090,7 @@
     m_greatestLeftNeighbourFrame = -1;
     m_smallestRightNeighbourFrame = std::numeric_limits<int>::max();
 
-    EventVector allEvents = m_model->getAllEvents();
+    EventVector allEvents = model->getAllEvents();
     
     for (auto currentNote: allEvents) {
         
@@ -1082,7 +1116,8 @@
 //    SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
     std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     int xdist = e->x() - m_dragStartX;
     int ydist = e->y() - m_dragStartY;
@@ -1091,12 +1126,13 @@
 
     sv_frame_t dragFrame = v->getFrameForX(newx);
     if (dragFrame < 0) dragFrame = 0;
-    dragFrame = dragFrame / m_model->getResolution() * m_model->getResolution();
+    dragFrame = dragFrame / model->getResolution() * model->getResolution();
     
     double value = getValueForY(v, newy);
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Drag Point"));
+        m_editingCommand =
+            new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
     }
     m_editingCommand->remove(m_editingPoint);
 
@@ -1182,7 +1218,8 @@
     std::cerr << "FlexiNoteLayer::editEnd("
               << e->x() << "," << e->y() << ")" << std::endl;
     
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
 
@@ -1218,9 +1255,11 @@
 void
 FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+
     // 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;
-    if (!m_model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
     // m_originalPoint = m_editingPoint;
@@ -1241,9 +1280,10 @@
 void
 FlexiNoteLayer::splitEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
     // GF: note splitting ends. (!! remove printing soon)
     std::cerr << "splitEnd" << std::endl;
-    if (!m_model || !m_editing || m_editMode != SplitNote) return;
+    if (!model || !m_editing || m_editMode != SplitNote) return;
 
     int xdist = e->x() - m_dragStartX;
     int ydist = e->y() - m_dragStartY;
@@ -1266,13 +1306,15 @@
 void
 FlexiNoteLayer::splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e)
 {
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return;
     
     Event note(*onPoints.begin());
 
-    ChangeEventsCommand *command = new ChangeEventsCommand
-        (m_model, tr("Edit Point"));
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
     command->remove(note);
 
     if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
@@ -1306,15 +1348,16 @@
 void
 FlexiNoteLayer::addNote(LayerGeometryProvider *v, QMouseEvent *e)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
     std::cerr << "addNote" << std::endl;
-    if (!m_model) return;
+    if (!model) return;
 
     sv_frame_t duration = 10000;
     
     sv_frame_t frame = v->getFrameForX(e->x());
     double value = getValueForY(v, e->y());
     
-    EventVector noteList = m_model->getAllEvents();
+    EventVector noteList = model->getAllEvents();
 
     if (m_intelligentActions) {
         sv_frame_t smallestRightNeighbourFrame = 0;
@@ -1333,16 +1376,15 @@
     }
 
     if (!m_intelligentActions || 
-        (m_model->getEventsCovering(frame).empty() && duration > 0)) {
+        (model->getEventsCovering(frame).empty() && duration > 0)) {
         Event newNote(frame, float(value), duration, 100.f, tr("new note"));
-        ChangeEventsCommand *command = new ChangeEventsCommand
-            (m_model, tr("Add Point"));
+        auto command = new ChangeEventsCommand(m_model.untyped, tr("Add Point"));
         command->add(newNote);
         finish(command);
     }
 }
 
-SparseTimeValueModel *
+ModelId
 FlexiNoteLayer::getAssociatedPitchModel(LayerGeometryProvider *v) const
 {
     // Better than we used to do, but still not very satisfactory
@@ -1354,29 +1396,28 @@
         if (layer &&
             layer->getLayerPresentationName() != "candidate") {
 //            cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
-            SparseTimeValueModel *model = qobject_cast<SparseTimeValueModel *>
-                (layer->getModel());
-//            cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl;
+            auto modelId = layer->getModel();
+            auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
             if (model && model->getScaleUnits() == "Hz") {
-                cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
-                return model;
+//                cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
+                return modelId;
             }
         }
     }
-    cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
-    return nullptr;
+//    cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
+    return {};
 }
 
 void
 FlexiNoteLayer::snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
-    ChangeEventsCommand *command = new ChangeEventsCommand
-        (m_model, tr("Snap Notes"));
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Snap Notes"));
 
     cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
 
@@ -1408,18 +1449,20 @@
 void
 FlexiNoteLayer::mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
     EventVector points;
     if (inclusive) {
-        points = m_model->getEventsSpanning(s.getStartFrame(), s.getDuration());
+        points = model->getEventsSpanning(s.getStartFrame(), s.getDuration());
     } else {
-        points = m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        points = model->getEventsWithin(s.getStartFrame(), s.getDuration());
     }
         
     EventVector::iterator i = points.begin();
     if (i == points.end()) return;
 
-    ChangeEventsCommand *command = 
-        new ChangeEventsCommand(m_model, tr("Merge Notes"));
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Merge Notes"));
 
     Event newNote(*i);
 
@@ -1446,7 +1489,8 @@
 bool
 FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const
 {
-    SparseTimeValueModel *model = getAssociatedPitchModel(v);
+    ModelId modelId = getAssociatedPitchModel(v);
+    auto model = ModelById::getAs<SparseTimeValueModel>(modelId);
     if (!model) return false;
         
     std::cerr << model->getTypeName() << std::endl;
@@ -1525,8 +1569,7 @@
 void
 FlexiNoteLayer::getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
 {
-    // GF: TODO: consoloidate the tolerance values
-    if (!m_model) return;
+    // GF: TODO: consolidate the tolerance values
 
     int ctol = 0;
     int noteStartX = v->getXForFrame(note.getFrame());
@@ -1555,7 +1598,8 @@
 FlexiNoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
     std::cerr << "Opening note editor dialog" << std::endl;
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     Event note(0);
     if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
@@ -1563,7 +1607,7 @@
 //    Event note = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowDuration |
          ItemEditDialog::ShowValue |
@@ -1583,8 +1627,7 @@
             .withDuration(dialog->getFrameDuration())
             .withLabel(dialog->getText());
         
-        ChangeEventsCommand *command = new ChangeEventsCommand
-            (m_model, tr("Edit Point"));
+        auto command = new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
         command->remove(note);
         command->add(newNote);
         finish(command);
@@ -1597,13 +1640,13 @@
 void
 FlexiNoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
-
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1618,13 +1661,13 @@
 void
 FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model || !s.getDuration()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !s.getDuration()) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -1648,13 +1691,14 @@
 void
 FlexiNoteLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+    auto command =
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1666,13 +1710,14 @@
 void
 FlexiNoteLayer::deleteSelectionInclusive(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+    auto command =
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsSpanning(s.getStartFrame(), s.getDuration());
+        model->getEventsSpanning(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1684,10 +1729,11 @@
 void
 FlexiNoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -1697,7 +1743,8 @@
 bool
 FlexiNoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /*frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -1720,8 +1767,7 @@
         }
     }
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -1745,8 +1791,8 @@
         Event p = *i;
         Event newPoint = p;
         if (!p.hasValue()) {
-            newPoint = newPoint.withValue((m_model->getValueMinimum() +
-                                           m_model->getValueMaximum()) / 2);
+            newPoint = newPoint.withValue((model->getValueMinimum() +
+                                           model->getValueMaximum()) / 2);
         }
         if (!p.hasDuration()) {
             sv_frame_t nextFrame = frame;
@@ -1758,7 +1804,7 @@
                 nextFrame = j->getFrame();
             }
             if (nextFrame == frame) {
-                newPoint = newPoint.withDuration(m_model->getResolution());
+                newPoint = newPoint.withDuration(model->getResolution());
             } else {
                 newPoint = newPoint.withDuration(nextFrame - frame);
             }
@@ -1789,13 +1835,11 @@
         if (lrintf(p.getValue()) == pitch) {
             m_pendingNoteOns.erase(i);
             Event note = p.withDuration(frame - p.getFrame());
-            if (m_model) {
-                ChangeEventsCommand *c = new ChangeEventsCommand
-                    (m_model, tr("Record Note"));
-                c->add(note);
-                // execute and bundle:
-                CommandHistory::getInstance()->addCommand(c, true, true);
-            }
+            auto c = new ChangeEventsCommand
+                (m_model.untyped, tr("Record Note"));
+            c->add(note);
+            // execute and bundle:
+            CommandHistory::getInstance()->addCommand(c, true, true);
             break;
         }
     }
@@ -1840,10 +1884,13 @@
 void
 FlexiNoteLayer::setVerticalRangeToNoteRange(LayerGeometryProvider *v)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
     double minf = std::numeric_limits<double>::max();
     double maxf = 0;
     bool hasNotes = 0;
-    EventVector allPoints = m_model->getAllEvents();
+    EventVector allPoints = model->getAllEvents();
     for (EventVector::const_iterator i = allPoints.begin();
          i != allPoints.end(); ++i) {
         hasNotes = 1;
--- a/layer/FlexiNoteLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/FlexiNoteLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -81,8 +81,8 @@
     void snapSelectedNotesToPitchTrack(LayerGeometryProvider *v, Selection s);
     void mergeNotes(LayerGeometryProvider *v, Selection s, bool inclusive);
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(NoteModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a NoteModel please
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -118,10 +118,10 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
-                                 bool &log, QString &unit) const override;
+                         bool &log, QString &unit) const override;
 
     bool getDisplayExtents(double &min, double &max) const override;
     bool setDisplayExtents(double min, double max) override;
@@ -176,11 +176,11 @@
     bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const;
     bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, Event &) const;
     void getRelativeMousePosition(LayerGeometryProvider *v, Event &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const;
-    SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const;
+    ModelId getAssociatedPitchModel(LayerGeometryProvider *v) const;
     bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, Event &note) const;
     void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e);
 
-    NoteModel *m_model;
+    ModelId m_model;
     bool m_editing;
     bool m_intelligentActions;
     int m_dragPointX;
--- a/layer/HorizontalFrequencyScale.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/HorizontalFrequencyScale.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -56,6 +56,11 @@
     int marginx = -1;
 
     for (int i = 0; i < n; ++i) {
+
+        // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+        // replacement (horizontalAdvance) was only added in Qt 5.11
+        // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
         
         double val = ticks[i].value;
         QString label = QString::fromStdString(ticks[i].label);
--- a/layer/ImageLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/ImageLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -43,8 +43,6 @@
 ImageLayer::m_imageMapMutex;
 
 ImageLayer::ImageLayer() :
-    Layer(),
-    m_model(nullptr),
     m_editing(false),
     m_editingCommand(nullptr)
 {
@@ -58,13 +56,29 @@
     }
 }
 
+int
+ImageLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-ImageLayer::setModel(ImageModel *model)
+ImageLayer::setModel(ModelId modelId)
 {
-    if (m_model == model) return;
-    m_model = model;
+    auto newModel = ModelById::getAs<ImageModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not an ImageModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+        connectSignals(m_model);
+    }
 
     emit modelReplaced();
 }
@@ -122,10 +136,11 @@
 EventVector
 ImageLayer::getLocalPoints(LayerGeometryProvider *v, int x, int ) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return {};
 
 //    SVDEBUG << "ImageLayer::getLocalPoints(" << x << "," << y << "):";
-    EventVector points(m_model->getAllEvents());
+    EventVector points(model->getAllEvents());
 
     EventVector rv;
 
@@ -169,12 +184,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x, pos.y());
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return "";
@@ -183,7 +199,7 @@
 
 //    int useFrame = points.begin()->frame;
 
-//    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+//    RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
 
     QString text;
 /*    
@@ -208,11 +224,12 @@
                                int &resolution,
                                SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame), -1);
@@ -222,7 +239,7 @@
     }    
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -237,9 +254,10 @@
 void
 ImageLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("ImageLayer::paint", true);
@@ -250,7 +268,7 @@
     sv_frame_t frame0 = v->getFrameForX(x0);
     sv_frame_t frame1 = v->getFrameForX(x1);
 
-    EventVector points(m_model->getEventsWithin(frame0, frame1 - frame0, 2));
+    EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
     if (points.empty()) return;
 
     paint.save();
@@ -341,6 +359,11 @@
             likelyWidth = availableWidth;
         }
 
+        // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+        // replacement (horizontalAdvance) was only added in Qt 5.11
+        // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
         int singleWidth = paint.fontMetrics().width(label);
         if (singleWidth < availableWidth && singleWidth < likelyWidth * 2) {
             likelyWidth = singleWidth + 4;
@@ -513,20 +536,21 @@
 {
 //    SVDEBUG << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) {
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) {
         SVDEBUG << "ImageLayer::drawStart: no model" << endl;
         return;
     }
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     m_editingPoint = Event(frame);
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, "Add Image");
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, "Add Image");
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -537,11 +561,12 @@
 {
 //    SVDEBUG << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     m_editingCommand->remove(m_editingPoint);
     m_editingPoint = m_editingPoint
@@ -553,7 +578,8 @@
 ImageLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     ImageDialog dialog(tr("Select image"), "", "");
 
@@ -586,8 +612,8 @@
     }
 
     Event point = Event(frame).withURI(url);
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, "Add Image");
+    auto command =
+        new ChangeEventsCommand(m_model.untyped, "Add Image");
     command->add(point);
     finish(command);
     return true;
@@ -598,7 +624,8 @@
 {
 //    SVDEBUG << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
     EventVector points = getLocalPoints(v, e->x(), e->y());
     if (points.empty()) return;
@@ -618,16 +645,17 @@
 void
 ImageLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
     sv_frame_t frame = m_originalPoint.getFrame() + frameDiff;
 
     if (frame < 0) frame = 0;
-    frame = (frame / m_model->getResolution()) * m_model->getResolution();
+    frame = (frame / model->getResolution()) * model->getResolution();
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Move Image"));
+        m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Move Image"));
     }
 
     m_editingCommand->remove(m_editingPoint);
@@ -640,7 +668,8 @@
 ImageLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
         finish(m_editingCommand);
@@ -653,7 +682,8 @@
 bool
 ImageLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return false;
 
     EventVector points = getLocalPoints(v, e->x(), e->y());
     if (points.empty()) return false;
@@ -669,8 +699,8 @@
 
         checkAddSource(dialog.getImage());
 
-        ChangeEventsCommand *command =
-            new ChangeEventsCommand(m_model, tr("Edit Image"));
+        auto command =
+            new ChangeEventsCommand(m_model.untyped, tr("Edit Image"));
         command->remove(*points.begin());
         command->add(points.begin()->
                      withURI(dialog.getImage()).withLabel(dialog.getLabel()));
@@ -683,13 +713,14 @@
 void
 ImageLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+    auto command =
+        new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -704,13 +735,14 @@
 void
 ImageLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+    auto command =
+        new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -732,13 +764,14 @@
 void
 ImageLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selection"));
+    auto command =
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -750,10 +783,11 @@
 void
 ImageLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -763,7 +797,8 @@
 bool
 ImageLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -786,8 +821,7 @@
         }
     }
 
-    ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+    auto command = new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -863,7 +897,8 @@
 void
 ImageLayer::checkAddSources()
 {
-    const EventVector &points(m_model->getAllEvents());
+    auto model = ModelById::getAs<ImageModel>(m_model);
+    const EventVector &points(model->getAllEvents());
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -895,7 +930,7 @@
     m_images.erase(img);
     for (ViewImageMap::iterator i = m_scaled.begin(); i != m_scaled.end(); ++i) {
         i->second.erase(img);
-        emit modelChanged();
+        emit modelChanged(getModel());
     }
 }
 
--- a/layer/ImageLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/ImageLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -43,8 +43,8 @@
     QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override;
 
     bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
-                                    int &resolution,
-                                    SnapType snap) const override;
+                            int &resolution,
+                            SnapType snap) const override;
 
     void drawStart(LayerGeometryProvider *v, QMouseEvent *) override;
     void drawDrag(LayerGeometryProvider *v, QMouseEvent *) override;
@@ -64,16 +64,16 @@
 
     bool editOpen(LayerGeometryProvider *, QMouseEvent *) override; // on double-click
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(ImageModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // an ImageModel please
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
     PropertyType getPropertyType(const PropertyName &) const override;
     int getPropertyRangeAndValue(const PropertyName &,
-                                         int *min, int *max, int *deflt) const override;
+                                 int *min, int *max, int *deflt) const override;
     QString getPropertyValueLabel(const PropertyName &,
-                                          int value) const override;
+                                  int value) const override;
     void setProperty(const PropertyName &, int value) override;
 
     ColourSignificance getLayerColourSignificance() const override {
@@ -84,13 +84,13 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
-                                 bool &logarithmic, QString &unit) const override;
+                         bool &logarithmic, QString &unit) const override;
 
     void toXml(QTextStream &stream, QString indent = "",
-                       QString extraAttributes = "") const override;
+               QString extraAttributes = "") const override;
 
     int getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &) const override { return 0; }
 
@@ -127,7 +127,7 @@
     QString getLocalFilename(QString img) const;
     void checkAddSource(QString img) const;
 
-    ImageModel *m_model;
+    ModelId m_model; // an ImageModel
     bool m_editing;
     QPoint m_editOrigin;
     Event m_originalPoint;
--- a/layer/Layer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Layer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -46,19 +46,22 @@
 }
 
 void
-Layer::connectSignals(const Model *model)
+Layer::connectSignals(ModelId modelId)
 {
-    connect(model, SIGNAL(modelChanged()),
-            this, SIGNAL(modelChanged()));
+    auto model = ModelById::get(modelId);
+    if (!model) return;
+    
+    connect(model.get(), SIGNAL(modelChanged(ModelId)),
+            this, SIGNAL(modelChanged(ModelId)));
 
-    connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-            this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
+    connect(model.get(), SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+            this, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
 
-    connect(model, SIGNAL(completionChanged()),
-            this, SIGNAL(modelCompletionChanged()));
+    connect(model.get(), SIGNAL(completionChanged(ModelId)),
+            this, SIGNAL(modelCompletionChanged(ModelId)));
 
-    connect(model, SIGNAL(alignmentCompletionChanged()),
-            this, SIGNAL(modelAlignmentCompletionChanged()));
+    connect(model.get(), SIGNAL(alignmentCompletionChanged(ModelId)),
+            this, SIGNAL(modelAlignmentCompletionChanged(ModelId)));
 }
 
 QString
@@ -84,7 +87,8 @@
         (factory->getLayerType(this));
 
     QString modelName;
-    if (getModel()) modelName = getModel()->objectName();
+    auto model = ModelById::get(getModel());
+    if (model) modelName = model->objectName();
             
     QString text;
     if (modelName != "") {
@@ -103,15 +107,11 @@
     emit layerNameChanged();
 }
 
-PlayParameters *
+std::shared_ptr<PlayParameters>
 Layer::getPlayParameters() 
 {
-//    cerr << "Layer (" << this << ", " << objectName() << ")::getPlayParameters: model is "<< getModel() << endl;
-    const Model *model = getModel();
-    if (model) {
-        return PlayParameterRepository::getInstance()->getPlayParameters(model);
-    }
-    return nullptr;
+    return PlayParameterRepository::getInstance()->getPlayParameters
+        (getModel().untyped);
 }
 
 void
@@ -143,10 +143,10 @@
 {
     if (!hasTimeXAxis()) return false;
 
-    const Model *m = getModel();
-    if (!m) return false;
+    auto model = ModelById::get(getModel());
+    if (!model) return false;
 
-    value = double(v->getFrameForX(x)) / m->getSampleRate();
+    value = double(v->getFrameForX(x)) / model->getSampleRate();
     unit = "s";
     return true;
 }
@@ -168,10 +168,9 @@
 sv_frame_t
 Layer::alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
-    const Model *m = getModel();
-    SVDEBUG << "Layer::alignToReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : nullptr) << endl;
-    if (m && m->getAlignmentReference()) {
-        return m->alignToReference(frame);
+    auto model = ModelById::get(getModel());
+    if (model && !model->getAlignmentReference().isNone()) {
+        return model->alignToReference(frame);
     } else {
         return v->getView()->alignToReference(frame);
     }
@@ -180,10 +179,9 @@
 sv_frame_t
 Layer::alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const
 {
-    const Model *m = getModel();
-    SVDEBUG << "Layer::alignFromReference(" << frame << "): model = " << m << ", alignment reference = " << (m ? m->getAlignmentReference() : nullptr) << endl;
-    if (m && m->getAlignmentReference()) {
-        return m->alignFromReference(frame);
+    auto model = ModelById::get(getModel());
+    if (model && !model->getAlignmentReference().isNone()) {
+        return model->alignFromReference(frame);
     } else {
         return v->getView()->alignFromReference(frame);
     }
@@ -644,12 +642,16 @@
             .arg(extraAttributes).arg(encodeEntities(m_presentationName));
     }
 
+    int modelExportId = -1;
+    auto model = ModelById::get(getModel());
+    if (model) modelExportId = model->getExportId();
+    
     stream << QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5")
         .arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
                             (LayerFactory::getInstance()->getLayerType(this))))
         .arg(getExportId())
         .arg(encodeEntities(objectName()))
-        .arg(getModel() ? getModel()->getExportId() : -1)
+        .arg(modelExportId)
         .arg(extraAttributes);
 
     if (m_measureRects.empty()) {
@@ -678,12 +680,16 @@
             .arg(extraAttributes).arg(encodeEntities(m_presentationName));
     }
 
+    int modelExportId = -1;
+    auto model = ModelById::get(getModel());
+    if (model) modelExportId = model->getExportId();
+
     stream << QString("<layer id=\"%2\" type=\"%1\" name=\"%3\" model=\"%4\" %5/>\n")
         .arg(encodeEntities(LayerFactory::getInstance()->getLayerTypeName
                             (LayerFactory::getInstance()->getLayerType(this))))
         .arg(getExportId())
         .arg(encodeEntities(objectName()))
-        .arg(getModel() ? getModel()->getExportId() : -1)
+        .arg(modelExportId)
         .arg(extraAttributes);
 }
 
--- a/layer/Layer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/Layer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -20,6 +20,8 @@
 #include "base/XmlExportable.h"
 #include "base/Selection.h"
 
+#include "data/model/Model.h"
+
 #include "widgets/CommandHistory.h"
 
 #include "system/System.h"
@@ -36,7 +38,6 @@
 #include <iostream>
 
 class ZoomConstraint;
-class Model;
 class QPainter;
 class View;
 class LayerGeometryProvider;
@@ -59,10 +60,10 @@
     Layer();
     virtual ~Layer();
 
-    virtual const Model *getModel() const = 0;
-    Model *getModel() {
-        return const_cast<Model *>(const_cast<const Layer *>(this)->getModel());
-    }
+    /**
+     * Return the ID of the model represented in this layer.
+     */
+    virtual ModelId getModel() const = 0;
     
     /**
      * Return a zoom constraint object defining the supported zoom
@@ -410,7 +411,12 @@
      */
     virtual bool isLayerDormant(const LayerGeometryProvider *v) const;
 
-    PlayParameters *getPlayParameters() override;
+    /**
+     * Return the play parameters for this layer, if any. The return
+     * value is a shared_ptr that can be passed to (e.g.)
+     * PlayParameterRepository::EditCommand to change the parameters.
+     */
+    std::shared_ptr<PlayParameters> getPlayParameters() override;
 
     /**
      * True if this layer will need to place text labels when it is
@@ -556,10 +562,10 @@
     void showLayer(LayerGeometryProvider *, bool show);
 
 signals:
-    void modelChanged();
-    void modelCompletionChanged();
-    void modelAlignmentCompletionChanged();
-    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
+    void modelChanged(ModelId);
+    void modelCompletionChanged(ModelId);
+    void modelAlignmentCompletionChanged(ModelId);
+    void modelChangedWithin(ModelId, sv_frame_t startFrame, sv_frame_t endFrame);
     void modelReplaced();
 
     void layerParametersChanged();
@@ -570,7 +576,7 @@
     void verticalZoomChanged();
 
 protected:
-    void connectSignals(const Model *);
+    void connectSignals(ModelId);
 
     virtual sv_frame_t alignToReference(LayerGeometryProvider *v, sv_frame_t frame) const;
     virtual sv_frame_t alignFromReference(LayerGeometryProvider *v, sv_frame_t frame) const;
--- a/layer/LayerFactory.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/LayerFactory.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -133,55 +133,55 @@
 }
 
 LayerFactory::LayerTypeSet
-LayerFactory::getValidLayerTypes(Model *model)
+LayerFactory::getValidLayerTypes(ModelId modelId)
 {
     LayerTypeSet types;
 
-    if (dynamic_cast<DenseThreeDimensionalModel *>(model)) {
+    if (ModelById::getAs<DenseThreeDimensionalModel>(modelId)) {
         types.insert(Colour3DPlot);
         types.insert(Slice);
     }
 
-    if (dynamic_cast<RangeSummarisableTimeValueModel *>(model)) {
+    if (ModelById::getAs<RangeSummarisableTimeValueModel>(modelId)) {
         types.insert(Waveform);
     }
 
-    if (dynamic_cast<DenseTimeValueModel *>(model)) {
+    if (ModelById::getAs<DenseTimeValueModel>(modelId)) {
         types.insert(Spectrogram);
         types.insert(MelodicRangeSpectrogram);
         types.insert(PeakFrequencySpectrogram);
     }
 
-    if (dynamic_cast<SparseOneDimensionalModel *>(model)) {
+    if (ModelById::getAs<SparseOneDimensionalModel>(modelId)) {
         types.insert(TimeInstants);
     }
 
-    if (dynamic_cast<SparseTimeValueModel *>(model)) {
+    if (ModelById::getAs<SparseTimeValueModel>(modelId)) {
         types.insert(TimeValues);
     }
 
-    if (dynamic_cast<NoteModel *>(model)) {
-        NoteModel *nm = dynamic_cast<NoteModel *>(model);
-        if (nm->getSubtype() == NoteModel::FLEXI_NOTE) {
+    if (ModelById::getAs<NoteModel>(modelId)) {
+        auto nm = ModelById::getAs<NoteModel>(modelId);
+        if (nm && nm->getSubtype() == NoteModel::FLEXI_NOTE) {
             types.insert(FlexiNotes);
         } else {
             types.insert(Notes);
         }
     }
 
-    if (dynamic_cast<RegionModel *>(model)) {
+    if (ModelById::getAs<RegionModel>(modelId)) {
         types.insert(Regions);
     }
 
-    if (dynamic_cast<TextModel *>(model)) {
+    if (ModelById::getAs<TextModel>(modelId)) {
         types.insert(Text);
     }
 
-    if (dynamic_cast<ImageModel *>(model)) {
+    if (ModelById::getAs<ImageModel>(modelId)) {
         types.insert(Image);
     }
 
-    if (dynamic_cast<DenseTimeValueModel *>(model)) {
+    if (ModelById::getAs<DenseTimeValueModel>(modelId)) {
         types.insert(Spectrum);
     }
 
@@ -300,11 +300,8 @@
 }
 
 void
-LayerFactory::setModel(Layer *layer, Model *model)
+LayerFactory::setModel(Layer *layer, ModelId model)
 {
-//    if (trySetModel<WaveformLayer, RangeSummarisableTimeValueModel>(layer, model))
-//        return;
-        
     if (trySetModel<WaveformLayer, WaveFileModel>(layer, model))
         return;
 
@@ -341,35 +338,34 @@
     if (trySetModel<Colour3DPlotLayer, DenseThreeDimensionalModel>(layer, model))
         return;
 
-    if (trySetModel<SpectrogramLayer, DenseTimeValueModel>(layer, model))
-        return;
-
     if (trySetModel<SpectrumLayer, DenseTimeValueModel>(layer, model)) 
         return;
-
-//    if (trySetModel<SliceLayer, DenseThreeDimensionalModel>(layer, model)) 
-//        return;
 }
 
-Model *
-LayerFactory::createEmptyModel(LayerType layerType, Model *baseModel)
+std::shared_ptr<Model>
+LayerFactory::createEmptyModel(LayerType layerType, ModelId baseModelId)
 {
+    auto baseModel = ModelById::get(baseModelId);
+    if (!baseModel) return {};
+
+    sv_samplerate_t rate = baseModel->getSampleRate();
+    
     if (layerType == TimeInstants) {
-        return new SparseOneDimensionalModel(baseModel->getSampleRate(), 1);
+        return std::make_shared<SparseOneDimensionalModel>(rate, 1);
     } else if (layerType == TimeValues) {
-        return new SparseTimeValueModel(baseModel->getSampleRate(), 1, true);
+        return std::make_shared<SparseTimeValueModel>(rate, 1, true);
     } else if (layerType == FlexiNotes) {
-        return new NoteModel(baseModel->getSampleRate(), 1, true);
+        return std::make_shared<NoteModel>(rate, 1, true);
     } else if (layerType == Notes) {
-        return new NoteModel(baseModel->getSampleRate(), 1, true);
+        return std::make_shared<NoteModel>(rate, 1, true);
     } else if (layerType == Regions) {
-        return new RegionModel(baseModel->getSampleRate(), 1, true);
+        return std::make_shared<RegionModel>(rate, 1, true);
     } else if (layerType == Text) {
-        return new TextModel(baseModel->getSampleRate(), 1, true);
+        return std::make_shared<TextModel>(rate, 1, true);
     } else if (layerType == Image) {
-        return new ImageModel(baseModel->getSampleRate(), 1, true);
+        return std::make_shared<ImageModel>(rate, 1, true);
     } else {
-        return nullptr;
+        return {};
     }
 }
 
--- a/layer/LayerFactory.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/LayerFactory.h	Wed Jul 17 14:25:16 2019 +0100
@@ -19,8 +19,9 @@
 #include <QString>
 #include <set>
 
+#include "data/model/Model.h"
+
 class Layer;
-class Model;
 class Clipboard;
 
 class LayerFactory
@@ -56,7 +57,7 @@
     virtual ~LayerFactory();
 
     typedef std::set<LayerType> LayerTypeSet;
-    LayerTypeSet getValidLayerTypes(Model *model);
+    LayerTypeSet getValidLayerTypes(ModelId modelId);
 
     /**
      * Return the set of layer types that an end user should be
@@ -86,8 +87,8 @@
 
     bool isLayerSliceable(const Layer *);
 
-    void setModel(Layer *layer, Model *model);
-    Model *createEmptyModel(LayerType type, Model *baseModel);
+    void setModel(Layer *layer, ModelId model);
+    std::shared_ptr<Model> createEmptyModel(LayerType type, ModelId baseModel);
 
     int getChannel(Layer *layer);
     void setChannel(Layer *layer, int channel);
@@ -100,12 +101,14 @@
 
 protected:
     template <typename LayerClass, typename ModelClass>
-    bool trySetModel(Layer *layerBase, Model *modelBase) {
+    bool trySetModel(Layer *layerBase, ModelId modelId) {
         LayerClass *layer = dynamic_cast<LayerClass *>(layerBase);
         if (!layer) return false;
-        ModelClass *model = dynamic_cast<ModelClass *>(modelBase);
-        if (!model) return false;
-        layer->setModel(model);
+        if (!modelId.isNone()) {
+            auto model = ModelById::getAs<ModelClass>(modelId);
+            if (!model) return false;
+        }
+        layer->setModel(modelId);
         return true;
     }
 
--- a/layer/LinearColourScale.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/LinearColourScale.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -26,6 +26,11 @@
 LinearColourScale::getWidth(LayerGeometryProvider *,
                             QPainter &paint)
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     return paint.fontMetrics().width("-000.00") + 15;
 }
 
--- a/layer/LinearNumericalScale.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/LinearNumericalScale.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -27,6 +27,11 @@
 LinearNumericalScale::getWidth(LayerGeometryProvider *,
                                    QPainter &paint)
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     return paint.fontMetrics().width("-000.00") + 10;
 }
 
--- a/layer/LogColourScale.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/LogColourScale.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -28,6 +28,11 @@
 LogColourScale::getWidth(LayerGeometryProvider *,
                             QPainter &paint)
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     return paint.fontMetrics().width("-000.00") + 15;
 }
 
--- a/layer/LogNumericalScale.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/LogNumericalScale.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -28,6 +28,11 @@
 LogNumericalScale::getWidth(LayerGeometryProvider *,
                             QPainter &paint)
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     return paint.fontMetrics().width("-000.00") + 10;
 }
 
--- a/layer/NoteLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/NoteLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -48,7 +48,6 @@
 
 NoteLayer::NoteLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_dragPointX(0),
     m_dragPointY(0),
@@ -65,16 +64,30 @@
     SVDEBUG << "constructed NoteLayer" << endl;
 }
 
+int
+NoteLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-NoteLayer::setModel(NoteModel *model)
-{        
-    if (m_model == model) return;
-    m_model = model;
+NoteLayer::setModel(ModelId modelId)
+{
+    auto newModel = ModelById::getAs<NoteModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a NoteModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
-
-//    SVDEBUG << "NoteLayer::setModel(" << model << ")" << endl;
-
+    if (newModel) {
+        connectSignals(m_model);
+    }
+    
     m_scaleMinimum = 0;
     m_scaleMaximum = 0;
 
@@ -118,7 +131,8 @@
 QString
 NoteLayer::getScaleUnits() const
 {
-    if (m_model) return m_model->getScaleUnits();
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (model) return model->getScaleUnits();
     else return "";
 }
 
@@ -139,9 +153,10 @@
     } else if (name == "Scale Units") {
 
         if (deflt) *deflt = 0;
-        if (m_model) {
+        auto model = ModelById::getAs<NoteModel>(m_model);
+        if (model) {
             val = UnitDatabase::getInstance()->getUnitId
-                (getScaleUnits());
+                (model->getScaleUnits());
         }
 
     } else {
@@ -174,10 +189,11 @@
     if (name == "Vertical Scale") {
         setVerticalScale(VerticalScale(value));
     } else if (name == "Scale Units") {
-        if (m_model) {
-            m_model->setScaleUnits
+        auto model = ModelById::getAs<NoteModel>(m_model);
+        if (model) {
+            model->setScaleUnits
                 (UnitDatabase::getInstance()->getUnitById(value));
-            emit modelChanged();
+            emit modelChanged(m_model);
         }
     } else {
         return SingleColourLayer::setProperty(name, value);
@@ -214,9 +230,10 @@
 NoteLayer::getValueExtents(double &min, double &max,
                            bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
-    min = m_model->getValueMinimum();
-    max = m_model->getValueMaximum();
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
+    min = model->getValueMinimum();
+    max = model->getValueMaximum();
 
     if (shouldConvertMIDIToHz()) {
         unit = "Hz";
@@ -225,7 +242,9 @@
     } else unit = getScaleUnits();
 
     if (m_verticalScale == MIDIRangeScale ||
-        m_verticalScale == LogScale) logarithmic = true;
+        m_verticalScale == LogScale) {
+        logarithmic = true;
+    }
 
     return true;
 }
@@ -233,7 +252,8 @@
 bool
 NoteLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model || shouldAutoAlign()) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || shouldAutoAlign()) return false;
 
     if (m_verticalScale == MIDIRangeScale) {
         min = Pitch::getFrequencyForPitch(0);
@@ -242,8 +262,8 @@
     }
 
     if (m_scaleMinimum == m_scaleMaximum) {
-        min = m_model->getValueMinimum();
-        max = m_model->getValueMaximum();
+        min = model->getValueMinimum();
+        max = model->getValueMaximum();
     } else {
         min = m_scaleMinimum;
         max = m_scaleMaximum;
@@ -264,7 +284,7 @@
 bool
 NoteLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
+    if (m_model.isNone()) return false;
 
     if (min == max) {
         if (min == 0.f) {
@@ -288,9 +308,7 @@
 int
 NoteLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
-
+    if (shouldAutoAlign() || m_model.isNone()) return 0;
     defaultStep = 0;
     return 100;
 }
@@ -298,8 +316,7 @@
 int
 NoteLayer::getCurrentVerticalZoomStep() const
 {
-    if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    if (shouldAutoAlign() || m_model.isNone()) return 0;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return 0;
@@ -319,8 +336,7 @@
 void
 NoteLayer::setVerticalZoomStep(int step)
 {
-    if (shouldAutoAlign()) return;
-    if (!m_model) return;
+    if (shouldAutoAlign() || m_model.isNone()) return;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return;
@@ -370,7 +386,7 @@
 RangeMapper *
 NoteLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    if (m_model.isNone()) return nullptr;
     
     RangeMapper *mapper;
 
@@ -393,21 +409,22 @@
 EventVector
 NoteLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return {};
     
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector local = m_model->getEventsCovering(frame);
+    EventVector local = model->getEventsCovering(frame);
     if (!local.empty()) return local;
 
     int fuzz = ViewManager::scalePixelSize(2);
     sv_frame_t start = v->getFrameForX(x - fuzz);
     sv_frame_t end = v->getFrameForX(x + fuzz);
 
-    local = m_model->getEventsStartingWithin(frame, end - frame);
+    local = model->getEventsStartingWithin(frame, end - frame);
     if (!local.empty()) return local;
 
-    local = m_model->getEventsSpanning(start, frame - start);
+    local = model->getEventsSpanning(start, frame - start);
     if (!local.empty()) return local;
 
     return {};
@@ -416,11 +433,12 @@
 bool
 NoteLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return false;
 
     int nearestDistance = -1;
@@ -441,12 +459,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return tr("No local points");
@@ -461,9 +480,9 @@
         int y = getYForValue(v, i->getValue());
         int h = 3;
 
-        if (m_model->getValueQuantization() != 0.0) {
+        if (model->getValueQuantization() != 0.0) {
             h = y - getYForValue
-                (v, i->getValue() + m_model->getValueQuantization());
+                (v, i->getValue() + model->getValueQuantization());
             if (h < 3) h = 3;
         }
 
@@ -476,9 +495,9 @@
     if (i == points.end()) return tr("No local points");
 
     RealTime rt = RealTime::frame2RealTime(note.getFrame(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     RealTime rd = RealTime::frame2RealTime(note.getDuration(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     
     QString pitchText;
 
@@ -530,7 +549,8 @@
                               int &resolution,
                               SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
@@ -541,7 +561,7 @@
     // an editing operation, i.e. closest feature in either direction
     // but only if it is "close enough"
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame));
@@ -551,7 +571,7 @@
     }    
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -570,6 +590,9 @@
     max = 0.0;
     log = false;
 
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
+    
     QString queryUnits;
     if (shouldConvertMIDIToHz()) queryUnits = "Hz";
     else queryUnits = getScaleUnits();
@@ -578,8 +601,8 @@
 
         if (!v->getValueExtents(queryUnits, min, max, log)) {
 
-            min = m_model->getValueMinimum();
-            max = m_model->getValueMaximum();
+            min = model->getValueMinimum();
+            max = model->getValueMaximum();
 
             if (shouldConvertMIDIToHz()) {
                 min = Pitch::getFrequencyForPitch(int(lrint(min)));
@@ -681,16 +704,17 @@
 bool
 NoteLayer::shouldAutoAlign() const
 {
-    if (!m_model) return false;
+    if (m_model.isNone()) return false;
     return (m_verticalScale == AutoAlignScale);
 }
 
 void
 NoteLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("NoteLayer::paint", true);
@@ -699,7 +723,7 @@
     sv_frame_t frame0 = v->getFrameForX(x0);
     sv_frame_t frame1 = v->getFrameForX(x1);
 
-    EventVector points(m_model->getEventsSpanning(frame0, frame1 - frame0));
+    EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
     if (points.empty()) return;
 
     paint.setPen(getBaseQColor());
@@ -708,10 +732,10 @@
     brushColour.setAlpha(80);
 
 //    SVDEBUG << "NoteLayer::paint: resolution is "
-//              << m_model->getResolution() << " frames" << endl;
+//              << model->getResolution() << " frames" << endl;
 
-    double min = m_model->getValueMinimum();
-    double max = m_model->getValueMaximum();
+    double min = model->getValueMinimum();
+    double max = model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     QPoint localPos;
@@ -739,8 +763,8 @@
         int w = v->getXForFrame(p.getFrame() + p.getDuration()) - x;
         int h = 3;
         
-        if (m_model->getValueQuantization() != 0.0) {
-            h = y - getYForValue(v, p.getValue() + m_model->getValueQuantization());
+        if (model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, p.getValue() + model->getValueQuantization());
             if (h < 3) h = 3;
         }
 
@@ -753,6 +777,11 @@
             paint.setPen(v->getForeground());
             paint.setBrush(v->getForeground());
 
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
             QString vlabel = QString("%1%2").arg(p.getValue()).arg(getScaleUnits());
             PaintAssistant::drawVisibleText(v, paint, 
                                x - paint.fontMetrics().width(vlabel) - 2,
@@ -761,7 +790,7 @@
                                vlabel, PaintAssistant::OutlinedText);
 
             QString hlabel = RealTime::frame2RealTime
-                (p.getFrame(), m_model->getSampleRate()).toText(true).c_str();
+                (p.getFrame(), model->getSampleRate()).toText(true).c_str();
             PaintAssistant::drawVisibleText(v, paint, 
                                x,
                                y - h/2 - paint.fontMetrics().descent() - 2,
@@ -777,7 +806,7 @@
 int
 NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
-    if (!m_model) {
+    if (m_model.isNone()) {
         return 0;
     }
 
@@ -795,7 +824,8 @@
 void
 NoteLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model || m_model->isEmpty()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || model->isEmpty()) return;
 
     QString unit;
     double min, max;
@@ -835,11 +865,12 @@
 {
 //    SVDEBUG << "NoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double value = getValueForY(v, e->y());
 
@@ -847,7 +878,7 @@
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -858,11 +889,12 @@
 {
 //    SVDEBUG << "NoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double newValue = getValueForY(v, e->y());
 
@@ -887,7 +919,8 @@
 NoteLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "NoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
     finish(m_editingCommand);
     m_editingCommand = nullptr;
     m_editing = false;
@@ -896,7 +929,8 @@
 void
 NoteLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
 
@@ -916,7 +950,8 @@
 void
 NoteLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     m_editing = false;
 
@@ -925,7 +960,7 @@
     if (p.getFrame() != m_editingPoint.getFrame() ||
         p.getValue() != m_editingPoint.getValue()) return;
 
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
 
     m_editingCommand->remove(m_editingPoint);
 
@@ -939,7 +974,8 @@
 {
 //    SVDEBUG << "NoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
     m_originalPoint = m_editingPoint;
@@ -962,7 +998,8 @@
 {
 //    SVDEBUG << "NoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     int xdist = e->x() - m_dragStartX;
     int ydist = e->y() - m_dragStartY;
@@ -971,13 +1008,13 @@
 
     sv_frame_t frame = v->getFrameForX(newx);
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double value = getValueForY(v, newy);
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model,
-                                                      tr("Drag Point"));
+        m_editingCommand = new ChangeEventsCommand
+            (m_model.untyped, tr("Drag Point"));
     }
 
     m_editingCommand->remove(m_editingPoint);
@@ -991,7 +1028,8 @@
 NoteLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "NoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
 
@@ -1018,7 +1056,8 @@
 bool
 NoteLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     Event note(0);
     if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
@@ -1026,7 +1065,7 @@
 //    Event note = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowDuration |
          ItemEditDialog::ShowValue |
@@ -1050,7 +1089,7 @@
             .withLabel(dialog->getText());
         
         ChangeEventsCommand *command = new ChangeEventsCommand
-            (m_model, tr("Edit Point"));
+            (m_model.untyped, tr("Edit Point"));
         command->remove(note);
         command->add(newNote);
         finish(command);
@@ -1066,13 +1105,14 @@
 void
 NoteLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1087,13 +1127,14 @@
 void
 NoteLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model || !s.getDuration()) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model || !s.getDuration()) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -1117,13 +1158,14 @@
 void
 NoteLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1135,10 +1177,11 @@
 void
 NoteLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -1149,7 +1192,8 @@
 NoteLayer::paste(LayerGeometryProvider *v, const Clipboard &from,
                  sv_frame_t /* frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<NoteModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -1173,7 +1217,7 @@
     }
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+        new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -1197,8 +1241,8 @@
         Event p = *i;
         Event newPoint = p;
         if (!p.hasValue()) {
-            newPoint = newPoint.withValue((m_model->getValueMinimum() +
-                                           m_model->getValueMaximum()) / 2);
+            newPoint = newPoint.withValue((model->getValueMinimum() +
+                                           model->getValueMaximum()) / 2);
         }
         if (!p.hasDuration()) {
             sv_frame_t nextFrame = frame;
@@ -1210,7 +1254,7 @@
                 nextFrame = j->getFrame();
             }
             if (nextFrame == frame) {
-                newPoint = newPoint.withDuration(m_model->getResolution());
+                newPoint = newPoint.withDuration(model->getResolution());
             } else {
                 newPoint = newPoint.withDuration(nextFrame - frame);
             }
@@ -1233,6 +1277,8 @@
 void
 NoteLayer::addNoteOff(sv_frame_t frame, int pitch)
 {
+    auto model = ModelById::getAs<NoteModel>(m_model);
+
     for (NoteSet::iterator i = m_pendingNoteOns.begin();
          i != m_pendingNoteOns.end(); ++i) {
 
@@ -1241,9 +1287,9 @@
         if (lrintf(p.getValue()) == pitch) {
             m_pendingNoteOns.erase(i);
             Event note = p.withDuration(frame - p.getFrame());
-            if (m_model) {
+            if (model) {
                 ChangeEventsCommand *c = new ChangeEventsCommand
-                    (m_model, tr("Record Note"));
+                    (m_model.untyped, tr("Record Note"));
                 c->add(note);
                 // execute and bundle:
                 CommandHistory::getInstance()->addCommand(c, true, true);
--- a/layer/NoteLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/NoteLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -68,8 +68,8 @@
     bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive) override;
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(NoteModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a NoteModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -95,7 +95,7 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const override;
@@ -146,7 +146,7 @@
 
     bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const;
 
-    NoteModel *m_model;
+    ModelId m_model;
     bool m_editing;
     int m_dragPointX;
     int m_dragPointY;
--- a/layer/PaintAssistant.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/PaintAssistant.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -114,6 +114,11 @@
         
         if (spaceForLabel) {
             
+            // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+            // replacement (horizontalAdvance) was only added in Qt 5.11
+            // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
             int tx = 3;
             if (paint.fontMetrics().width(text) < w - 10) {
                 tx = w - 10 - paint.fontMetrics().width(text);
--- a/layer/PianoScale.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/PianoScale.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -58,7 +58,7 @@
             QColor col = Qt::gray;
             if (i == 61) { // filling middle C
                 col = Qt::blue;
-                col = col.light(150);
+                col = col.lighter(150);
             }
             if (ppy - y > 2) {
                 paint.fillRect(x0 + 1,
@@ -129,7 +129,7 @@
             QColor col = Qt::gray;
             if (i == 61) { // filling middle C
                 col = Qt::blue;
-                col = col.light(150);
+                col = col.lighter(150);
             }
             if (x - ppx > 2) {
                 paint.fillRect((px + ppx) / 2 + 1,
--- a/layer/RegionLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/RegionLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -46,7 +46,6 @@
 
 RegionLayer::RegionLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_dragPointX(0),
     m_dragPointY(0),
@@ -63,26 +62,44 @@
     
 }
 
+int
+RegionLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-RegionLayer::setModel(RegionModel *model)
+RegionLayer::setModel(ModelId modelId)
 {
-    if (m_model == model) return;
-    m_model = model;
+    auto oldModel = ModelById::getAs<RegionModel>(m_model);
+    auto newModel = ModelById::getAs<RegionModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a RegionModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+    
+        connectSignals(m_model);
 
-    connect(m_model, SIGNAL(modelChanged()), this, SLOT(recalcSpacing()));
-    recalcSpacing();
+        connect(newModel.get(), SIGNAL(modelChanged()),
+                this, SLOT(recalcSpacing()));
+    
+        recalcSpacing();
 
-//    SVDEBUG << "RegionLayer::setModel(" << model << ")" << endl;
-
-    if (m_model && m_model->getRDFTypeURI().endsWith("Segment")) {
-        setPlotStyle(PlotSegmentation);
+        if (newModel->getRDFTypeURI().endsWith("Segment")) {
+            setPlotStyle(PlotSegmentation);
+        }
+        if (newModel->getRDFTypeURI().endsWith("Change")) {
+            setPlotStyle(PlotSegmentation);
+        }
     }
-    if (m_model && m_model->getRDFTypeURI().endsWith("Change")) {
-        setPlotStyle(PlotSegmentation);
-    }
-
+    
     emit modelReplaced();
 }
 
@@ -157,9 +174,10 @@
     } else if (name == "Scale Units") {
 
         if (deflt) *deflt = 0;
-        if (m_model) {
+        auto model = ModelById::getAs<RegionModel>(m_model);
+        if (model) {
             val = UnitDatabase::getInstance()->getUnitId
-                (getScaleUnits());
+                (model->getScaleUnits());
         }
 
     } else {
@@ -206,10 +224,11 @@
     } else if (name == "Vertical Scale") {
         setVerticalScale(VerticalScale(value));
     } else if (name == "Scale Units") {
-        if (m_model) {
-            m_model->setScaleUnits
+        auto model = ModelById::getAs<RegionModel>(m_model);
+        if (model) {
+            model->setScaleUnits
                 (UnitDatabase::getInstance()->getUnitById(value));
-            emit modelChanged();
+            emit modelChanged(m_model);
         }
     } else {
         return SingleColourLayer::setProperty(name, value);
@@ -257,11 +276,13 @@
 {
     m_spacingMap.clear();
     m_distributionMap.clear();
-    if (!m_model) return;
+
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
 //    SVDEBUG << "RegionLayer::recalcSpacing" << endl;
 
-    EventVector allEvents = m_model->getAllEvents();
+    EventVector allEvents = model->getAllEvents();
     for (const Event &e: allEvents) {
         m_distributionMap[e.getValue()]++;
 //        SVDEBUG << "RegionLayer::recalcSpacing: value found: " << e.getValue() << " (now have " << m_distributionMap[e.getValue()] << " of this value)" <<  endl;
@@ -280,9 +301,10 @@
 RegionLayer::getValueExtents(double &min, double &max,
                            bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
-    min = m_model->getValueMinimum();
-    max = m_model->getValueMaximum();
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return false;
+    min = model->getValueMinimum();
+    max = model->getValueMaximum();
     unit = getScaleUnits();
 
     if (m_verticalScale == LogScale) logarithmic = true;
@@ -293,12 +315,13 @@
 bool
 RegionLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model ||
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model ||
         m_verticalScale == AutoAlignScale ||
         m_verticalScale == EqualSpaced) return false;
 
-    min = m_model->getValueMinimum();
-    max = m_model->getValueMaximum();
+    min = model->getValueMinimum();
+    max = model->getValueMaximum();
 
     return true;
 }
@@ -306,21 +329,22 @@
 EventVector
 RegionLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return EventVector();
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return EventVector();
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector local = m_model->getEventsCovering(frame);
+    EventVector local = model->getEventsCovering(frame);
     if (!local.empty()) return local;
 
     int fuzz = ViewManager::scalePixelSize(2);
     sv_frame_t start = v->getFrameForX(x - fuzz);
     sv_frame_t end = v->getFrameForX(x + fuzz);
 
-    local = m_model->getEventsStartingWithin(frame, end - frame);
+    local = model->getEventsStartingWithin(frame, end - frame);
     if (!local.empty()) return local;
 
-    local = m_model->getEventsSpanning(start, frame - start);
+    local = model->getEventsSpanning(start, frame - start);
     if (!local.empty()) return local;
 
     return {};
@@ -329,11 +353,12 @@
 bool
 RegionLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &point) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector onPoints = m_model->getEventsCovering(frame);
+    EventVector onPoints = model->getEventsCovering(frame);
     if (onPoints.empty()) return false;
 
     int nearestDistance = -1;
@@ -352,9 +377,10 @@
 QString
 RegionLayer::getLabelPreceding(sv_frame_t frame) const
 {
-    if (!m_model) return "";
-    EventVector points = m_model->getEventsStartingWithin
-        (m_model->getStartFrame(), frame - m_model->getStartFrame());
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return "";
+    EventVector points = model->getEventsStartingWithin
+        (model->getStartFrame(), frame - model->getStartFrame());
     if (!points.empty()) {
         for (auto i = points.rbegin(); i != points.rend(); ++i) {
             if (i->getLabel() != QString()) {
@@ -370,12 +396,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return tr("No local points");
@@ -393,9 +420,9 @@
         int y = getYForValue(v, i->getValue());
         int h = 3;
 
-        if (m_model->getValueQuantization() != 0.0) {
+        if (model->getValueQuantization() != 0.0) {
             h = y - getYForValue
-                (v, i->getValue() + m_model->getValueQuantization());
+                (v, i->getValue() + model->getValueQuantization());
             if (h < 3) h = 3;
         }
 
@@ -408,9 +435,9 @@
     if (i == points.end()) return tr("No local points");
 
     RealTime rt = RealTime::frame2RealTime(region.getFrame(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     RealTime rd = RealTime::frame2RealTime(region.getDuration(),
-                                           m_model->getSampleRate());
+                                           model->getSampleRate());
     
     QString valueText;
 
@@ -441,7 +468,8 @@
                                 int &resolution,
                                 SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
@@ -452,7 +480,7 @@
     // an editing operation, i.e. closest feature in either direction
     // but only if it is "close enough"
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame));
@@ -469,7 +497,7 @@
     
     Event left;
     bool haveLeft = false;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame, [](Event) { return true; }, EventSeries::Backward, left)) {
         haveLeft = true;
     }
@@ -481,7 +509,7 @@
 
     Event right;
     bool haveRight = false;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame, [](Event) { return true; }, EventSeries::Forward, right)) {
         haveRight = true;
     }
@@ -515,7 +543,8 @@
                                   int &resolution,
                                   SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) {
         return Layer::snapToSimilarFeature(v, frame, resolution, snap);
     }
 
@@ -523,14 +552,14 @@
     // don't do the same trick as in snapToFeatureFrame, of snapping
     // to the end of a feature sometimes.
     
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     Event ref;
     Event e;
     float matchvalue;
     bool found;
 
-    found = m_model->getNearestEventMatching
+    found = model->getNearestEventMatching
         (frame, [](Event) { return true; }, EventSeries::Backward, ref);
 
     if (!found) {
@@ -539,7 +568,7 @@
 
     matchvalue = ref.getValue();
     
-    found = m_model->getNearestEventMatching
+    found = model->getNearestEventMatching
         (frame,
          [matchvalue](Event e) {
              double epsilon = 0.0001;
@@ -559,7 +588,8 @@
 QString
 RegionLayer::getScaleUnits() const
 {
-    if (m_model) return m_model->getScaleUnits();
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (model) return model->getScaleUnits();
     else return "";
 }
 
@@ -570,6 +600,9 @@
     max = 0.0;
     log = false;
 
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
+
     QString queryUnits;
     queryUnits = getScaleUnits();
 
@@ -577,8 +610,8 @@
 
         if (!v->getValueExtents(queryUnits, min, max, log)) {
 
-            min = m_model->getValueMinimum();
-            max = m_model->getValueMaximum();
+            min = model->getValueMinimum();
+            max = model->getValueMaximum();
 
 //            cerr << "RegionLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
 
@@ -603,8 +636,8 @@
 
     } else {
 
-        min = m_model->getValueMinimum();
-        max = m_model->getValueMaximum();
+        min = model->getValueMinimum();
+        max = model->getValueMaximum();
 
         if (m_verticalScale == LogScale) {
             LogRange::mapRange(min, max);
@@ -808,9 +841,10 @@
 void
 RegionLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("RegionLayer::paint", true);
@@ -820,7 +854,7 @@
     sv_frame_t wholeFrame0 = v->getFrameForX(0);
     sv_frame_t wholeFrame1 = v->getFrameForX(v->getPaintWidth());
 
-    EventVector points(m_model->getEventsSpanning(wholeFrame0,
+    EventVector points(model->getEventsSpanning(wholeFrame0,
                                                   wholeFrame1 - wholeFrame0));
     if (points.empty()) return;
 
@@ -830,10 +864,10 @@
     brushColour.setAlpha(80);
 
 //    SVDEBUG << "RegionLayer::paint: resolution is "
-//              << m_model->getResolution() << " frames" << endl;
+//              << model->getResolution() << " frames" << endl;
 
-    double min = m_model->getValueMinimum();
-    double max = m_model->getValueMaximum();
+    double min = model->getValueMinimum();
+    double max = model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     QPoint localPos;
@@ -878,9 +912,9 @@
             if (nx < ex) ex = nx;
         }
 
-        if (m_model->getValueQuantization() != 0.0) {
+        if (model->getValueQuantization() != 0.0) {
             h = y - getYForValue
-                (v, p.getValue() + m_model->getValueQuantization());
+                (v, p.getValue() + model->getValueQuantization());
             if (h < 3) h = 3;
         }
 
@@ -917,6 +951,11 @@
                 paint.setPen(v->getForeground());
                 paint.setBrush(v->getForeground());
 
+                // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+                // replacement (horizontalAdvance) was only added in Qt 5.11
+                // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
                 QString vlabel =
                     QString("%1%2").arg(p.getValue()).arg(getScaleUnits());
                 PaintAssistant::drawVisibleText(v, paint, 
@@ -926,7 +965,7 @@
                                    vlabel, PaintAssistant::OutlinedText);
                 
                 QString hlabel = RealTime::frame2RealTime
-                    (p.getFrame(), m_model->getSampleRate()).toText(true).c_str();
+                    (p.getFrame(), model->getSampleRate()).toText(true).c_str();
                 PaintAssistant::drawVisibleText(v, paint, 
                                    x,
                                    y - h/2 - paint.fontMetrics().descent() - gap,
@@ -1009,7 +1048,8 @@
 int
 RegionLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
-    if (!m_model || 
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || 
         m_verticalScale == AutoAlignScale || 
         m_verticalScale == EqualSpaced) {
         return 0;
@@ -1031,7 +1071,8 @@
 void
 RegionLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model || m_model->isEmpty()) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || model->isEmpty()) return;
 
     QString unit;
     double min, max;
@@ -1074,11 +1115,12 @@
 void
 RegionLayer::drawStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double value = getValueForY(v, e->y());
 
@@ -1086,8 +1128,7 @@
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model,
-                                                    tr("Draw Region"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Region"));
     m_editingCommand->add(m_editingPoint);
 
     recalcSpacing();
@@ -1098,11 +1139,12 @@
 void
 RegionLayer::drawDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double newValue = m_editingPoint.getValue();
     if (m_verticalScale != EqualSpaced) newValue = getValueForY(v, e->y());
@@ -1129,7 +1171,8 @@
 void
 RegionLayer::drawEnd(LayerGeometryProvider *, QMouseEvent *)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !m_editing) return;
     finish(m_editingCommand);
     m_editingCommand = nullptr;
     m_editing = false;
@@ -1140,7 +1183,8 @@
 void
 RegionLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
 
@@ -1161,7 +1205,8 @@
 void
 RegionLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !m_editing) return;
 
     m_editing = false;
 
@@ -1171,7 +1216,7 @@
         p.getValue() != m_editingPoint.getValue()) return;
 
     m_editingCommand = new ChangeEventsCommand
-        (m_model, tr("Erase Region"));
+        (m_model.untyped, tr("Erase Region"));
 
     m_editingCommand->remove(m_editingPoint);
 
@@ -1184,7 +1229,8 @@
 void
 RegionLayer::editStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
         return;
@@ -1209,7 +1255,8 @@
 void
 RegionLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !m_editing) return;
 
     int xdist = e->x() - m_dragStartX;
     int ydist = e->y() - m_dragStartY;
@@ -1218,7 +1265,7 @@
 
     sv_frame_t frame = v->getFrameForX(newx);
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     // Do not bisect between two values, if one of those values is
     // that of the point we're actually moving ...
@@ -1230,7 +1277,7 @@
     double value = getValueForY(v, newy, avoid);
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model,
+        m_editingCommand = new ChangeEventsCommand(m_model.untyped,
                                                       tr("Drag Region"));
     }
 
@@ -1245,7 +1292,8 @@
 void
 RegionLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
 
@@ -1273,13 +1321,14 @@
 bool
 RegionLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return false;
 
     Event region(0);
     if (!getPointToDrag(v, e->x(), e->y(), region)) return false;
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowDuration |
          ItemEditDialog::ShowValue |
@@ -1300,7 +1349,7 @@
             .withLabel(dialog->getText());
         
         ChangeEventsCommand *command = new ChangeEventsCommand
-            (m_model, tr("Edit Region"));
+            (m_model.untyped, tr("Edit Region"));
         command->remove(region);
         command->add(newRegion);
         finish(command);
@@ -1314,13 +1363,14 @@
 void
 RegionLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (EventVector::iterator i = points.begin();
          i != points.end(); ++i) {
@@ -1338,13 +1388,14 @@
 void
 RegionLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model || !s.getDuration()) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model || !s.getDuration()) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -1369,13 +1420,14 @@
 void
 RegionLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (EventVector::iterator i = points.begin();
          i != points.end(); ++i) {
@@ -1392,10 +1444,11 @@
 void
 RegionLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -1405,7 +1458,8 @@
 bool
 RegionLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -1429,7 +1483,7 @@
     }
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+        new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
@@ -1453,8 +1507,8 @@
         Event p = *i;
         Event newPoint = p;
         if (!p.hasValue()) {
-            newPoint = newPoint.withValue((m_model->getValueMinimum() +
-                                           m_model->getValueMaximum()) / 2);
+            newPoint = newPoint.withValue((model->getValueMinimum() +
+                                           model->getValueMaximum()) / 2);
         }
         if (!p.hasDuration()) {
             sv_frame_t nextFrame = frame;
@@ -1466,7 +1520,7 @@
                 nextFrame = j->getFrame();
             }
             if (nextFrame == frame) {
-                newPoint = newPoint.withDuration(m_model->getResolution());
+                newPoint = newPoint.withDuration(model->getResolution());
             } else {
                 newPoint = newPoint.withDuration(nextFrame - frame);
             }
--- a/layer/RegionLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/RegionLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -76,8 +76,8 @@
     bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive) override;
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(RegionModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a RegionModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -114,7 +114,7 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
                                  bool &log, QString &unit) const override;
@@ -145,7 +145,7 @@
 
     bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const;
 
-    RegionModel *m_model;
+    ModelId m_model;
     bool m_editing;
     int m_dragPointX;
     int m_dragPointY;
--- a/layer/SliceLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SliceLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -32,7 +32,6 @@
 
 
 SliceLayer::SliceLayer() :
-    m_sliceableModel(nullptr),
     m_binAlignment(BinsSpanScalePoints),
     m_colourMap(int(ColourMapper::Ice)),
     m_colourInverted(false),
@@ -57,27 +56,24 @@
 }
 
 void
-SliceLayer::setSliceableModel(const Model *model)
+SliceLayer::setSliceableModel(ModelId modelId)
 {
-    const DenseThreeDimensionalModel *sliceable =
-        dynamic_cast<const DenseThreeDimensionalModel *>(model);
-
-    if (model && !sliceable) {
-        cerr << "WARNING: SliceLayer::setSliceableModel(" << model
-                  << "): model is not a DenseThreeDimensionalModel" << endl;
+    auto newModel = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a DenseThreeDimensionalModel");
     }
 
-    if (m_sliceableModel == sliceable) return;
+    if (m_sliceableModel == modelId) return;
+    m_sliceableModel = modelId;
 
-    m_sliceableModel = sliceable;
+    if (newModel) {
+        connectSignals(m_sliceableModel);
 
-    if (!m_sliceableModel) return;
-
-    connectSignals(m_sliceableModel);
-
-    if (m_minbin == 0 && m_maxbin == 0) {
-        m_minbin = 0;
-        m_maxbin = m_sliceableModel->getHeight();
+        if (m_minbin == 0 && m_maxbin == 0) {
+            m_minbin = 0;
+            m_maxbin = newModel->getHeight();
+        }
     }
     
     emit modelReplaced();
@@ -85,23 +81,12 @@
 }
 
 void
-SliceLayer::sliceableModelReplaced(const Model *orig, const Model *replacement)
+SliceLayer::sliceableModelReplaced(ModelId orig, ModelId replacement)
 {
     SVDEBUG << "SliceLayer::sliceableModelReplaced(" << orig << ", " << replacement << ")" << endl;
 
     if (orig == m_sliceableModel) {
-        setSliceableModel
-            (dynamic_cast<const DenseThreeDimensionalModel *>(replacement));
-    }
-}
-
-void
-SliceLayer::modelAboutToBeDeleted(Model *m)
-{
-    SVDEBUG << "SliceLayer::modelAboutToBeDeleted(" << m << ")" << endl;
-
-    if (m == m_sliceableModel) {
-        setSliceableModel(nullptr);
+        setSliceableModel(replacement);
     }
 }
 
@@ -119,7 +104,10 @@
 {
     minbin = 0;
     maxbin = 0;
-    if (!m_sliceableModel) return "";
+    
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return "";
 
     if (m_binAlignment == BinsSpanScalePoints) {
         minbin = int(getBinForX(v, p.x()));
@@ -129,13 +117,13 @@
         maxbin = int(getBinForX(v, p.x() + 1) + 0.5);
     }        
 
-    int mh = m_sliceableModel->getHeight();
+    int mh = sliceableModel->getHeight();
     if (minbin >= mh) minbin = mh - 1;
     if (maxbin >= mh) maxbin = mh - 1;
     if (minbin < 0) minbin = 0;
     if (maxbin < 0) maxbin = 0;
     
-    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
+    sv_samplerate_t sampleRate = sliceableModel->getSampleRate();
 
     sv_frame_t f0 = m_currentf0;
     sv_frame_t f1 = m_currentf1;
@@ -420,9 +408,11 @@
 void
 SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_sliceableModel ||
-        !m_sliceableModel->isOK() ||
-        !m_sliceableModel->isReady()) return;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel ||
+        !sliceableModel->isOK() ||
+        !sliceableModel->isReady()) return;
 
     Profiler profiler("SliceLayer::paint()");
 
@@ -442,7 +432,7 @@
         }
     }
 
-    int mh = m_sliceableModel->getHeight();
+    int mh = sliceableModel->getHeight();
     int bin0 = 0;
     if (m_maxbin > m_minbin) {
         mh = m_maxbin - m_minbin;
@@ -493,7 +483,7 @@
 
 //    cerr << "centre frame " << v->getCentreFrame() << ", x " << f0x << ", f0 " << f0 << ", f1 " << f1 << endl;
 
-    int res = m_sliceableModel->getResolution();
+    int res = sliceableModel->getResolution();
     int col0 = int(f0 / res);
     int col1 = col0;
     if (m_samplingMode != NearestSample) col1 = int(f1 / res);
@@ -512,7 +502,7 @@
 
     for (int col = col0; col <= col1; ++col) {
         DenseThreeDimensionalModel::Column column =
-            m_sliceableModel->getColumn(col);
+            sliceableModel->getColumn(col);
         for (int bin = 0; bin < mh; ++bin) {
             float value = column[bin0 + bin];
             if (bin < cs) value *= curve[bin];
@@ -674,6 +664,11 @@
 int
 SliceLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     int width;
     if (m_energyScale == LinearScale || m_energyScale == AbsoluteScale) {
         width = std::max(paint.fontMetrics().width("0.0") + 13,
@@ -1166,10 +1161,12 @@
 SliceLayer::getValueExtents(double &min, double &max, bool &logarithmic,
                             QString &unit) const
 {
-    if (!m_sliceableModel) return false;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return false;
     
     min = 0;
-    max = double(m_sliceableModel->getHeight());
+    max = double(sliceableModel->getHeight());
 
     logarithmic = (m_binScale == BinScale::LogBins);
     unit = "";
@@ -1180,9 +1177,11 @@
 bool
 SliceLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_sliceableModel) return false;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return false;
 
-    double hmax = double(m_sliceableModel->getHeight());
+    double hmax = double(sliceableModel->getHeight());
     
     min = m_minbin;
     max = m_maxbin;
@@ -1199,7 +1198,9 @@
 bool
 SliceLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_sliceableModel) return false;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return false;
 
     m_minbin = int(lrint(min));
     m_maxbin = int(lrint(max));
@@ -1210,11 +1211,11 @@
     if (m_maxbin < 0) {
         m_maxbin = 0;
     }
-    if (m_minbin > m_sliceableModel->getHeight()) {
-        m_minbin = m_sliceableModel->getHeight();
+    if (m_minbin > sliceableModel->getHeight()) {
+        m_minbin = sliceableModel->getHeight();
     }
-    if (m_maxbin > m_sliceableModel->getHeight()) {
-        m_maxbin = m_sliceableModel->getHeight();
+    if (m_maxbin > sliceableModel->getHeight()) {
+        m_maxbin = sliceableModel->getHeight();
     }
     if (m_maxbin < m_minbin) {
         m_maxbin = m_minbin;
@@ -1227,31 +1228,37 @@
 int
 SliceLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return 0;
 
     defaultStep = 0;
-    int h = m_sliceableModel->getHeight();
+    int h = sliceableModel->getHeight();
     return h;
 }
 
 int
 SliceLayer::getCurrentVerticalZoomStep() const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return 0;
 
     double min, max;
     getDisplayExtents(min, max);
-    return m_sliceableModel->getHeight() - int(lrint(max - min));
+    return sliceableModel->getHeight() - int(lrint(max - min));
 }
 
 void
 SliceLayer::setVerticalZoomStep(int step)
 {
-    if (!m_sliceableModel) return;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return;
 
 //    SVDEBUG << "SliceLayer::setVerticalZoomStep(" <<step <<"): before: minbin = " << m_minbin << ", maxbin = " << m_maxbin << endl;
 
-    int dist = m_sliceableModel->getHeight() - step;
+    int dist = sliceableModel->getHeight() - step;
     if (dist < 1) dist = 1;
     double centre = m_minbin + (m_maxbin - m_minbin) / 2.0;
     int minbin = int(lrint(centre - dist/2.0));
@@ -1262,10 +1269,12 @@
 RangeMapper *
 SliceLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_sliceableModel) return nullptr;
+    auto sliceableModel =
+        ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
+    if (!sliceableModel) return nullptr;
 
-    return new LinearRangeMapper(0, m_sliceableModel->getHeight(),
-                                 0, m_sliceableModel->getHeight(), "");
+    return new LinearRangeMapper(0, sliceableModel->getHeight(),
+                                 0, sliceableModel->getHeight(), "");
 }
 
 void
--- a/layer/SliceLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SliceLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -32,9 +32,9 @@
     SliceLayer();
     ~SliceLayer();
     
-    const Model *getModel() const override { return 0; }
+    ModelId getModel() const override { return {}; }
 
-    void setSliceableModel(const Model *model);    
+    void setSliceableModel(ModelId model);
 
     void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override;
 
@@ -117,8 +117,7 @@
                        QString extraAttributes = "") const override;
 
 public slots:
-    void sliceableModelReplaced(const Model *, const Model *);
-    void modelAboutToBeDeleted(Model *);
+    void sliceableModelReplaced(ModelId, ModelId);
 
 protected:
     /// Convert a (possibly non-integral) bin into x-coord. May be overridden
@@ -171,28 +170,28 @@
         BinsSpanScalePoints
     };
 
-    const DenseThreeDimensionalModel *m_sliceableModel;
-    BinAlignment                      m_binAlignment;
-    int                               m_colourMap;
-    bool                              m_colourInverted;
-    EnergyScale                       m_energyScale;
-    SamplingMode                      m_samplingMode;
-    PlotStyle                         m_plotStyle;
-    BinScale                          m_binScale;
-    bool                              m_normalize;
-    float                             m_threshold;
-    float                             m_initialThreshold;
-    float                             m_gain;
-    int                               m_minbin;
-    int                               m_maxbin;
-    mutable std::vector<int>          m_scalePoints;
-    mutable int                       m_scalePaintHeight;
-    mutable std::map<int, int>        m_xorigins; // LayerGeometryProvider id -> x
-    mutable std::map<int, int>        m_yorigins; // LayerGeometryProvider id -> y
-    mutable std::map<int, int>        m_heights;  // LayerGeometryProvider id -> h
-    mutable sv_frame_t                m_currentf0;
-    mutable sv_frame_t                m_currentf1;
-    mutable std::vector<float>        m_values;
+    ModelId                     m_sliceableModel; // a DenseThreeDimensionalModel
+    BinAlignment                m_binAlignment;
+    int                         m_colourMap;
+    bool                        m_colourInverted;
+    EnergyScale                 m_energyScale;
+    SamplingMode                m_samplingMode;
+    PlotStyle                   m_plotStyle;
+    BinScale                    m_binScale;
+    bool                        m_normalize;
+    float                       m_threshold;
+    float                       m_initialThreshold;
+    float                       m_gain;
+    int                         m_minbin;
+    int                         m_maxbin;
+    mutable std::vector<int>    m_scalePoints;
+    mutable int                 m_scalePaintHeight;
+    mutable std::map<int, int>  m_xorigins; // LayerGeometryProvider id -> x
+    mutable std::map<int, int>  m_yorigins; // LayerGeometryProvider id -> y
+    mutable std::map<int, int>  m_heights;  // LayerGeometryProvider id -> h
+    mutable sv_frame_t          m_currentf0;
+    mutable sv_frame_t          m_currentf1;
+    mutable std::vector<float>  m_values;
 };
 
 #endif
--- a/layer/SliceableLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SliceableLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -34,14 +34,15 @@
     // spectrogram that was constructed from a DenseTimeValueModel).
     // The SliceableLayer retains ownership of the model, and will
     // emit sliceableModelReplaced if it is about to become invalid.
-    virtual const Model *getSliceableModel() const = 0;
-
+    virtual ModelId getSliceableModel() const = 0;
+/*!!!
 signals:
     // Emitted when a model that was obtained through
     // getSliceableModel is about to be deleted.  If replacement is
     // non-NULL, it may be used instead.
     void sliceableModelReplaced(const Model *modelToBeReplaced,
                                 const Model *replacement);
+*/
 };
 
 #endif
--- a/layer/SpectrogramLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SpectrogramLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -56,7 +56,6 @@
 using namespace std;
 
 SpectrogramLayer::SpectrogramLayer(Configuration config) :
-    m_model(nullptr),
     m_channel(0),
     m_windowSize(1024),
     m_windowType(HanningWindow),
@@ -83,9 +82,6 @@
     m_synchronous(false),
     m_haveDetailedScale(false),
     m_exiting(false),
-    m_fftModel(nullptr),
-    m_wholeCache(nullptr),
-    m_peakCache(nullptr),
     m_peakCacheDivisor(8)
 {
     QString colourConfigName = "spectrogram-colour";
@@ -140,17 +136,13 @@
 void
 SpectrogramLayer::deleteDerivedModels()
 {
-    if (m_fftModel) m_fftModel->aboutToDelete();
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    if (m_wholeCache) m_wholeCache->aboutToDelete();
-
-    delete m_fftModel;
-    delete m_peakCache;
-    delete m_wholeCache;
-
-    m_fftModel = nullptr;
-    m_peakCache = nullptr;
-    m_wholeCache = nullptr;
+    ModelById::release(m_fftModel);
+    ModelById::release(m_peakCache);
+    ModelById::release(m_wholeCache);
+
+    m_fftModel = {};
+    m_peakCache = {};
+    m_wholeCache = {};
 }
 
 pair<ColourScaleType, double>
@@ -208,24 +200,29 @@
 }
 
 void
-SpectrogramLayer::setModel(const DenseTimeValueModel *model)
+SpectrogramLayer::setModel(ModelId modelId)
 {
-//    cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << endl;
-
-    if (model == m_model) return;
-
-    m_model = model;
-
-    recreateFFTModel();
-
-    if (!m_model || !m_model->isOK()) return;
-
-    connectSignals(m_model);
-
-    connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
-    connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-            this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t)));
-
+    auto newModel = ModelById::getAs<DenseTimeValueModel>(modelId);
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a DenseTimeValueModel");
+    }
+    
+    if (modelId == m_model) return;
+    m_model = modelId;
+
+    if (newModel) {
+        recreateFFTModel();
+
+        connectSignals(m_model);
+
+        connect(newModel.get(),
+                SIGNAL(modelChanged(ModelId)),
+                this, SLOT(cacheInvalid(ModelId)));
+        connect(newModel.get(),
+                SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+                this, SLOT(cacheInvalid(ModelId, sv_frame_t, sv_frame_t)));
+    }
+    
     emit modelReplaced();
 }
 
@@ -1063,7 +1060,7 @@
 }
 
 void
-SpectrogramLayer::cacheInvalid()
+SpectrogramLayer::cacheInvalid(ModelId)
 {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     cerr << "SpectrogramLayer::cacheInvalid()" << endl;
@@ -1075,6 +1072,7 @@
 
 void
 SpectrogramLayer::cacheInvalid(
+    ModelId,
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     sv_frame_t from, sv_frame_t to
 #else 
@@ -1106,7 +1104,10 @@
 double
 SpectrogramLayer::getEffectiveMinFrequency() const
 {
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+    
+    sv_samplerate_t sr = model->getSampleRate();
     double minf = double(sr) / getFFTSize();
 
     if (m_minFrequency > 0.0) {
@@ -1121,7 +1122,10 @@
 double
 SpectrogramLayer::getEffectiveMaxFrequency() const
 {
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+    
+    sv_samplerate_t sr = model->getSampleRate();
     double maxf = double(sr) / 2;
 
     if (m_maxFrequency > 0.0) {
@@ -1147,10 +1151,13 @@
 double
 SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+    
     double minf = getEffectiveMinFrequency();
     double maxf = getEffectiveMaxFrequency();
     bool logarithmic = (m_binScale == BinScale::Log);
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     double freq = (bin * sr) / getFFTSize();
     
@@ -1162,7 +1169,10 @@
 double
 SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const
 {
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0.0;
+
+    sv_samplerate_t sr = model->getSampleRate();
     double minf = getEffectiveMinFrequency();
     double maxf = getEffectiveMaxFrequency();
 
@@ -1179,8 +1189,11 @@
 bool
 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();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
+    sv_frame_t modelStart = model->getStartFrame();
+    sv_frame_t modelEnd = model->getEndFrame();
 
     // Each pixel column covers an exact range of sample frames:
     sv_frame_t f0 = v->getFrameForX(x) - modelStart;
@@ -1203,6 +1216,9 @@
 bool
 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
     double s0 = 0, s1 = 0;
     if (!getXBinRange(v, x, s0, s1)) return false;
     
@@ -1214,8 +1230,8 @@
     int w1 = s1i * windowIncrement + windowIncrement +
         (m_windowSize - windowIncrement)/2 - 1;
     
-    min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
-    max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
+    min = RealTime::frame2RealTime(w0, model->getSampleRate());
+    max = RealTime::frame2RealTime(w1, model->getSampleRate());
     return true;
 }
 
@@ -1223,13 +1239,16 @@
 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
 const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
     double q0 = 0, q1 = 0;
     if (!getYBinRange(v, y, q0, q1)) return false;
 
     int q0i = int(q0 + 0.001);
     int q1i = int(q1);
 
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     for (int q = q0i; q <= q1i; ++q) {
         if (q == q0i) freqMin = (sr * q) / getFFTSize();
@@ -1244,11 +1263,12 @@
                                              double &adjFreqMin, double &adjFreqMax)
 const
 {
-    if (!m_model || !m_model->isOK() || !m_model->isReady()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK() || !model->isReady()) {
         return false;
     }
 
-    FFTModel *fft = getFFTModel();
+    auto fft = ModelById::getAs<FFTModel>(m_fftModel);
     if (!fft) return false;
 
     double s0 = 0, s1 = 0;
@@ -1263,7 +1283,7 @@
     int q0i = int(q0 + 0.001);
     int q1i = int(q1);
 
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     bool haveAdj = false;
 
@@ -1311,7 +1331,8 @@
                                       double &min, double &max,
                                       double &phaseMin, double &phaseMax) const
 {
-    if (!m_model || !m_model->isOK() || !m_model->isReady()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK() || !model->isReady()) {
         return false;
     }
 
@@ -1329,7 +1350,7 @@
 
     bool rv = false;
 
-    FFTModel *fft = getFFTModel();
+    auto fft = ModelById::getAs<FFTModel>(m_fftModel);
 
     if (fft) {
 
@@ -1374,55 +1395,51 @@
 {
     SVDEBUG << "SpectrogramLayer::recreateFFTModel called" << endl;
 
-    if (!m_model || !m_model->isOK()) {
-        emit sliceableModelReplaced(m_fftModel, nullptr);
-        deleteDerivedModels();
-        return;
+    { // scope, avoid hanging on to this pointer
+        auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+        if (!model || !model->isOK()) {
+            deleteDerivedModels();
+            return;
+        }
     }
-
-    if (m_fftModel) m_fftModel->aboutToDelete();
     
-    if (m_peakCache) m_peakCache->aboutToDelete();
-    delete m_peakCache;
-    m_peakCache = nullptr;
-
-    if (m_wholeCache) m_wholeCache->aboutToDelete();
-    delete m_wholeCache;
-    m_wholeCache = nullptr;
-    
-    FFTModel *newModel = new FFTModel(m_model,
-                                      m_channel,
-                                      m_windowType,
-                                      m_windowSize,
-                                      getWindowIncrement(),
-                                      getFFTSize());
-
-    if (!newModel->isOK()) {
+    deleteDerivedModels();
+
+    auto newFFTModel = std::make_shared<FFTModel>(m_model,
+                                                  m_channel,
+                                                  m_windowType,
+                                                  m_windowSize,
+                                                  getWindowIncrement(),
+                                                  getFFTSize());
+
+    if (!newFFTModel->isOK()) {
         QMessageBox::critical
             (nullptr, tr("FFT cache failed"),
              tr("Failed to create the FFT model for this spectrogram.\n"
                 "There may be insufficient memory or disc space to continue."));
-        delete newModel;
-        delete m_fftModel;
-        m_fftModel = nullptr;
         return;
     }
 
-    FFTModel *oldModel = m_fftModel;
-    m_fftModel = newModel;
+    m_fftModel = ModelById::add(newFFTModel);
 
     bool createWholeCache = false;
     checkCacheSpace(&m_peakCacheDivisor, &createWholeCache);
     
     if (createWholeCache) {
-        m_wholeCache = new Dense3DModelPeakCache(m_fftModel, 1);
-        m_peakCache = new Dense3DModelPeakCache(m_wholeCache, m_peakCacheDivisor);
+
+        auto whole = std::make_shared<Dense3DModelPeakCache>(m_fftModel, 1);
+        m_wholeCache = ModelById::add(whole);
+
+        auto peaks = std::make_shared<Dense3DModelPeakCache>(m_wholeCache,
+                                                             m_peakCacheDivisor);
+        m_peakCache = ModelById::add(peaks);
+
     } else {
-        m_peakCache = new Dense3DModelPeakCache(m_fftModel, m_peakCacheDivisor);
+
+        auto peaks = std::make_shared<Dense3DModelPeakCache>(m_fftModel,
+                                                             m_peakCacheDivisor);
+        m_peakCache = ModelById::add(peaks);
     }
-
-    emit sliceableModelReplaced(oldModel, m_fftModel);
-    delete oldModel;
 }
 
 void
@@ -1431,12 +1448,13 @@
 {
     *suggestedPeakDivisor = 8;
     *createWholeCache = false;
-    
-    if (!m_fftModel) return;
+
+    auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
+    if (!fftModel) return;
 
     size_t sz =
-        size_t(m_fftModel->getWidth()) *
-        size_t(m_fftModel->getHeight()) *
+        size_t(fftModel->getWidth()) *
+        size_t(fftModel->getHeight()) *
         sizeof(float);
 
     try {
@@ -1466,7 +1484,7 @@
     }
 }
 
-const Model *
+ModelId
 SpectrogramLayer::getSliceableModel() const
 {
     return m_fftModel;
@@ -1496,10 +1514,10 @@
 
         Colour3DPlotRenderer::Sources sources;
         sources.verticalBinLayer = this;
-        sources.fft = getFFTModel();
+        sources.fft = m_fftModel;
         sources.source = sources.fft;
-        if (m_peakCache) sources.peakCaches.push_back(m_peakCache);
-        if (m_wholeCache) sources.peakCaches.push_back(m_wholeCache);
+        if (!m_peakCache.isNone()) sources.peakCaches.push_back(m_peakCache);
+        if (!m_wholeCache.isNone()) sources.peakCaches.push_back(m_wholeCache);
 
         ColourScale::Parameters cparams;
         cparams.colourMap = m_colourMap;
@@ -1635,7 +1653,8 @@
     cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
 #endif
 
-    if (!m_model || !m_model->isOK() || !m_model->isReady()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK() || !model->isReady()) {
         return;
     }
 
@@ -1649,8 +1668,10 @@
 {
     Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
 
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    
     QPoint localPos;
-    if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) {
+    if (!v->shouldIlluminateLocalFeatures(this, localPos) || !model) {
         return;
     }
 
@@ -1708,8 +1729,9 @@
 int
 SpectrogramLayer::getCompletion(LayerGeometryProvider *) const
 {
-    if (!m_fftModel) return 100;
-    int completion = m_fftModel->getCompletion();
+    auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
+    if (!fftModel) return 100;
+    int completion = fftModel->getCompletion();
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
 #endif
@@ -1719,17 +1741,19 @@
 QString
 SpectrogramLayer::getError(LayerGeometryProvider *) const
 {
-    if (!m_fftModel) return "";
-    return m_fftModel->getError();
+    auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
+    if (!fftModel) return "";
+    return fftModel->getError();
 }
 
 bool
 SpectrogramLayer::getValueExtents(double &min, double &max,
                                   bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
-
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
+
+    sv_samplerate_t sr = model->getSampleRate();
     min = double(sr) / getFFTSize();
     max = double(sr) / 2;
     
@@ -1751,12 +1775,13 @@
 bool
 SpectrogramLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return false;
 
 //    SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
 
     if (min < 0) min = 0;
-    if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0;
+    if (max > model->getSampleRate()/2.0) max = model->getSampleRate()/2.0;
     
     int minf = int(lrint(min));
     int maxf = int(lrint(max));
@@ -1831,6 +1856,11 @@
                                       QPoint cursorPos,
                                       vector<QRect> &extents) const
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
     extents.push_back(vertical);
 
@@ -1869,6 +1899,9 @@
 SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                   QPoint cursorPos) const
 {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return;
+
     paint.save();
     
     int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
@@ -1885,35 +1918,39 @@
     
     double fundamental = getFrequencyForY(v, cursorPos.y());
 
-    PaintAssistant::drawVisibleText(v, paint,
-                       sw + 2,
-                       cursorPos.y() - 2,
-                       QString("%1 Hz").arg(fundamental),
-                       PaintAssistant::OutlinedText);
+    PaintAssistant::drawVisibleText
+        (v, paint,
+         sw + 2,
+         cursorPos.y() - 2,
+         QString("%1 Hz").arg(fundamental),
+         PaintAssistant::OutlinedText);
 
     if (Pitch::isFrequencyInMidiRange(fundamental)) {
         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
-        PaintAssistant::drawVisibleText(v, paint,
-                           sw + 2,
-                           cursorPos.y() + paint.fontMetrics().ascent() + 2,
-                           pitchLabel,
-                           PaintAssistant::OutlinedText);
+        PaintAssistant::drawVisibleText
+            (v, paint,
+             sw + 2,
+             cursorPos.y() + paint.fontMetrics().ascent() + 2,
+             pitchLabel,
+             PaintAssistant::OutlinedText);
     }
 
     sv_frame_t frame = v->getFrameForX(cursorPos.x());
-    RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate());
+    RealTime rt = RealTime::frame2RealTime(frame, model->getSampleRate());
     QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
     QString frameLabel = QString("%1").arg(frame);
-    PaintAssistant::drawVisibleText(v, paint,
-                       cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
-                       v->getPaintHeight() - 2,
-                       frameLabel,
-                       PaintAssistant::OutlinedText);
-    PaintAssistant::drawVisibleText(v, paint,
-                       cursorPos.x() + 2,
-                       v->getPaintHeight() - 2,
-                       rtLabel,
-                       PaintAssistant::OutlinedText);
+    PaintAssistant::drawVisibleText
+        (v, paint,
+         cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
+         v->getPaintHeight() - 2,
+         frameLabel,
+         PaintAssistant::OutlinedText);
+    PaintAssistant::drawVisibleText
+        (v, paint,
+         cursorPos.x() + 2,
+         v->getPaintHeight() - 2,
+         rtLabel,
+         PaintAssistant::OutlinedText);
 
     int harmonic = 2;
 
@@ -1949,7 +1986,8 @@
     int x = pos.x();
     int y = pos.y();
 
-    if (!m_model || !m_model->isOK()) return "";
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return "";
 
     double magMin = 0, magMax = 0;
     double phaseMin = 0, phaseMax = 0;
@@ -2068,7 +2106,8 @@
 int
 SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
 {
-    if (!m_model || !m_model->isOK()) return 0;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return 0;
 
     int cw = 0;
     if (detailed) cw = getColourScaleWidth(paint);
@@ -2076,7 +2115,7 @@
     int tw = paint.fontMetrics().width(QString("%1")
                                      .arg(m_maxFrequency > 0 ?
                                           m_maxFrequency - 1 :
-                                          m_model->getSampleRate() / 2));
+                                          model->getSampleRate() / 2));
 
     int fw = paint.fontMetrics().width(tr("43Hz"));
     if (tw < fw) tw = fw;
@@ -2090,7 +2129,8 @@
 SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed,
                                      QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) {
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) {
         return;
     }
 
@@ -2110,7 +2150,7 @@
     int pkw = (m_binScale == BinScale::Log ? 10 : 0);
 
     int bins = getFFTSize() / 2;
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
 
     if (m_maxFrequency > 0) {
         bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
@@ -2380,9 +2420,10 @@
 int
 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const
 {
-    if (!m_model) return 0;
-
-    sv_samplerate_t sr = m_model->getSampleRate();
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0;
+
+    sv_samplerate_t sr = model->getSampleRate();
 
     SpectrogramRangeMapper mapper(sr, getFFTSize());
 
@@ -2403,12 +2444,13 @@
 int
 SpectrogramLayer::getCurrentVerticalZoomStep() const
 {
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return 0;
 
     double dmin, dmax;
     getDisplayExtents(dmin, dmax);
     
-    SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize());
+    SpectrogramRangeMapper mapper(model->getSampleRate(), getFFTSize());
     int n = mapper.getPositionForValue(dmax - dmin);
 //    SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl;
     return n;
@@ -2417,14 +2459,15 @@
 void
 SpectrogramLayer::setVerticalZoomStep(int step)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return;
 
     double dmin = m_minFrequency, dmax = m_maxFrequency;
 //    getDisplayExtents(dmin, dmax);
 
 //    cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
     
-    sv_samplerate_t sr = m_model->getSampleRate();
+    sv_samplerate_t sr = model->getSampleRate();
     SpectrogramRangeMapper mapper(sr, getFFTSize());
     double newdist = mapper.getValueForPosition(step);
 
@@ -2485,8 +2528,9 @@
 RangeMapper *
 SpectrogramLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
-    return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize());
+    auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
+    if (!model) return nullptr;
+    return new SpectrogramRangeMapper(model->getSampleRate(), getFFTSize());
 }
 
 void
--- a/layer/SpectrogramLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SpectrogramLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -60,7 +60,7 @@
     ~SpectrogramLayer();
 
     const ZoomConstraint *getZoomConstraint() const override { return this; }
-    const Model *getModel() const override { return m_model; }
+    ModelId getModel() const override { return m_model; }
     void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override;
     void setSynchronousPainting(bool synchronous) override;
 
@@ -81,7 +81,7 @@
 
     bool hasLightBackground() const override;
 
-    void setModel(const DenseTimeValueModel *model);
+    void setModel(ModelId model); // a DenseTimeValueModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -234,16 +234,16 @@
     void setVerticalZoomStep(int) override;
     RangeMapper *getNewVerticalZoomRangeMapper() const override;
 
-    const Model *getSliceableModel() const override;
+    ModelId getSliceableModel() const override;
 
 protected slots:
-    void cacheInvalid();
-    void cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame);
+    void cacheInvalid(ModelId);
+    void cacheInvalid(ModelId, sv_frame_t startFrame, sv_frame_t endFrame);
     
     void preferenceChanged(PropertyContainer::PropertyName name);
 
 protected:
-    const DenseTimeValueModel *m_model; // I do not own this
+    ModelId m_model; // a DenseTimeValueModel
 
     int                 m_channel;
     int                 m_windowSize;
@@ -306,11 +306,11 @@
 
     int getFFTSize() const; // m_windowSize * getOversampling()
 
-    FFTModel *m_fftModel;
-    FFTModel *getFFTModel() const { return m_fftModel; }
-    Dense3DModelPeakCache *m_wholeCache;
-    Dense3DModelPeakCache *m_peakCache;
-    Dense3DModelPeakCache *getPeakCache() const { return m_peakCache; }
+    // We take responsibility for registering/deregistering these
+    // models and caches with ModelById
+    ModelId m_fftModel; // an FFTModel
+    ModelId m_wholeCache; // a Dense3DModelPeakCache
+    ModelId m_peakCache; // a Dense3DModelPeakCache
     int m_peakCacheDivisor;
     void checkCacheSpace(int *suggestedPeakDivisor,
                          bool *createWholeCache) const;
--- a/layer/SpectrumLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SpectrumLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -33,7 +33,6 @@
 
 
 SpectrumLayer::SpectrumLayer() :
-    m_originModel(nullptr),
     m_channel(-1),
     m_channelSet(false),
     m_windowSize(4096),
@@ -56,29 +55,19 @@
 
 SpectrumLayer::~SpectrumLayer()
 {
-    Model *m = const_cast<Model *>
-        (static_cast<const Model *>(m_sliceableModel));
-    if (m) m->aboutToDelete();
-    m_sliceableModel = nullptr;
-    delete m;
+    ModelById::release(m_sliceableModel);
 }
 
 void
-SpectrumLayer::setModel(DenseTimeValueModel *model)
+SpectrumLayer::setModel(ModelId modelId)
 {
-    SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl;
+    auto newModel = ModelById::getAs<DenseTimeValueModel>(modelId);
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a DenseTimeValueModel");
+    }
     
-    if (m_originModel == model) return;
-
-    m_originModel = model;
-
-    if (m_sliceableModel) {
-        Model *m = const_cast<Model *>
-            (static_cast<const Model *>(m_sliceableModel));
-        m->aboutToDelete();
-        setSliceableModel(nullptr);
-        delete m;
-    }
+    if (m_originModel == modelId) return;
+    m_originModel = modelId;
 
     m_newFFTNeeded = true;
 
@@ -104,26 +93,21 @@
 void
 SpectrumLayer::setupFFT()
 {
-    if (m_sliceableModel) {
-        Model *m = const_cast<Model *>
-            (static_cast<const Model *>(m_sliceableModel));
-        m->aboutToDelete();
-        setSliceableModel(nullptr);
-        delete m;
-    }
+    ModelById::release(m_sliceableModel);
+    m_sliceableModel = {};
 
-    if (!m_originModel) {
+    if (m_originModel.isNone()) {
         return;
     }
 
     int fftSize = getFFTSize();
 
-    FFTModel *newFFT = new FFTModel(m_originModel,
-                                    m_channel,
-                                    m_windowType,
-                                    m_windowSize,
-                                    getWindowIncrement(),
-                                    fftSize);
+    auto newFFT = std::make_shared<FFTModel>(m_originModel,
+                                             m_channel,
+                                             m_windowType,
+                                             m_windowSize,
+                                             getWindowIncrement(),
+                                             fftSize);
 
     if (m_minbin == 0 && m_maxbin == 0) {
         m_minbin = 1;
@@ -132,7 +116,7 @@
         m_maxbin = newFFT->getHeight();
     }
 
-    setSliceableModel(newFFT);
+    setSliceableModel(ModelById::add(newFFT));
 
     m_biasCurve.clear();
     for (int i = 0; i < fftSize; ++i) {
@@ -403,15 +387,19 @@
 double
 SpectrumLayer::getBinForFrequency(double freq) const
 {
-    if (!m_sliceableModel) return 0;
-    double bin = (freq * getFFTSize()) / m_sliceableModel->getSampleRate();
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
+    double bin = (freq * getFFTSize()) / sliceableModel->getSampleRate();
     return bin;
 }
 
 double
 SpectrumLayer::getBinForX(const LayerGeometryProvider *v, double x) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
     double bin = getBinForFrequency(getFrequencyForX(v, x));
     return bin;
 }
@@ -419,7 +407,9 @@
 double
 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
 
     double fmin = getFrequencyForBin(m_minbin);
     double fmax = getFrequencyForBin(m_maxbin);
@@ -431,15 +421,19 @@
 double
 SpectrumLayer::getFrequencyForBin(double bin) const
 {
-    if (!m_sliceableModel) return 0;
-    double freq = (bin * m_sliceableModel->getSampleRate()) / getFFTSize();
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
+    double freq = (bin * sliceableModel->getSampleRate()) / getFFTSize();
     return freq;
 }
 
 double
 SpectrumLayer::getXForBin(const LayerGeometryProvider *v, double bin) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
     double x = getXForFrequency(v, getFrequencyForBin(bin));
     return x;
 }
@@ -447,7 +441,9 @@
 double
 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
 {
-    if (!m_sliceableModel) return 0;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return 0;
 
     double fmin = getFrequencyForBin(m_minbin);
     double fmax = getFrequencyForBin(m_maxbin);
@@ -475,8 +471,12 @@
 
         if (value > 0.0) {
             value = 10.0 * log10(value);
-            if (value < m_threshold) value = m_threshold;
-        } else value = m_threshold;
+            if (value < m_threshold) {
+                value = m_threshold;
+            }
+        } else {
+            value = m_threshold;
+        }
 
         unit = "dBV";
 
@@ -513,6 +513,11 @@
 
     int sw = getVerticalScaleWidth(v, false, paint);
 
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
                 paint.fontMetrics().width("0.0000001 V") + 2,
                 paint.fontMetrics().height());
@@ -543,7 +548,9 @@
 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
                                QPoint cursorPos) const
 {
-    if (!m_sliceableModel) return;
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return;
 
     paint.save();
     QFont fn = paint.font();
@@ -630,7 +637,9 @@
 QString
 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
 {
-    if (!m_sliceableModel) return "";
+    auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
+        (m_sliceableModel);
+    if (!sliceableModel) return "";
 
     int minbin = 0, maxbin = 0, range = 0;
     QString genericDesc = SliceLayer::getFeatureDescriptionAux
@@ -651,10 +660,10 @@
     
     QString binstr;
     QString hzstr;
-    int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
+    int minfreq = int(lrint((minbin * sliceableModel->getSampleRate()) /
                             getFFTSize()));
     int maxfreq = int(lrint((std::max(maxbin, minbin)
-                             * m_sliceableModel->getSampleRate()) /
+                             * sliceableModel->getSampleRate()) /
                             getFFTSize()));
 
     if (maxbin != minbin) {
@@ -698,7 +707,7 @@
 
     QString description;
 
-    if (range > int(m_sliceableModel->getResolution())) {
+    if (range > int(sliceableModel->getResolution())) {
         description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
             .arg(genericDesc)
             .arg(binstr)
@@ -722,8 +731,8 @@
 void
 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_originModel || !m_originModel->isOK() ||
-        !m_originModel->isReady()) {
+    auto originModel = ModelById::get(m_originModel);
+    if (!originModel || !originModel->isOK() || !originModel->isReady()) {
         SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
         return;
     }
@@ -733,8 +742,8 @@
         const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
     }
 
-    FFTModel *fft = dynamic_cast<FFTModel *>
-        (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
+    auto fft = ModelById::getAs<FFTModel>(m_sliceableModel);
+    if (!fft) return;
 
     double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj
 
--- a/layer/SpectrumLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/SpectrumLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -37,8 +37,8 @@
     SpectrumLayer();
     ~SpectrumLayer();
     
-    void setModel(DenseTimeValueModel *model);
-    virtual const Model *getModel() const override { return m_originModel; }
+    void setModel(ModelId model); // a DenseTimeValueModel
+    virtual ModelId getModel() const override { return m_originModel; }
 
     virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos,
                                      std::vector<QRect> &extents) const override;
@@ -115,7 +115,7 @@
     void preferenceChanged(PropertyContainer::PropertyName name);
 
 protected:
-    DenseTimeValueModel    *m_originModel;
+    ModelId                 m_originModel; // a DenseTimeValueModel
     int                     m_channel;
     bool                    m_channelSet;
     int                     m_windowSize;
--- a/layer/TextLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TextLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -34,7 +34,6 @@
 
 TextLayer::TextLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_originalPoint(0, 0.0, tr("Empty Label")),
     m_editingPoint(0, 0.0, tr("Empty Label")),
@@ -43,15 +42,29 @@
     
 }
 
+int
+TextLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-TextLayer::setModel(TextModel *model)
+TextLayer::setModel(ModelId modelId)
 {
-    if (m_model == model) return;
-    m_model = model;
+    auto newModel = ModelById::getAs<TextModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a TextModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
-
-//    SVDEBUG << "TextLayer::setModel(" << model << ")" << endl;
+    if (newModel) {
+        connectSignals(m_model);
+    }
 
     emit modelReplaced();
 }
@@ -111,14 +124,15 @@
 EventVector
 TextLayer::getLocalPoints(LayerGeometryProvider *v, int x, int y) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return {};
 
     int overlap = ViewManager::scalePixelSize(150);
     
     sv_frame_t frame0 = v->getFrameForX(-overlap);
     sv_frame_t frame1 = v->getFrameForX(v->getPaintWidth() + overlap);
     
-    EventVector points(m_model->getEventsSpanning(frame0, frame1 - frame0));
+    EventVector points(model->getEventsSpanning(frame0, frame1 - frame0));
 
     EventVector rv;
     QFontMetrics metrics = QFontMetrics(QFont());
@@ -156,11 +170,12 @@
 bool
 TextLayer::getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &p) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return false;
 
     sv_frame_t a = v->getFrameForX(x - ViewManager::scalePixelSize(120));
     sv_frame_t b = v->getFrameForX(x + ViewManager::scalePixelSize(10));
-    EventVector onPoints = m_model->getEventsWithin(a, b);
+    EventVector onPoints = model->getEventsWithin(a, b);
     if (onPoints.empty()) return false;
 
     double nearestDistance = -1;
@@ -186,12 +201,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x, pos.y());
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return "";
@@ -200,7 +216,7 @@
 
     sv_frame_t useFrame = points.begin()->getFrame();
 
-    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+    RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
     
     QString text;
 
@@ -224,7 +240,8 @@
                               int &resolution,
                               SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
@@ -235,7 +252,7 @@
     // an editing operation, i.e. closest feature in either direction
     // but only if it is "close enough"
 
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame), -1);
@@ -245,7 +262,7 @@
     }    
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -274,9 +291,10 @@
 void
 TextLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
 //    Profiler profiler("TextLayer::paint", true);
@@ -286,7 +304,7 @@
     sv_frame_t frame0 = v->getFrameForX(x0 - overlap);
     sv_frame_t frame1 = v->getFrameForX(x1 + overlap);
 
-    EventVector points(m_model->getEventsWithin(frame0, frame1 - frame0, 2));
+    EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 2));
     if (points.empty()) return;
 
     QColor brushColour(getBaseQColor());
@@ -299,7 +317,7 @@
     penColour = v->getForeground();
 
 //    SVDEBUG << "TextLayer::paint: resolution is "
-//              << m_model->getResolution() << " frames" << endl;
+//              << model->getResolution() << " frames" << endl;
 
     QPoint localPos;
     Event illuminatePoint(0);
@@ -379,14 +397,15 @@
 {
 //    SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) {
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) {
         SVDEBUG << "TextLayer::drawStart: no model" << endl;
         return;
     }
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double height = getHeightForY(v, e->y());
 
@@ -394,7 +413,7 @@
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, "Add Label");
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, "Add Label");
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -405,11 +424,12 @@
 {
 //    SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double height = getHeightForY(v, e->y());
 
@@ -424,7 +444,8 @@
 TextLayer::drawEnd(LayerGeometryProvider *v, QMouseEvent *)
 {
 //    SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !m_editing) return;
 
     bool ok = false;
     QString label = QInputDialog::getText(v->getView(), tr("Enter label"),
@@ -447,7 +468,8 @@
 void
 TextLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
 
@@ -467,7 +489,8 @@
 void
 TextLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !m_editing) return;
 
     m_editing = false;
 
@@ -476,7 +499,7 @@
     if (p.getFrame() != m_editingPoint.getFrame() ||
         p.getValue() != m_editingPoint.getValue()) return;
 
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
     m_editingCommand->remove(m_editingPoint);
     finish(m_editingCommand);
     m_editingCommand = nullptr;
@@ -488,7 +511,8 @@
 {
 //    SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
         return;
@@ -508,7 +532,8 @@
 void
 TextLayer::editDrag(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frameDiff =
         v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
@@ -519,10 +544,10 @@
     double height = m_originalPoint.getValue() + heightDiff;
 
     if (frame < 0) frame = 0;
-    frame = (frame / m_model->getResolution()) * m_model->getResolution();
+    frame = (frame / model->getResolution()) * model->getResolution();
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Drag Label"));
+        m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Label"));
     }
 
     m_editingCommand->remove(m_editingPoint);
@@ -536,7 +561,8 @@
 TextLayer::editEnd(LayerGeometryProvider *, QMouseEvent *)
 {
 //    SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
 
@@ -563,7 +589,8 @@
 bool
 TextLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return false;
 
     Event text;
     if (!getPointToDrag(v, e->x(), e->y(), text)) return false;
@@ -576,7 +603,7 @@
                                   QLineEdit::Normal, label, &ok);
     if (ok && label != text.getLabel()) {
         ChangeEventsCommand *command =
-            new ChangeEventsCommand(m_model, tr("Re-Label Point"));
+            new ChangeEventsCommand(m_model.untyped, tr("Re-Label Point"));
         command->remove(text);
         command->add(text.withLabel(label));
         finish(command);
@@ -588,13 +615,14 @@
 void
 TextLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -609,13 +637,14 @@
 void
 TextLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -637,13 +666,14 @@
 void
 TextLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -655,10 +685,11 @@
 void
 TextLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsStartingWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -668,7 +699,8 @@
 bool
 TextLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */, bool /* interactive */)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<TextModel>(m_model);
+    if (!model) return false;
 
     const EventVector &points = from.getPoints();
 
@@ -692,7 +724,7 @@
     }
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+        new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     double valueMin = 0.0, valueMax = 1.0;
     for (EventVector::const_iterator i = points.begin();
--- a/layer/TextLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TextLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -62,8 +62,8 @@
 
     bool editOpen(LayerGeometryProvider *, QMouseEvent *) override; // on double-click
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(TextModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a TextModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -78,7 +78,7 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
     bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const override;
@@ -100,7 +100,7 @@
 
     bool getPointToDrag(LayerGeometryProvider *v, int x, int y, Event &) const;
 
-    TextModel *m_model;
+    ModelId m_model;
     bool m_editing;
     QPoint m_editOrigin;
     Event m_originalPoint;
--- a/layer/TimeInstantLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TimeInstantLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -41,7 +41,6 @@
 
 TimeInstantLayer::TimeInstantLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_editingPoint(0, tr("New Point")),
     m_editingCommand(nullptr),
@@ -53,20 +52,30 @@
 {
 }
 
+int
+TimeInstantLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-TimeInstantLayer::setModel(SparseOneDimensionalModel *model)
+TimeInstantLayer::setModel(ModelId modelId)
 {
-    if (m_model == model) return;
-    m_model = model;
+    auto newModel = ModelById::getAs<SparseOneDimensionalModel>(modelId);
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a SparseOneDimensionalModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
-
-#ifdef DEBUG_TIME_INSTANT_LAYER
-    cerr << "TimeInstantLayer::setModel(" << model << ")" << endl;
-#endif
-
-    if (m_model && m_model->getRDFTypeURI().endsWith("Segment")) {
-        setPlotStyle(PlotSegmentation);
+    if (newModel) {
+        connectSignals(m_model);
+        if (newModel->getRDFTypeURI().endsWith("Segment")) {
+            setPlotStyle(PlotSegmentation);
+        }
     }
 
     emit modelReplaced();
@@ -149,6 +158,14 @@
 }
 
 bool
+TimeInstantLayer::needsTextLabelHeight() const
+{
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (model) return model->hasTextLabels();
+    else return false;
+}
+
+bool
 TimeInstantLayer::isLayerScrollable(const LayerGeometryProvider *v) const
 {
     QPoint discard;
@@ -158,7 +175,8 @@
 EventVector
 TimeInstantLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return {};
 
     // Return a set of points that all have the same frame number, the
     // nearest to the given x coordinate, and that are within a
@@ -166,12 +184,12 @@
 
     sv_frame_t frame = v->getFrameForX(x);
 
-    EventVector exact = m_model->getEventsStartingAt(frame);
+    EventVector exact = model->getEventsStartingAt(frame);
     if (!exact.empty()) return exact;
 
     // overspill == 1, so one event either side of the given span
-    EventVector neighbouring = m_model->getEventsWithin
-        (frame, m_model->getResolution(), 1);
+    EventVector neighbouring = model->getEventsWithin
+        (frame, model->getResolution(), 1);
 
     double fuzz = v->scaleSize(2);
     sv_frame_t suitable = 0;
@@ -195,7 +213,7 @@
     }
 
     if (have) {
-        return m_model->getEventsStartingAt(suitable);
+        return model->getEventsStartingAt(suitable);
     } else {
         return {};
     }
@@ -204,10 +222,11 @@
 QString
 TimeInstantLayer::getLabelPreceding(sv_frame_t frame) const
 {
-    if (!m_model || !m_model->hasTextLabels()) return "";
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !model->hasTextLabels()) return "";
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event e) { return e.hasLabel() && e.getLabel() != ""; },
          EventSeries::Backward,
@@ -223,12 +242,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return tr("No local points");
@@ -237,7 +257,7 @@
 
     sv_frame_t useFrame = points.begin()->getFrame();
 
-    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+    RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
     
     QString text;
 
@@ -259,7 +279,8 @@
                                      int &resolution,
                                      SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
@@ -270,7 +291,7 @@
     // an editing operation, i.e. closest feature in either direction
     // but only if it is "close enough"
     
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame));
@@ -280,7 +301,7 @@
     }
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -295,7 +316,8 @@
 void
 TimeInstantLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !model->isOK()) return;
 
 //    Profiler profiler("TimeInstantLayer::paint", true);
 
@@ -311,12 +333,12 @@
         overspill = 1;
     }
     
-    EventVector points(m_model->getEventsWithin(frame0, frame1 - frame0,
+    EventVector points(model->getEventsWithin(frame0, frame1 - frame0,
                                                 overspill));
 
     bool odd = false;
     if (m_plotStyle == PlotSegmentation && !points.empty()) {
-        int index = m_model->getRowForFrame(points.begin()->getFrame());
+        int index = model->getRowForFrame(points.begin()->getFrame());
         odd = ((index % 2) == 1);
     }
 
@@ -337,13 +359,13 @@
         } else if (getBaseQColor() == Qt::darkGreen) {
             oddBrushColour = Qt::green;
         } else {
-            oddBrushColour = oddBrushColour.light(150);
+            oddBrushColour = oddBrushColour.lighter(150);
         }
         oddBrushColour.setAlpha(100);
     }
 
 //    SVDEBUG << "TimeInstantLayer::paint: resolution is "
-//              << m_model->getResolution() << " frames" << endl;
+//              << model->getResolution() << " frames" << endl;
 
     QPoint localPos;
     sv_frame_t illuminateFrame = -1;
@@ -369,7 +391,7 @@
         if (x == prevX && m_plotStyle == PlotInstants &&
             p.getFrame() != illuminateFrame) continue;
 
-        int iw = v->getXForFrame(p.getFrame() + m_model->getResolution()) - x;
+        int iw = v->getXForFrame(p.getFrame() + model->getResolution()) - x;
         if (iw < 2) {
             if (iw < 1) {
                 iw = 2;
@@ -405,7 +427,7 @@
                 Event q(*j);
                 nx = v->getXForFrame(q.getFrame());
             } else {
-                nx = v->getXForFrame(m_model->getEndFrame());
+                nx = v->getXForFrame(model->getEndFrame());
             }
 
             if (nx >= x) {
@@ -424,6 +446,11 @@
         paint.setPen(getBaseQColor());
         
         if (p.getLabel() != "") {
+            
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 
             // only draw if there's enough room from here to the next point
 
@@ -454,16 +481,17 @@
     cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl;
 #endif
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     m_editingPoint = Event(frame, tr("New Point"));
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
     m_editingCommand->add(m_editingPoint);
 
     m_editing = true;
@@ -476,11 +504,12 @@
     cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl;
 #endif
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
     m_editingCommand->remove(m_editingPoint);
     m_editingPoint = m_editingPoint.withFrame(frame);
     m_editingCommand->add(m_editingPoint);
@@ -492,10 +521,11 @@
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl;
 #endif
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !m_editing) return;
     QString newName = tr("Add Point at %1 s")
         .arg(RealTime::frame2RealTime(m_editingPoint.getFrame(),
-                                      m_model->getSampleRate())
+                                      model->getSampleRate())
              .toText(false).c_str());
     m_editingCommand->setName(newName);
     finish(m_editingCommand);
@@ -506,7 +536,8 @@
 void
 TimeInstantLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
@@ -529,7 +560,8 @@
 void
 TimeInstantLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !m_editing) return;
 
     m_editing = false;
 
@@ -537,7 +569,7 @@
     if (points.empty()) return;
     if (points.begin()->getFrame() != m_editingPoint.getFrame()) return;
 
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
     m_editingCommand->remove(m_editingPoint);
     finish(m_editingCommand);
     m_editingCommand = nullptr;
@@ -551,7 +583,8 @@
     cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl;
 #endif
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
@@ -573,14 +606,15 @@
     cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl;
 #endif
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Drag Point"));
+        m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
     }
 
     m_editingCommand->remove(m_editingPoint);
@@ -594,11 +628,12 @@
 #ifdef DEBUG_TIME_INSTANT_LAYER
     cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl;
 #endif
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model || !m_editing) return;
     if (m_editingCommand) {
         QString newName = tr("Move Point to %1 s")
             .arg(RealTime::frame2RealTime(m_editingPoint.getFrame(),
-                                          m_model->getSampleRate())
+                                          model->getSampleRate())
                  .toText(false).c_str());
         m_editingCommand->setName(newName);
         finish(m_editingCommand);
@@ -610,7 +645,8 @@
 bool
 TimeInstantLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return false;
 
     EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return false;
@@ -618,7 +654,7 @@
     Event point = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowText);
 
@@ -632,7 +668,7 @@
             .withLabel(dialog->getText());
         
         ChangeEventsCommand *command =
-            new ChangeEventsCommand(m_model, tr("Edit Point"));
+            new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
         command->remove(point);
         command->add(newPoint);
         finish(command);
@@ -645,13 +681,14 @@
 void
 TimeInstantLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     for (auto p: points) {
         Event newPoint = p
@@ -666,13 +703,14 @@
 void
 TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -694,13 +732,14 @@
 void
 TimeInstantLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selection"));
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     for (auto p: points) {
         command->remove(p);
@@ -712,10 +751,11 @@
 void
 TimeInstantLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     for (auto p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -725,7 +765,8 @@
 bool
 TimeInstantLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset, bool)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
+    if (!model) return false;
 
     EventVector points = from.getPoints();
 
@@ -749,7 +790,7 @@
     }
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+        new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     for (EventVector::const_iterator i = points.begin();
          i != points.end(); ++i) {
--- a/layer/TimeInstantLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TimeInstantLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -64,8 +64,8 @@
     bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive) override;
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(SparseOneDimensionalModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a SparseOneDimensionalModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -88,9 +88,9 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
-    bool needsTextLabelHeight() const override { return m_model->hasTextLabels(); }
+    bool needsTextLabelHeight() const override;
 
     bool getValueExtents(double &, double &, bool &, QString &) const override {
         return false;
@@ -118,7 +118,7 @@
 
     bool clipboardAlignmentDiffers(LayerGeometryProvider *v, const Clipboard &) const;
 
-    SparseOneDimensionalModel *m_model;
+    ModelId m_model;
     bool m_editing;
     Event m_editingPoint;
     ChangeEventsCommand *m_editingCommand;
--- a/layer/TimeRulerLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TimeRulerLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -36,14 +36,13 @@
 
 TimeRulerLayer::TimeRulerLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_labelHeight(LabelTop)
 {
     
 }
 
 void
-TimeRulerLayer::setModel(Model *model)
+TimeRulerLayer::setModel(ModelId model)
 {
     if (m_model == model) return;
     m_model = model;
@@ -54,7 +53,8 @@
 TimeRulerLayer::snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame,
                                    int &resolution, SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::get(m_model);
+    if (!model) {
         resolution = 1;
         return false;
     }
@@ -62,7 +62,7 @@
     bool q;
     int64_t tickUSec = getMajorTickUSec(v, q);
     RealTime rtick = RealTime::fromMicroseconds(tickUSec);
-    sv_samplerate_t rate = m_model->getSampleRate();
+    sv_samplerate_t rate = model->getSampleRate();
     
     RealTime rt = RealTime::frame2RealTime(frame, rate);
     double ratio = rt / rtick;
@@ -137,9 +137,10 @@
                                  bool &quarterTicks) const
 {
     // return value is in microseconds
-    if (!m_model || !v) return 1000 * 1000;
+    auto model = ModelById::get(m_model);
+    if (!model || !v) return 1000 * 1000;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return 1000 * 1000;
 
     sv_frame_t startFrame = v->getStartFrame();
@@ -148,6 +149,11 @@
         endFrame = startFrame + 1;
     }
 
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     int exampleWidth = QFontMetrics(QFont()).width("10:42.987654");
     int minPixelSpacing = v->getXForViewX(exampleWidth);
 
@@ -210,7 +216,8 @@
 int
 TimeRulerLayer::getXForUSec(LayerGeometryProvider *v, double us) const
 {
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    auto model = ModelById::get(m_model);
+    sv_samplerate_t sampleRate = model->getSampleRate();
     double dframe = (us * sampleRate) / 1000000.0;
     double eps = 1e-7;
     sv_frame_t frame = sv_frame_t(floor(dframe + eps));
@@ -249,9 +256,10 @@
            << ") [" << rect.width() << "x" << rect.height() << "]" << endl;
 #endif
     
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::get(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
     sv_frame_t startFrame = v->getFrameForX(rect.x() - 50);
--- a/layer/TimeRulerLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TimeRulerLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -34,8 +34,8 @@
 
     void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override;
 
-    void setModel(Model *);
-    const Model *getModel() const override { return m_model; }
+    void setModel(ModelId);
+    ModelId getModel() const override { return m_model; }
 
     enum LabelHeight { LabelTop, LabelMiddle, LabelBottom };
     void setLabelHeight(LabelHeight h) { m_labelHeight = h; }
@@ -63,7 +63,7 @@
     bool canExistWithoutModel() const override { return true; }
 
 protected:
-    Model *m_model;
+    ModelId m_model;
     LabelHeight m_labelHeight;
 
     int getDefaultColourHint(bool dark, bool &impose) override;
--- a/layer/TimeValueLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TimeValueLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -54,7 +54,6 @@
 
 TimeValueLayer::TimeValueLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_originalPoint(0, 0.0, tr("New Point")),
     m_editingPoint(0, 0.0, tr("New Point")),
@@ -71,28 +70,41 @@
     
 }
 
+int
+TimeValueLayer::getCompletion(LayerGeometryProvider *) const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getCompletion();
+    else return 0;
+}
+
 void
-TimeValueLayer::setModel(SparseTimeValueModel *model)
+TimeValueLayer::setModel(ModelId modelId)
 {
-    if (m_model == model) return;
-    m_model = model;
+    auto newModel = ModelById::getAs<SparseTimeValueModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a SparseTimeValueModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+        
+        connectSignals(m_model);
 
-    m_scaleMinimum = 0;
-    m_scaleMaximum = 0;
+        m_scaleMinimum = 0;
+        m_scaleMaximum = 0;
 
-    if (m_model && m_model->getRDFTypeURI().endsWith("Segment")) {
-        setPlotStyle(PlotSegmentation);
+        if (newModel->getRDFTypeURI().endsWith("Segment")) {
+            setPlotStyle(PlotSegmentation);
+        }
+        if (newModel->getRDFTypeURI().endsWith("Change")) {
+            setPlotStyle(PlotSegmentation);
+        }
     }
-    if (m_model && m_model->getRDFTypeURI().endsWith("Change")) {
-        setPlotStyle(PlotSegmentation);
-    }
-
-#ifdef DEBUG_TIME_VALUE_LAYER
-    cerr << "TimeValueLayer::setModel(" << model << ")" << endl;
-#endif
-
+    
     emit modelReplaced();
 }
 
@@ -152,10 +164,19 @@
     return SingleColourLayer::getPropertyGroupName(name);
 }
 
+bool
+TimeValueLayer::needsTextLabelHeight() const
+{
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return false;
+    return m_plotStyle == PlotSegmentation && model->hasTextLabels();
+}
+
 QString
 TimeValueLayer::getScaleUnits() const
 {
-    if (m_model) return m_model->getScaleUnits();
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (model) return model->getScaleUnits();
     else return "";
 }
 
@@ -192,7 +213,8 @@
     } else if (name == "Scale Units") {
 
         if (deflt) *deflt = 0;
-        if (m_model) {
+        auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+        if (model) {
             val = UnitDatabase::getInstance()->getUnitId
                 (getScaleUnits());
         }
@@ -258,10 +280,11 @@
     } else if (name == "Vertical Scale") {
         setVerticalScale(VerticalScale(value));
     } else if (name == "Scale Units") {
-        if (m_model) {
-            m_model->setScaleUnits
+        auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+        if (model) {
+            model->setScaleUnits
                 (UnitDatabase::getInstance()->getUnitById(value));
-            emit modelChanged();
+            emit modelChanged(m_model);
         }
     } else if (name == "Draw Segment Division Lines") {
         setDrawSegmentDivisions(value > 0.5);
@@ -335,10 +358,11 @@
 TimeValueLayer::getValueExtents(double &min, double &max,
                                 bool &logarithmic, QString &unit) const
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return false;
 
-    min = m_model->getValueMinimum();
-    max = m_model->getValueMaximum();
+    min = model->getValueMinimum();
+    max = model->getValueMaximum();
 
     logarithmic = (m_verticalScale == LogScale);
 
@@ -375,7 +399,8 @@
 bool
 TimeValueLayer::getDisplayExtents(double &min, double &max) const
 {
-    if (!m_model || shouldAutoAlign()) return false;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || shouldAutoAlign()) return false;
 
     if (m_scaleMinimum == m_scaleMaximum) {
         bool log;
@@ -401,7 +426,8 @@
 bool
 TimeValueLayer::setDisplayExtents(double min, double max)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return false;
 
     if (min == max) {
         if (min == 0.f) {
@@ -426,7 +452,8 @@
 TimeValueLayer::getVerticalZoomSteps(int &defaultStep) const
 {
     if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return 0;
 
     defaultStep = 0;
     return 100;
@@ -436,7 +463,8 @@
 TimeValueLayer::getCurrentVerticalZoomStep() const
 {
     if (shouldAutoAlign()) return 0;
-    if (!m_model) return 0;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return 0;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return 0;
@@ -459,7 +487,8 @@
 TimeValueLayer::setVerticalZoomStep(int step)
 {
     if (shouldAutoAlign()) return;
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     RangeMapper *mapper = getNewVerticalZoomRangeMapper();
     if (!mapper) return;
@@ -511,7 +540,8 @@
 RangeMapper *
 TimeValueLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return nullptr;
     
     RangeMapper *mapper;
 
@@ -534,7 +564,8 @@
 EventVector
 TimeValueLayer::getLocalPoints(LayerGeometryProvider *v, int x) const
 {
-    if (!m_model) return {};
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return {};
 
     // Return all points at a frame f, where f is the closest frame to
     // pixel coordinate x whose pixel coordinate is both within a
@@ -544,12 +575,12 @@
     
     sv_frame_t frame = v->getFrameForX(x);
     
-    EventVector exact = m_model->getEventsStartingAt(frame);
+    EventVector exact = model->getEventsStartingAt(frame);
     if (!exact.empty()) return exact;
 
     // overspill == 1, so one event either side of the given span
-    EventVector neighbouring = m_model->getEventsWithin
-        (frame, m_model->getResolution(), 1);
+    EventVector neighbouring = model->getEventsWithin
+        (frame, model->getResolution(), 1);
 
     double fuzz = v->scaleSize(2);
     sv_frame_t suitable = 0;
@@ -573,7 +604,7 @@
     }
 
     if (have) {
-        return m_model->getEventsStartingAt(suitable);
+        return model->getEventsStartingAt(suitable);
     } else {
         return {};
     }
@@ -582,10 +613,11 @@
 QString
 TimeValueLayer::getLabelPreceding(sv_frame_t frame) const
 {
-    if (!m_model || !m_model->hasTextLabels()) return "";
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !model->hasTextLabels()) return "";
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event e) { return e.hasLabel() && e.getLabel() != ""; },
          EventSeries::Backward,
@@ -601,12 +633,13 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->getSampleRate()) return "";
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !model->getSampleRate()) return "";
 
     EventVector points = getLocalPoints(v, x);
 
     if (points.empty()) {
-        if (!m_model->isReady()) {
+        if (!model->isReady()) {
             return tr("In progress");
         } else {
             return tr("No local points");
@@ -615,7 +648,7 @@
 
     sv_frame_t useFrame = points.begin()->getFrame();
 
-    RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
+    RealTime rt = RealTime::frame2RealTime(useFrame, model->getSampleRate());
     
     QString valueText;
     float value = points.begin()->getValue();
@@ -656,7 +689,8 @@
                                    int &resolution,
                                    SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) {
         return Layer::snapToFeatureFrame(v, frame, resolution, snap);
     }
 
@@ -667,7 +701,7 @@
     // an editing operation, i.e. closest feature in either direction
     // but only if it is "close enough"
     
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     if (snap == SnapNeighbouring) {
         EventVector points = getLocalPoints(v, v->getXForFrame(frame));
@@ -677,7 +711,7 @@
     }
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -695,20 +729,21 @@
                                      int &resolution,
                                      SnapType snap) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) {
         return Layer::snapToSimilarFeature(v, frame, resolution, snap);
     }
 
     // snap is only permitted to be SnapLeft or SnapRight here.
     
-    resolution = m_model->getResolution();
+    resolution = model->getResolution();
 
     Event ref;
     Event e;
     float matchvalue;
     bool found;
 
-    found = m_model->getNearestEventMatching
+    found = model->getNearestEventMatching
         (frame, [](Event) { return true; }, EventSeries::Backward, ref);
 
     if (!found) {
@@ -717,7 +752,7 @@
 
     matchvalue = ref.getValue();
     
-    found = m_model->getNearestEventMatching
+    found = model->getNearestEventMatching
         (frame,
          [matchvalue](Event e) {
              double epsilon = 0.0001;
@@ -741,11 +776,14 @@
     max = 0.0;
     log = false;
 
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
+
     if (shouldAutoAlign()) {
 
         if (!v->getValueExtents(getScaleUnits(), min, max, log)) {
-            min = m_model->getValueMinimum();
-            max = m_model->getValueMaximum();
+            min = model->getValueMinimum();
+            max = model->getValueMaximum();
         } else if (log) {
             LogRange::mapRange(min, max);
         }
@@ -812,7 +850,6 @@
 bool
 TimeValueLayer::shouldAutoAlign() const
 {
-    if (!m_model) return false;
     QString unit = getScaleUnits();
     return (m_verticalScale == AutoAlignScale && unit != "");
 }
@@ -851,9 +888,10 @@
 void
 TimeValueLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return;
 
-    sv_samplerate_t sampleRate = m_model->getSampleRate();
+    sv_samplerate_t sampleRate = model->getSampleRate();
     if (!sampleRate) return;
 
     paint.setRenderHint(QPainter::Antialiasing, false);
@@ -865,7 +903,7 @@
     sv_frame_t frame1 = v->getFrameForX(x1);
     if (m_derivative) --frame0;
 
-    EventVector points(m_model->getEventsWithin(frame0, frame1 - frame0, 1));
+    EventVector points(model->getEventsWithin(frame0, frame1 - frame0, 1));
     if (points.empty()) return;
 
     paint.setPen(getBaseQColor());
@@ -876,11 +914,11 @@
 
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::paint: resolution is "
-         << m_model->getResolution() << " frames" << endl;
+         << model->getResolution() << " frames" << endl;
 #endif
 
-    double min = m_model->getValueMinimum();
-    double max = m_model->getValueMaximum();
+    double min = model->getValueMinimum();
+    double max = model->getValueMaximum();
     if (max == min) max = min + 1.0;
 
     int origin = int(nearbyint(v->getPaintHeight() -
@@ -900,7 +938,7 @@
     }
 
     int w =
-        v->getXForFrame(frame0 + m_model->getResolution()) -
+        v->getXForFrame(frame0 + model->getResolution()) -
         v->getXForFrame(frame0);
 
     if (m_plotStyle == PlotStems) {
@@ -953,7 +991,7 @@
                 continue;
             }
             gap = (p.getFrame() > prevFrame &&
-                   (p.getFrame() - prevFrame >= m_model->getResolution() * 2));
+                   (p.getFrame() - prevFrame >= model->getResolution() * 2));
         }
 
         if (m_plotStyle != PlotSegmentation) {
@@ -1077,7 +1115,7 @@
                     if (m_plotStyle == PlotDiscreteCurves) {
                         bool nextGap =
                             (nvalue == 0.0) ||
-                            (nf - p.getFrame() >= m_model->getResolution() * 2);
+                            (nf - p.getFrame() >= model->getResolution() * 2);
                         if (nextGap) {
                             x1 = x0;
                             y1 = y0;
@@ -1140,6 +1178,11 @@
                 italic = true;
             }
 
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
             if (label != "") {
                 // Quick test for 20px before we do the slower test using metrics
                 bool haveRoom = (nx > x + 20);
@@ -1179,7 +1222,8 @@
 int
 TimeValueLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
-    if (!m_model) {
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) {
         return 0;
     } else if (shouldAutoAlign() && !valueExtentsMatchMine(v)) {
         return 0;
@@ -1201,7 +1245,8 @@
 void
 TimeValueLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model || m_model->isEmpty()) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || model->isEmpty()) return;
 
     QString unit;
     double min, max;
@@ -1257,10 +1302,11 @@
     cerr << "TimeValueLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
 #endif
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
-    int resolution = m_model->getResolution();
+    int resolution = model->getResolution();
     if (frame < 0) frame = 0;
     frame = (frame / resolution) * resolution;
 
@@ -1290,7 +1336,7 @@
     m_originalPoint = m_editingPoint;
 
     if (m_editingCommand) finish(m_editingCommand);
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Draw Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Draw Point"));
     if (!havePoint) {
         m_editingCommand->add(m_editingPoint);
     }
@@ -1305,10 +1351,11 @@
     cerr << "TimeValueLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
 #endif
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
-    int resolution = m_model->getResolution();
+    int resolution = model->getResolution();
     if (frame < 0) frame = 0;
     frame = (frame / resolution) * resolution;
 
@@ -1366,7 +1413,8 @@
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::drawEnd" << endl;
 #endif
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !m_editing) return;
     finish(m_editingCommand);
     m_editingCommand = nullptr;
     m_editing = false;
@@ -1375,7 +1423,8 @@
 void
 TimeValueLayer::eraseStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
@@ -1398,7 +1447,8 @@
 void
 TimeValueLayer::eraseEnd(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !m_editing) return;
 
     m_editing = false;
 
@@ -1407,7 +1457,7 @@
     if (points.begin()->getFrame() != m_editingPoint.getFrame() ||
         points.begin()->getValue() != m_editingPoint.getValue()) return;
 
-    m_editingCommand = new ChangeEventsCommand(m_model, tr("Erase Point"));
+    m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Erase Point"));
     m_editingCommand->remove(m_editingPoint);
     finish(m_editingCommand);
     m_editingCommand = nullptr;
@@ -1421,7 +1471,8 @@
     cerr << "TimeValueLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
 #endif
 
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return;
@@ -1444,16 +1495,17 @@
     cerr << "TimeValueLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
 #endif
 
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !m_editing) return;
 
     sv_frame_t frame = v->getFrameForX(e->x());
     if (frame < 0) frame = 0;
-    frame = frame / m_model->getResolution() * m_model->getResolution();
+    frame = frame / model->getResolution() * model->getResolution();
 
     double value = getValueForY(v, e->y());
 
     if (!m_editingCommand) {
-        m_editingCommand = new ChangeEventsCommand(m_model, tr("Drag Point"));
+        m_editingCommand = new ChangeEventsCommand(m_model.untyped, tr("Drag Point"));
     }
 
     m_editingCommand->remove(m_editingPoint);
@@ -1469,7 +1521,8 @@
 #ifdef DEBUG_TIME_VALUE_LAYER
     cerr << "TimeValueLayer::editEnd" << endl;
 #endif
-    if (!m_model || !m_editing) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !m_editing) return;
 
     if (m_editingCommand) {
 
@@ -1496,7 +1549,8 @@
 bool
 TimeValueLayer::editOpen(LayerGeometryProvider *v, QMouseEvent *e)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return false;
 
     EventVector points = getLocalPoints(v, e->x());
     if (points.empty()) return false;
@@ -1504,7 +1558,7 @@
     Event point = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowValue |
          ItemEditDialog::ShowText,
@@ -1522,7 +1576,7 @@
             .withLabel(dialog->getText());
         
         ChangeEventsCommand *command =
-            new ChangeEventsCommand(m_model, tr("Edit Point"));
+            new ChangeEventsCommand(m_model.untyped, tr("Edit Point"));
         command->remove(point);
         command->add(newPoint);
         finish(command);
@@ -1535,13 +1589,14 @@
 void
 TimeValueLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Drag Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Drag Selection"));
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
 
@@ -1557,13 +1612,14 @@
 void
 TimeValueLayer::resizeSelection(Selection s, Selection newSize)
 {
-    if (!m_model || !s.getDuration()) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model || !s.getDuration()) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Resize Selection"));
+        new ChangeEventsCommand(m_model.untyped, tr("Resize Selection"));
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     double ratio = double(newSize.getDuration()) / double(s.getDuration());
     double oldStart = double(s.getStartFrame());
@@ -1585,13 +1641,14 @@
 void
 TimeValueLayer::deleteSelection(Selection s)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Delete Selected Points"));
+        new ChangeEventsCommand(m_model.untyped, tr("Delete Selected Points"));
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         command->remove(p);
@@ -1603,10 +1660,11 @@
 void
 TimeValueLayer::copy(LayerGeometryProvider *v, Selection s, Clipboard &to)
 {
-    if (!m_model) return;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return;
 
     EventVector points =
-        m_model->getEventsWithin(s.getStartFrame(), s.getDuration());
+        model->getEventsWithin(s.getStartFrame(), s.getDuration());
 
     for (Event p: points) {
         to.addPoint(p.withReferenceFrame(alignToReference(v, p.getFrame())));
@@ -1617,7 +1675,8 @@
 TimeValueLayer::paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t /* frameOffset */,
                       bool interactive)
 {
-    if (!m_model) return false;
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
+    if (!model) return false;
 
     EventVector points = from.getPoints();
 
@@ -1641,7 +1700,7 @@
     }
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+        new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     enum ValueAvailability {
         UnknownAvailability,
@@ -1654,7 +1713,7 @@
 
     bool haveUsableLabels = false;
     Labeller labeller;
-    labeller.setSampleRate(m_model->getSampleRate());
+    labeller.setSampleRate(model->getSampleRate());
 
     if (interactive) {
 
--- a/layer/TimeValueLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/TimeValueLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -74,8 +74,8 @@
     bool paste(LayerGeometryProvider *v, const Clipboard &from, sv_frame_t frameOffset,
                        bool interactive) override;
 
-    const Model *getModel() const override { return m_model; }
-    void setModel(SparseTimeValueModel *model);
+    ModelId getModel() const override { return m_model; }
+    void setModel(ModelId model); // a SparseTimeValueModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -124,11 +124,9 @@
 
     bool isLayerEditable() const override { return true; }
 
-    int getCompletion(LayerGeometryProvider *) const override { return m_model->getCompletion(); }
+    int getCompletion(LayerGeometryProvider *) const override;
 
-    bool needsTextLabelHeight() const override {
-        return m_plotStyle == PlotSegmentation && m_model->hasTextLabels();
-    }
+    bool needsTextLabelHeight() const override;
 
     bool getValueExtents(double &min, double &max,
                                  bool &logarithmic, QString &unit) const override;
@@ -178,7 +176,7 @@
 
     int getDefaultColourHint(bool dark, bool &impose) override;
 
-    SparseTimeValueModel *m_model;
+    ModelId m_model;
     bool m_editing;
     Event m_originalPoint;
     Event m_editingPoint;
--- a/layer/WaveformLayer.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/WaveformLayer.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -41,7 +41,6 @@
 
 WaveformLayer::WaveformLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_gain(1.0f),
     m_autoNormalize(false),
     m_showMeans(true),
@@ -60,29 +59,47 @@
     delete m_cache;
 }
 
+const ZoomConstraint *
+WaveformLayer::getZoomConstraint() const
+{
+    auto model = ModelById::get(m_model);
+    if (model) return model->getZoomConstraint();
+    else return nullptr;
+}
+
 void
-WaveformLayer::setModel(const RangeSummarisableTimeValueModel *model)
+WaveformLayer::setModel(ModelId modelId)
 {
+    auto oldModel = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    auto newModel = ModelById::getAs<RangeSummarisableTimeValueModel>(modelId);
+
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a RangeSummarisableTimeValueModel");
+    }
+    
+    if (m_model == modelId) return;
+    m_model = modelId;
+
+    m_cacheValid = false;
+    
     bool channelsChanged = false;
     if (m_channel == -1) {
-        if (!m_model) {
-            if (model) {
+        if (!oldModel) {
+            if (newModel) {
                 channelsChanged = true;
             }
         } else {
-            if (model &&
-                m_model->getChannelCount() != model->getChannelCount()) {
+            if (newModel &&
+                oldModel->getChannelCount() != newModel->getChannelCount()) {
                 channelsChanged = true;
             }
         }
     }
 
-    m_model = model;
-    m_cacheValid = false;
-    if (!m_model || !m_model->isOK()) return;
-
-    connectSignals(m_model);
-
+    if (newModel) {
+        connectSignals(m_model);
+    }
+        
     emit modelReplaced();
 
     if (channelsChanged) emit layerParametersChanged();
@@ -96,7 +113,8 @@
     list.push_back("Gain");
     list.push_back("Normalize Visible Area");
 
-    if (m_model && m_model->getChannelCount() > 1 && m_channel == -1) {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (model && model->getChannelCount() > 1 && m_channel == -1) {
         list.push_back("Channels");
     }
 
@@ -323,8 +341,9 @@
 WaveformLayer::getCompletion(LayerGeometryProvider *) const
 {
     int completion = 100;
-    if (!m_model || !m_model->isOK()) return completion;
-    if (m_model->isReady(&completion)) return 100;
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return completion;
+    if (model->isReady(&completion)) return 100;
     return completion;
 }
 
@@ -361,9 +380,10 @@
                                      bool &merging, bool &mixing)
     const
 {
-    if (!m_model || !m_model->isOK()) return 0;
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return 0;
 
-    int channels = m_model->getChannelCount();
+    int channels = model->getChannelCount();
     if (channels == 0) return 0;
 
     int rawChannels = channels;
@@ -406,6 +426,9 @@
                                    int x, int modelZoomLevel,
                                    sv_frame_t &f0, sv_frame_t &f1) const
 {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model) return false;
+    
     sv_frame_t viewFrame = v->getFrameForX(x);
     if (viewFrame < 0) {
         f0 = 0;
@@ -426,17 +449,20 @@
         f1 = f1 * modelZoomLevel;
     }
     
-    return (f0 < m_model->getEndFrame());
+    return (f0 < model->getEndFrame());
 }
 
 float
 WaveformLayer::getNormalizeGain(LayerGeometryProvider *v, int channel) const
 {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model) return 0.f;
+    
     sv_frame_t startFrame = v->getStartFrame();
     sv_frame_t endFrame = v->getEndFrame();
 
-    sv_frame_t modelStart = m_model->getStartFrame();
-    sv_frame_t modelEnd = m_model->getEndFrame();
+    sv_frame_t modelStart = model->getStartFrame();
+    sv_frame_t modelEnd = model->getEndFrame();
     
     sv_frame_t rangeStart, rangeEnd;
             
@@ -450,7 +476,7 @@
     if (rangeEnd < rangeStart) rangeEnd = rangeStart;
 
     RangeSummarisableTimeValueModel::Range range =
-        m_model->getSummary(channel, rangeStart, rangeEnd - rangeStart);
+        model->getSummary(channel, rangeStart, rangeEnd - rangeStart);
 
     int minChannel = 0, maxChannel = 0;
     bool mergingChannels = false, mixingChannels = false;
@@ -460,7 +486,7 @@
 
     if (mergingChannels || mixingChannels) {
         RangeSummarisableTimeValueModel::Range otherRange =
-            m_model->getSummary(1, rangeStart, rangeEnd - rangeStart);
+            model->getSummary(1, rangeStart, rangeEnd - rangeStart);
         range.setMax(std::max(range.max(), otherRange.max()));
         range.setMin(std::min(range.min(), otherRange.min()));
         range.setAbsmean(std::min(range.absmean(), otherRange.absmean()));
@@ -472,7 +498,8 @@
 void
 WaveformLayer::paint(LayerGeometryProvider *v, QPainter &viewPainter, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model || !model->isOK()) {
         return;
     }
   
@@ -576,7 +603,7 @@
     if (zoomLevel.zone == ZoomLevel::FramesPerPixel) {
         desiredBlockSize = zoomLevel.level;
     }
-    int blockSize = m_model->getSummaryBlockSize(desiredBlockSize);
+    int blockSize = model->getSummaryBlockSize(desiredBlockSize);
 
     sv_frame_t frame0;
     sv_frame_t frame1;
@@ -625,7 +652,7 @@
     }
 
     if (m_aggressive) {
-        if (m_model->isReady() && rect == v->getPaintRect()) {
+        if (model->isReady() && rect == v->getPaintRect()) {
             m_cacheValid = true;
             m_cacheZoomLevel = zoomLevel;
         }
@@ -642,9 +669,12 @@
                                 int blockSize, RangeVec &ranges)
     const
 {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model) return;
+    
     for (int ch = minChannel; ch <= maxChannel; ++ch) {
         ranges.push_back({});
-        m_model->getSummaries(ch, frame0, frame1 - frame0,
+        model->getSummaries(ch, frame0, frame1 - frame0,
                               ranges[ch - minChannel], blockSize);
 #ifdef DEBUG_WAVEFORM_PAINT
             SVCERR << "channel " << ch << ": " << ranges[ch - minChannel].size() << " ranges from " << frame0 << " to " << frame1 << " at zoom level " << blockSize << endl;
@@ -654,9 +684,9 @@
     if (mixingOrMerging) {
         if (minChannel != 0 || maxChannel != 0) {
             throw std::logic_error("Internal error: min & max channels should be 0 when merging or mixing all channels");
-        } else if (m_model->getChannelCount() > 1) {
+        } else if (model->getChannelCount() > 1) {
             ranges.push_back({});
-            m_model->getSummaries
+            model->getSummaries
                 (1, frame0, frame1 - frame0, ranges[1], blockSize);
         }
     }
@@ -669,11 +699,14 @@
                                     int oversampleBy, RangeVec &ranges)
     const
 {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model) return;
+    
     if (mixingOrMerging) {
         if (minChannel != 0 || maxChannel != 0) {
             throw std::logic_error("Internal error: min & max channels should be 0 when merging or mixing all channels");
         }
-        if (m_model->getChannelCount() > 1) {
+        if (model->getChannelCount() > 1) {
             // call back on self for the individual channels with
             // mixingOrMerging false
             getOversampledRanges
@@ -686,8 +719,8 @@
     // sample rate, not the oversampled rate
 
     sv_frame_t tail = 16;
-    sv_frame_t startFrame = m_model->getStartFrame();
-    sv_frame_t endFrame = m_model->getEndFrame();
+    sv_frame_t startFrame = model->getStartFrame();
+    sv_frame_t endFrame = model->getEndFrame();
 
     sv_frame_t rf0 = frame0 - tail;
     if (rf0 < startFrame) {
@@ -706,7 +739,7 @@
     
     for (int ch = minChannel; ch <= maxChannel; ++ch) {
         floatvec_t oversampled = WaveformOversampler::getOversampledData
-            (m_model, ch, frame0, frame1 - frame0, oversampleBy);
+            (*model, ch, frame0, frame1 - frame0, oversampleBy);
         RangeSummarisableTimeValueModel::RangeBlock rr;
         for (float v: oversampled) {
             RangeSummarisableTimeValueModel::Range r;
@@ -738,6 +771,9 @@
                             sv_frame_t frame1)
     const
 {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model) return;
+    
     int x0 = rect.left();
     int y0 = rect.top();
 
@@ -759,9 +795,9 @@
     if (midColour == Qt::black) {
         midColour = Qt::gray;
     } else if (v->hasLightBackground()) {
-        midColour = midColour.light(150);
+        midColour = midColour.lighter(150);
     } else {
-        midColour = midColour.light(50);
+        midColour = midColour.lighter(50);
     }
 
     double gain = m_effectiveGains[ch];
@@ -1033,7 +1069,7 @@
         penWidth = 0.0;
     }
     
-    if (m_model->isReady()) {
+    if (model->isReady()) {
         paint->setPen(QPen(baseColour, penWidth));
     } else {
         paint->setPen(QPen(midColour, penWidth));
@@ -1136,7 +1172,8 @@
 {
     int x = pos.x();
 
-    if (!m_model || !m_model->isOK()) return "";
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model || !model->isOK()) return "";
 
     ZoomLevel zoomLevel = v->getZoomLevel();
 
@@ -1145,15 +1182,15 @@
         desiredBlockSize = zoomLevel.level;
     }
 
-    int blockSize = m_model->getSummaryBlockSize(desiredBlockSize);
+    int blockSize = model->getSummaryBlockSize(desiredBlockSize);
 
     sv_frame_t f0, f1;
     if (!getSourceFramesForX(v, x, blockSize, f0, f1)) return "";
     
     QString text;
 
-    RealTime rt0 = RealTime::frame2RealTime(f0, m_model->getSampleRate());
-    RealTime rt1 = RealTime::frame2RealTime(f1, m_model->getSampleRate());
+    RealTime rt0 = RealTime::frame2RealTime(f0, model->getSampleRate());
+    RealTime rt1 = RealTime::frame2RealTime(f1, model->getSampleRate());
 
     if (f1 != f0 + 1 && (rt0.sec != rt1.sec || rt0.msec() != rt1.msec())) {
         text += tr("Time:\t%1 - %2")
@@ -1174,7 +1211,7 @@
     for (int ch = minChannel; ch <= maxChannel; ++ch) {
 
         RangeSummarisableTimeValueModel::RangeBlock ranges;
-        m_model->getSummaries(ch, f0, f1 - f0, ranges, blockSize);
+        model->getSummaries(ch, f0, f1 - f0, ranges, blockSize);
 
         if (ranges.empty()) continue;
         
@@ -1374,6 +1411,11 @@
 int
 WaveformLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     if (m_scale == LinearScale) {
         return paint.fontMetrics().width("0.0") + 13;
     } else {
@@ -1385,7 +1427,8 @@
 void
 WaveformLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
 {
-    if (!m_model || !m_model->isOK()) {
+    auto model = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
+    if (!model || !model->isOK()) {
         return;
     }
 
--- a/layer/WaveformLayer.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/layer/WaveformLayer.h	Wed Jul 17 14:25:16 2019 +0100
@@ -36,10 +36,8 @@
     WaveformLayer();
     ~WaveformLayer();
 
-    const ZoomConstraint *getZoomConstraint() const override {
-        return m_model ? m_model->getZoomConstraint() : 0;
-    }
-    const Model *getModel() const override { return m_model; }
+    const ZoomConstraint *getZoomConstraint() const override;
+    ModelId getModel() const override { return m_model; }
     void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override;
 
     QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override;
@@ -51,7 +49,7 @@
     int getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const override;
     void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const override;
 
-    void setModel(const RangeSummarisableTimeValueModel *model);
+    void setModel(ModelId model); // a RangeSummarisableTimeValueModel
 
     PropertyList getProperties() const override;
     QString getPropertyLabel(const PropertyName &) const override;
@@ -196,7 +194,7 @@
 protected:
     double dBscale(double sample, int m) const;
 
-    const RangeSummarisableTimeValueModel *m_model; // I do not own this
+    ModelId m_model; 
 
     typedef std::vector<RangeSummarisableTimeValueModel::RangeBlock> RangeVec;
 
--- a/view/AlignmentView.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/AlignmentView.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -147,25 +147,26 @@
         return getDefaultKeyFrames();
     }
 
-    SparseOneDimensionalModel *m = nullptr;
+    ModelId m;
 
     // get the topmost such
     for (int i = 0; i < m_above->getLayerCount(); ++i) {
         if (qobject_cast<TimeInstantLayer *>(m_above->getLayer(i))) {
-            SparseOneDimensionalModel *mm = 
-                qobject_cast<SparseOneDimensionalModel *>
-                (m_above->getLayer(i)->getModel());
-            if (mm) m = mm;
+            ModelId mm = m_above->getLayer(i)->getModel();
+            if (ModelById::isa<SparseOneDimensionalModel>(mm)) {
+                m = mm;
+            }
         }
     }
 
-    if (!m) {
+    auto model = ModelById::getAs<SparseOneDimensionalModel>(m);
+    if (!model) {
         return getDefaultKeyFrames();
     }
 
     vector<sv_frame_t> keyFrames;
 
-    EventVector pp = m->getAllEvents();
+    EventVector pp = model->getAllEvents();
     for (EventVector::const_iterator pi = pp.begin(); pi != pp.end(); ++pi) {
         keyFrames.push_back(pi->getFrame());
     }
--- a/view/Overview.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/Overview.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -42,7 +42,7 @@
 }
 
 void
-Overview::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
+Overview::modelChangedWithin(ModelId modelId, sv_frame_t startFrame, sv_frame_t endFrame)
 {
     using namespace std::rel_ops;
     
@@ -60,9 +60,8 @@
         if (m_modelTestTime.elapsed() < 1000) {
             for (LayerList::const_iterator i = m_layerStack.begin();
                  i != m_layerStack.end(); ++i) {
-                if ((*i)->getModel() &&
-                    (!(*i)->getModel()->isOK() ||
-                     !(*i)->getModel()->isReady())) {
+                auto model = ModelById::get((*i)->getModel());
+                if (model && (!model->isOK() || !model->isReady())) {
                     return;
                 }
             }
@@ -71,7 +70,7 @@
         }
     }
 
-    View::modelChangedWithin(startFrame, endFrame);
+    View::modelChangedWithin(modelId, startFrame, endFrame);
 }
 
 void
--- a/view/Overview.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/Overview.h	Wed Jul 17 14:25:16 2019 +0100
@@ -41,7 +41,7 @@
     QString getPropertyContainerIconName() const override { return "panner"; }
 
 public slots:
-    void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame) override;
+    void modelChangedWithin(ModelId, sv_frame_t startFrame, sv_frame_t endFrame) override;
     void modelReplaced() override;
 
     void globalCentreFrameChanged(sv_frame_t) override;
--- a/view/Pane.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/Pane.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -367,33 +367,49 @@
     Layer *topLayer = getTopLayer();
     bool haveSomeTimeXAxis = false;
 
-    const Model *waveformModel = nullptr; // just for reporting purposes
-    const Model *workModel = nullptr;
-
-    for (LayerList::iterator vi = m_layerStack.end(); vi != m_layerStack.begin(); ) {
+    ModelId waveformModelId; // just for reporting purposes
+    ModelId workModelId;
+
+    for (LayerList::iterator vi = m_layerStack.end();
+         vi != m_layerStack.begin(); ) {
+
         --vi;
+
         if (!haveSomeTimeXAxis && (*vi)->hasTimeXAxis()) {
             haveSomeTimeXAxis = true;
         }
-        if (dynamic_cast<WaveformLayer *>(*vi)) {
-            waveformModel = (*vi)->getModel();
-            workModel = waveformModel;
-        } else {
-            Model *m = (*vi)->getModel();
-            if (dynamic_cast<WaveFileModel *>(m)) {
-                workModel = m;
-            } else if (m && dynamic_cast<WaveFileModel *>(m->getSourceModel())) {
-                workModel = m->getSourceModel();
+
+        ModelId modelId = (*vi)->getModel();
+        if (!modelId.isNone()) {
+            if (dynamic_cast<WaveformLayer *>(*vi)) {
+                waveformModelId = modelId;
+                workModelId = modelId;
+            } else {
+                if (ModelById::isa<WaveFileModel>(modelId)) {
+                    workModelId = modelId;
+                } else {
+                    auto model = ModelById::get(modelId);
+                    if (model) {
+                        ModelId sourceId = model->getSourceModel();
+                        if (ModelById::isa<WaveFileModel>(sourceId)) {
+                            workModelId = sourceId;
+                        }
+                    }
+                }
             }
         }
                 
-        if (waveformModel && workModel && haveSomeTimeXAxis) break;
+        if (!waveformModelId.isNone() &&
+            !workModelId.isNone() &&
+            haveSomeTimeXAxis) {
+            break;
+        }
     }
 
     // Block off left and right extents so we can see where the main model ends
     
-    if (workModel && hasTopLayerTimeXAxis()) {
-        drawModelTimeExtents(r, paint, workModel);
+    if (!workModelId.isNone() && hasTopLayerTimeXAxis()) {
+        drawModelTimeExtents(r, paint, workModelId);
     }
 
     // Crosshairs for mouse movement in measure mode
@@ -444,26 +460,26 @@
     
     paint.setPen(QColor(50, 50, 50));
 
-    if (waveformModel &&
+    if (!waveformModelId.isNone() &&
         sampleRate &&
         m_manager &&
         m_manager->shouldShowDuration()) {
-        drawDurationAndRate(r, waveformModel, sampleRate, paint);
+        drawDurationAndRate(r, waveformModelId, sampleRate, paint);
     }
 
     bool haveWorkTitle = false;
 
-    if (workModel &&
+    if (!workModelId.isNone() &&
         m_manager &&
         m_manager->shouldShowWorkTitle()) {
-        drawWorkTitle(r, paint, workModel);
+        drawWorkTitle(r, paint, workModelId);
         haveWorkTitle = true;
     }
 
-    if (workModel &&
+    if (!workModelId.isNone() &&
         m_manager &&
         m_manager->getAlignMode()) {
-        drawAlignmentStatus(r, paint, workModel, haveWorkTitle);
+        drawAlignmentStatus(r, paint, workModelId, haveWorkTitle);
     }
 
     if (m_manager &&
@@ -632,6 +648,11 @@
         
         paint.save();
         
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
         int tabStop =
             paint.fontMetrics().width(tr("Some lengthy prefix:"));
         
@@ -750,8 +771,11 @@
 }
 
 void
-Pane::drawModelTimeExtents(QRect r, QPainter &paint, const Model *model)
+Pane::drawModelTimeExtents(QRect r, QPainter &paint, ModelId modelId)
 {
+    auto model = ModelById::get(modelId);
+    if (!model) return;
+
     paint.save();
     
     QBrush brush;
@@ -788,14 +812,17 @@
 }
 
 void
-Pane::drawAlignmentStatus(QRect r, QPainter &paint, const Model *model,
+Pane::drawAlignmentStatus(QRect r, QPainter &paint, ModelId modelId,
                           bool down)
 {
-    const Model *reference = model->getAlignmentReference();
+    auto model = ModelById::get(modelId);
+    if (!model) return;
+    
+    ModelId reference = model->getAlignmentReference();
 /*
     if (!reference) {
         cerr << "Pane[" << this << "]::drawAlignmentStatus: No reference" << endl;
-    } else if (reference == model) {
+    } else if (reference == model->getId()) {
         cerr << "Pane[" << this << "]::drawAlignmentStatus: This is the reference model" << endl;
     } else {
         cerr << "Pane[" << this << "]::drawAlignmentStatus: This is not the reference" << endl;
@@ -804,9 +831,9 @@
     QString text;
     int completion = 100;
 
-    if (reference == model) {
+    if (reference == modelId) {
         text = tr("Reference");
-    } else if (!reference) {
+    } else if (reference.isNone()) {
         text = tr("Unaligned");
     } else {
         completion = model->getAlignmentCompletion();
@@ -841,15 +868,18 @@
 }
 
 void
-Pane::modelAlignmentCompletionChanged()
+Pane::modelAlignmentCompletionChanged(ModelId modelId)
 {
-    View::modelAlignmentCompletionChanged();
+    View::modelAlignmentCompletionChanged(modelId);
     update(QRect(0, 0, 300, 100));
 }
 
 void
-Pane::drawWorkTitle(QRect r, QPainter &paint, const Model *model)
+Pane::drawWorkTitle(QRect r, QPainter &paint, ModelId modelId)
 {
+    auto model = ModelById::get(modelId);
+    if (!model) return;
+    
     QString title = model->getTitle();
     QString maker = model->getMaker();
 //SVDEBUG << "Pane::drawWorkTitle: title=\"" << title//<< "\", maker=\"" << maker << "\"" << endl;
@@ -1025,9 +1055,12 @@
 }
 
 void
-Pane::drawDurationAndRate(QRect r, const Model *waveformModel,
+Pane::drawDurationAndRate(QRect r, ModelId waveformModelId,
                           sv_samplerate_t sampleRate, QPainter &paint)
 {
+    auto waveformModel = ModelById::get(waveformModelId);
+    if (!waveformModel) return;
+    
     int fontHeight = paint.fontMetrics().height();
     int fontAscent = paint.fontMetrics().ascent();
 
--- a/view/Pane.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/Pane.h	Wed Jul 17 14:25:16 2019 +0100
@@ -88,7 +88,7 @@
     virtual void toolModeChanged() override;
     virtual void zoomWheelsEnabledChanged() override;
     virtual void viewZoomLevelChanged(View *, ZoomLevel, bool locked) override;
-    virtual void modelAlignmentCompletionChanged() override;
+    virtual void modelAlignmentCompletionChanged(ModelId) override;
 
     // local slots, not overrides
     virtual void horizontalThumbwheelMoved(int value);
@@ -129,12 +129,12 @@
     void drawVerticalScale(QRect r, Layer *, QPainter &);
     void drawFeatureDescription(Layer *, QPainter &);
     void drawCentreLine(sv_samplerate_t, QPainter &, bool omitLine);
-    void drawModelTimeExtents(QRect, QPainter &, const Model *);
-    void drawDurationAndRate(QRect, const Model *, sv_samplerate_t, QPainter &);
-    void drawWorkTitle(QRect, QPainter &, const Model *);
+    void drawModelTimeExtents(QRect, QPainter &, ModelId);
+    void drawDurationAndRate(QRect, ModelId, sv_samplerate_t, QPainter &);
+    void drawWorkTitle(QRect, QPainter &, ModelId);
     void drawLayerNames(QRect, QPainter &);
     void drawEditingSelection(QPainter &);
-    void drawAlignmentStatus(QRect, QPainter &, const Model *, bool down);
+    void drawAlignmentStatus(QRect, QPainter &, ModelId, bool down);
 
     virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1) override;
 
--- a/view/View.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/View.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -632,9 +632,9 @@
 
     if (widgetLight == light) {
         if (widgetLight) {
-            return widgetbg.light();
+            return widgetbg.lighter();
         } else {
-            return widgetbg.dark();
+            return widgetbg.darker();
         }
     }
     else if (light) return Qt::white;
@@ -706,14 +706,14 @@
             this,    SLOT(layerMeasurementRectsChanged()));
     connect(layer, SIGNAL(layerNameChanged()),
             this,    SLOT(layerNameChanged()));
-    connect(layer, SIGNAL(modelChanged()),
-            this,    SLOT(modelChanged()));
-    connect(layer, SIGNAL(modelCompletionChanged()),
-            this,    SLOT(modelCompletionChanged()));
-    connect(layer, SIGNAL(modelAlignmentCompletionChanged()),
-            this,    SLOT(modelAlignmentCompletionChanged()));
-    connect(layer, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-            this,    SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
+    connect(layer, SIGNAL(modelChanged(ModelId)),
+            this,    SLOT(modelChanged(ModelId)));
+    connect(layer, SIGNAL(modelCompletionChanged(ModelId)),
+            this,    SLOT(modelCompletionChanged(ModelId)));
+    connect(layer, SIGNAL(modelAlignmentCompletionChanged(ModelId)),
+            this,    SLOT(modelAlignmentCompletionChanged(ModelId)));
+    connect(layer, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+            this,    SLOT(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
     connect(layer, SIGNAL(modelReplaced()),
             this,    SLOT(modelReplaced()));
 
@@ -761,14 +761,14 @@
                this,    SLOT(layerParameterRangesChanged()));
     disconnect(layer, SIGNAL(layerNameChanged()),
                this,    SLOT(layerNameChanged()));
-    disconnect(layer, SIGNAL(modelChanged()),
-               this,    SLOT(modelChanged()));
-    disconnect(layer, SIGNAL(modelCompletionChanged()),
-               this,    SLOT(modelCompletionChanged()));
-    disconnect(layer, SIGNAL(modelAlignmentCompletionChanged()),
-               this,    SLOT(modelAlignmentCompletionChanged()));
-    disconnect(layer, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-               this,    SLOT(modelChangedWithin(sv_frame_t, sv_frame_t)));
+    disconnect(layer, SIGNAL(modelChanged(ModelId)),
+               this,    SLOT(modelChanged(ModelId)));
+    disconnect(layer, SIGNAL(modelCompletionChanged(ModelId)),
+               this,    SLOT(modelCompletionChanged(ModelId)));
+    disconnect(layer, SIGNAL(modelAlignmentCompletionChanged(ModelId)),
+               this,    SLOT(modelAlignmentCompletionChanged(ModelId)));
+    disconnect(layer, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
+               this,    SLOT(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
     disconnect(layer, SIGNAL(modelReplaced()),
                this,    SLOT(modelReplaced()));
 
@@ -922,14 +922,12 @@
 }
 
 void
-View::modelChanged()
+View::modelChanged(ModelId modelId)
 {
-    QObject *obj = sender();
-
 #ifdef DEBUG_VIEW_WIDGET_PAINT
     cerr << "View(" << this << ")::modelChanged()" << endl;
 #endif
-    
+
     // If the model that has changed is not used by any of the cached
     // layers, we won't need to recreate the cache
     
@@ -939,7 +937,7 @@
     LayerList scrollables = getScrollableBackLayers(false, discard);
     for (LayerList::const_iterator i = scrollables.begin();
          i != scrollables.end(); ++i) {
-        if (*i == obj || (*i)->getModel() == obj) {
+        if ((*i)->getModel() == modelId) {
             recreate = true;
             break;
         }
@@ -951,16 +949,15 @@
 
     emit layerModelChanged();
 
-    checkProgress(obj);
+    checkProgress(modelId);
 
     update();
 }
 
 void
-View::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame)
+View::modelChangedWithin(ModelId modelId,
+                         sv_frame_t startFrame, sv_frame_t endFrame)
 {
-    QObject *obj = sender();
-
     sv_frame_t myStartFrame = getStartFrame();
     sv_frame_t myEndFrame = getEndFrame();
 
@@ -969,11 +966,11 @@
 #endif
 
     if (myStartFrame > 0 && endFrame < myStartFrame) {
-        checkProgress(obj);
+        checkProgress(modelId);
         return;
     }
     if (startFrame > myEndFrame) {
-        checkProgress(obj);
+        checkProgress(modelId);
         return;
     }
 
@@ -986,7 +983,7 @@
     LayerList scrollables = getScrollableBackLayers(false, discard);
     for (LayerList::const_iterator i = scrollables.begin();
          i != scrollables.end(); ++i) {
-        if (*i == obj || (*i)->getModel() == obj) {
+        if ((*i)->getModel() == modelId) {
             recreate = true;
             break;
         }
@@ -999,27 +996,21 @@
     if (startFrame < myStartFrame) startFrame = myStartFrame;
     if (endFrame > myEndFrame) endFrame = myEndFrame;
 
-    checkProgress(obj);
+    checkProgress(modelId);
 
     update();
 }    
 
 void
-View::modelCompletionChanged()
+View::modelCompletionChanged(ModelId modelId)
 {
-//    cerr << "View(" << this << ")::modelCompletionChanged()" << endl;
-
-    QObject *obj = sender();
-    checkProgress(obj);
+    checkProgress(modelId);
 }
 
 void
-View::modelAlignmentCompletionChanged()
+View::modelAlignmentCompletionChanged(ModelId modelId)
 {
-//    cerr << "View(" << this << ")::modelAlignmentCompletionChanged()" << endl;
-
-    QObject *obj = sender();
-    checkProgress(obj);
+    checkProgress(modelId);
 }
 
 void
@@ -1270,11 +1261,13 @@
     bool first = true;
     sv_frame_t startFrame = 0;
 
-    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
-
-        if ((*i)->getModel() && (*i)->getModel()->isOK()) {
-
-            sv_frame_t thisStartFrame = (*i)->getModel()->getStartFrame();
+    for (Layer *layer: m_layerStack) {
+
+        auto model = ModelById::get(layer->getModel());
+
+        if (model && model->isOK()) {
+
+            sv_frame_t thisStartFrame = model->getStartFrame();
 
             if (first || thisStartFrame < startFrame) {
                 startFrame = thisStartFrame;
@@ -1282,6 +1275,7 @@
             first = false;
         }
     }
+    
     return startFrame;
 }
 
@@ -1291,11 +1285,13 @@
     bool first = true;
     sv_frame_t endFrame = 0;
 
-    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
-
-        if ((*i)->getModel() && (*i)->getModel()->isOK()) {
-
-            sv_frame_t thisEndFrame = (*i)->getModel()->getEndFrame();
+    for (Layer *layer: m_layerStack) {
+
+        auto model = ModelById::get(layer->getModel());
+
+        if (model && model->isOK()) {
+
+            sv_frame_t thisEndFrame = model->getEndFrame();
 
             if (first || thisEndFrame > endFrame) {
                 endFrame = thisEndFrame;
@@ -1317,11 +1313,15 @@
 
     //!!! nah, this wants to always return the sr of the main model!
 
-    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
-        if ((*i)->getModel() && (*i)->getModel()->isOK()) {
-            return (*i)->getModel()->getSampleRate();
+    for (Layer *layer: m_layerStack) {
+
+        auto model = ModelById::get(layer->getModel());
+
+        if (model && model->isOK()) {
+            return model->getSampleRate();
         }
     }
+
     return 0;
 }
 
@@ -1338,52 +1338,50 @@
             continue;
         }
 
-        if (layer && layer->getModel()) {
-            Model *model = layer->getModel();
-            models.insert(model);
+        if (layer && !layer->getModel().isNone()) {
+            models.insert(layer->getModel());
         }
     }
 
     return models;
 }
 
-Model *
+ModelId
 View::getAligningModel() const
 {
     if (!m_manager ||
         !m_manager->getAlignMode() ||
-        !m_manager->getPlaybackModel()) {
-        return nullptr;
+        m_manager->getPlaybackModel().isNone()) {
+        return {};
     }
 
-    Model *anyModel = nullptr;
-    Model *alignedModel = nullptr;
-    Model *goodModel = nullptr;
-
-    for (LayerList::const_iterator i = m_layerStack.begin();
-         i != m_layerStack.end(); ++i) {
-
-        Layer *layer = *i;
+    ModelId anyModel;
+    ModelId alignedModel;
+    ModelId goodModel;
+
+    for (auto layer: m_layerStack) {
 
         if (!layer) continue;
         if (dynamic_cast<TimeRulerLayer *>(layer)) continue;
 
-        Model *model = (*i)->getModel();
+        ModelId thisId = layer->getModel();
+        auto model = ModelById::get(thisId);
         if (!model) continue;
 
-        anyModel = model;
-
-        if (model->getAlignmentReference()) {
-            alignedModel = model;
+        anyModel = thisId;
+
+        if (!model->getAlignmentReference().isNone()) {
+            alignedModel = thisId;
             if (layer->isLayerOpaque() ||
-                dynamic_cast<RangeSummarisableTimeValueModel *>(model)) {
-                goodModel = model;
+                std::dynamic_pointer_cast
+                <RangeSummarisableTimeValueModel>(model)) {
+                goodModel = thisId;
             }
         }
     }
 
-    if (goodModel) return goodModel;
-    else if (alignedModel) return alignedModel;
+    if (!goodModel.isNone()) return goodModel;
+    else if (!alignedModel.isNone()) return alignedModel;
     else return anyModel;
 }
 
@@ -1391,7 +1389,7 @@
 View::alignFromReference(sv_frame_t f) const
 {
     if (!m_manager || !m_manager->getAlignMode()) return f;
-    Model *aligningModel = getAligningModel();
+    auto aligningModel = ModelById::get(getAligningModel());
     if (!aligningModel) return f;
     return aligningModel->alignFromReference(f);
 }
@@ -1400,7 +1398,7 @@
 View::alignToReference(sv_frame_t f) const
 {
     if (!m_manager->getAlignMode()) return f;
-    Model *aligningModel = getAligningModel();
+    auto aligningModel = ModelById::get(getAligningModel());
     if (!aligningModel) return f;
     return aligningModel->alignToReference(f);
 }
@@ -1412,7 +1410,7 @@
     sv_frame_t pf = m_manager->getPlaybackFrame();
     if (!m_manager->getAlignMode()) return pf;
 
-    Model *aligningModel = getAligningModel();
+    auto aligningModel = ModelById::get(getAligningModel());
     if (!aligningModel) return pf;
 
     sv_frame_t af = aligningModel->alignFromReference(pf);
@@ -1721,25 +1719,27 @@
     QPushButton *cancel = qobject_cast<QPushButton *>(sender());
     if (!cancel) return;
 
+    Layer *layer = nullptr;
+    
     for (ProgressMap::iterator i = m_progressBars.begin();
          i != m_progressBars.end(); ++i) {
-
         if (i->second.cancel == cancel) {
-
-            Layer *layer = i->first;
-            Model *model = layer->getModel();
-
-            if (model) model->abandon();
+            layer = i->first;
+            break;
         }
     }
+
+    if (layer) {
+        emit cancelButtonPressed(layer);
+    }
 }
 
 void
-View::checkProgress(void *object)
+View::checkProgress(ModelId modelId)
 {
     if (!m_showProgress) {
 #ifdef DEBUG_PROGRESS_STUFF
-        SVCERR << "View[" << this << "]::checkProgress(" << object << "): "
+        SVCERR << "View[" << this << "]::checkProgress(" << modelId << "): "
                << "m_showProgress is off" << endl;
 #endif
         return;
@@ -1759,7 +1759,7 @@
         QProgressBar *pb = i->second.bar;
         QPushButton *cancel = i->second.cancel;
 
-        if (i->first == object) {
+        if (i->first && i->first->getModel() == modelId) {
 
             found = true;
 
@@ -1793,23 +1793,28 @@
                 m_lastError = error;
             }
 
-            Model *model = i->first->getModel();
-            RangeSummarisableTimeValueModel *wfm = 
-                dynamic_cast<RangeSummarisableTimeValueModel *>(model);
+            auto model = ModelById::get(modelId);
+            auto wfm = std::dynamic_pointer_cast
+                <RangeSummarisableTimeValueModel>(model);
 
             if (completion > 0) {
                 pb->setMaximum(100); // was 0, for indeterminate start
             }
 
             if (completion >= 100) {
-
-                //!!!
+                
                 if (wfm ||
                     (model && 
-                     (wfm = dynamic_cast<RangeSummarisableTimeValueModel *>
+                     (wfm = ModelById::getAs<RangeSummarisableTimeValueModel>
                       (model->getSourceModel())))) {
+
                     completion = wfm->getAlignmentCompletion();
 
+                    // We don't allow cancelling alignment operations
+                    // - they aren't usually all that expensive, and
+                    // it would leave things in a very uncertain state
+                    showCancelButton = false;
+
 #ifdef DEBUG_PROGRESS_STUFF
                     SVCERR << "View[" << this << "]::checkProgress(" << object << "): "
                            << "alignment completion = " << completion << endl;
@@ -2340,6 +2345,11 @@
                       (i->getEndFrame() - i->getStartFrame(), sampleRate)
                       .toText(true)))
                 .arg(i->getEndFrame() - i->getStartFrame());
+            
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 
             int sw = metrics.width(startText),
                 ew = metrics.width(endText),
--- a/view/View.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/View.h	Wed Jul 17 14:25:16 2019 +0100
@@ -27,6 +27,8 @@
 #include "base/XmlExportable.h"
 #include "base/BaseTypes.h"
 
+#include "data/model/Model.h"
+
 // #define DEBUG_VIEW_WIDGET_PAINT 1
 
 class Layer;
@@ -381,11 +383,11 @@
     double scalePenWidth(double width) const override;
     QPen scalePen(QPen pen) const override;
 
-    typedef std::set<Model *> ModelSet;
+    typedef std::set<ModelId> ModelSet;
     ModelSet getModels();
 
     //!!!
-    Model *getAligningModel() const;
+    ModelId getAligningModel() const;
     sv_frame_t alignFromReference(sv_frame_t) const;
     sv_frame_t alignToReference(sv_frame_t) const;
     sv_frame_t getAlignedPlaybackFrame() const;
@@ -406,6 +408,8 @@
 
     void layerModelChanged();
 
+    void cancelButtonPressed(Layer *);
+    
     void centreFrameChanged(sv_frame_t frame,
                             bool globalScroll,
                             PlaybackFollowMode followMode);
@@ -415,10 +419,10 @@
     void contextHelpChanged(const QString &);
 
 public slots:
-    virtual void modelChanged();
-    virtual void modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame);
-    virtual void modelCompletionChanged();
-    virtual void modelAlignmentCompletionChanged();
+    virtual void modelChanged(ModelId);
+    virtual void modelChangedWithin(ModelId, sv_frame_t startFrame, sv_frame_t endFrame);
+    virtual void modelCompletionChanged(ModelId);
+    virtual void modelAlignmentCompletionChanged(ModelId);
     virtual void modelReplaced();
     virtual void layerParametersChanged();
     virtual void layerParameterRangesChanged();
@@ -502,7 +506,7 @@
 
     void movePlayPointer(sv_frame_t f);
 
-    void checkProgress(void *object);
+    void checkProgress(ModelId);
     int getProgressBarWidth() const; // if visible
 
     int effectiveDevicePixelRatio() const;
--- a/view/ViewManager.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/ViewManager.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -35,7 +35,6 @@
     m_globalCentreFrame(0),
     m_globalZoom(ZoomLevel::FramesPerPixel, 1024),
     m_playbackFrame(0),
-    m_playbackModel(nullptr),
     m_mainModelSampleRate(0),
     m_lastLeft(0), 
     m_lastRight(0),
@@ -194,14 +193,14 @@
     }
 }
 
-Model *
+ModelId
 ViewManager::getPlaybackModel() const
 {
     return m_playbackModel;
 }
 
 void
-ViewManager::setPlaybackModel(Model *model)
+ViewManager::setPlaybackModel(ModelId model)
 {
     m_playbackModel = model;
 }
@@ -212,10 +211,14 @@
 #ifdef DEBUG_VIEW_MANAGER
     cerr << "ViewManager::alignPlaybackFrameToReference(" << frame << "): playback model is " << m_playbackModel << endl;
 #endif
-    if (!m_playbackModel || !m_alignMode) {
+    if (m_playbackModel.isNone() || !m_alignMode) {
         return frame;
     } else {
-        sv_frame_t f = m_playbackModel->alignToReference(frame);
+        auto playbackModel = ModelById::get(m_playbackModel);
+        if (!playbackModel) {
+            return frame;
+        } 
+        sv_frame_t f = playbackModel->alignToReference(frame);
 #ifdef DEBUG_VIEW_MANAGER
         cerr << "aligned frame = " << f << endl;
 #endif
@@ -229,10 +232,14 @@
 #ifdef DEBUG_VIEW_MANAGER
     cerr << "ViewManager::alignReferenceToPlaybackFrame(" << frame << "): playback model is " << m_playbackModel << endl;
 #endif
-    if (!m_playbackModel || !m_alignMode) {
+    if (m_playbackModel.isNone() || !m_alignMode) {
         return frame;
     } else {
-        sv_frame_t f = m_playbackModel->alignFromReference(frame);
+        auto playbackModel = ModelById::get(m_playbackModel);
+        if (!playbackModel) {
+            return frame;
+        } 
+        sv_frame_t f = playbackModel->alignFromReference(frame);
 #ifdef DEBUG_VIEW_MANAGER
         cerr << "aligned frame = " << f << endl;
 #endif
--- a/view/ViewManager.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/view/ViewManager.h	Wed Jul 17 14:25:16 2019 +0100
@@ -29,6 +29,8 @@
 #include "base/BaseTypes.h"
 #include "base/ZoomLevel.h"
 
+#include "data/model/Model.h"
+
 class AudioPlaySource;
 class AudioRecordTarget;
 class Model;
@@ -93,8 +95,8 @@
     sv_frame_t getPlaybackFrame() const; // the set method is a slot
 
     // Only meaningful in solo mode, and used for optional alignment feature
-    Model *getPlaybackModel() const;
-    void setPlaybackModel(Model *);
+    ModelId getPlaybackModel() const;
+    void setPlaybackModel(ModelId);
 
     sv_frame_t alignPlaybackFrameToReference(sv_frame_t) const override;
     sv_frame_t alignReferenceToPlaybackFrame(sv_frame_t) const override;
@@ -347,7 +349,7 @@
     sv_frame_t m_globalCentreFrame;
     ZoomLevel m_globalZoom;
     mutable sv_frame_t m_playbackFrame;
-    Model *m_playbackModel; //!!!
+    ModelId m_playbackModel;
     sv_samplerate_t m_mainModelSampleRate;
 
     float m_lastLeft;
--- a/widgets/AudioDial.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/AudioDial.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -132,7 +132,7 @@
         
     QColor knobColor(m_knobColor);
     if (knobColor == Qt::black) {
-        knobColor = palette().window().color().light(150);
+        knobColor = palette().window().color().lighter(150);
     }
     bool knobIsDark =
         (knobColor.red() + knobColor.green() + knobColor.blue() <= 384);
@@ -183,9 +183,9 @@
     int darkWidth = (width-2*indent) * 3 / 4;
     while (darkWidth) {
         if (knobIsDark) {
-            c = c.dark(102);
+            c = c.darker(102);
         } else {
-            c = c.light(102);
+            c = c.lighter(102);
         }
         pen.setColor(c);
         paint.setPen(pen);
@@ -238,9 +238,9 @@
 
     int shadowAngle = -720;
     if (knobIsDark) {
-        c = knobColor.light();
+        c = knobColor.lighter();
     } else {
-        c = knobColor.dark();
+        c = knobColor.darker();
     }
     for (int arc = 120; arc < 2880; arc += 240) {
         pen.setColor(c);
@@ -250,9 +250,9 @@
         paint.drawArc(indent, indent,
                       width-2*indent, width-2*indent, shadowAngle - arc, 240);
         if (knobIsDark) {
-            c = c.dark(110);
+            c = c.darker(110);
         } else {
-            c = c.light(110);
+            c = c.lighter(110);
         }
     }
 
@@ -266,7 +266,7 @@
         int arc = i * 240 + 120;
         paint.drawArc(scale/2, scale/2,
                       width-scale, width-scale, shadowAngle + arc, 240);
-        c = c.light(110);
+        c = c.lighter(110);
     }
     c = palette().shadow().color();
     for (int i = 0; i < 12; ++i) {
@@ -275,7 +275,7 @@
         int arc = i * 240 + 120;
         paint.drawArc(scale/2, scale/2,
                       width-scale, width-scale, shadowAngle - arc, 240);
-        c = c.light(110);
+        c = c.lighter(110);
     }
 
     // Scale ends...
@@ -310,9 +310,9 @@
     c = notchColor;
     if (isEnabled()) {
         if (knobIsDark) {
-            c = c.light(130);
+            c = c.lighter(130);
         } else {
-            c = c.dark(130);
+            c = c.darker(130);
         }
     }
     pen.setColor(c);
--- a/widgets/InteractiveFileFinder.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/InteractiveFileFinder.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -388,7 +388,7 @@
     dialog.setDirectory(lastPath);
     dialog.setAcceptMode(QFileDialog::AcceptSave);
     dialog.setFileMode(QFileDialog::AnyFile);
-    dialog.setConfirmOverwrite(false); // we'll do that
+    dialog.setOption(QFileDialog::DontConfirmOverwrite, true); // we'll do that
     
     QString defaultSuffix;
     if (type == SessionFile) {
--- a/widgets/KeyReference.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/KeyReference.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -21,7 +21,7 @@
 #include <QVBoxLayout>
 #include <QDialogButtonBox>
 #include <QApplication>
-#include <QDesktopWidget>
+#include <QScreen>
 
 KeyReference::KeyReference() :
     m_text(nullptr),
@@ -185,9 +185,9 @@
     layout->addWidget(bb);
 
     m_dialog->show();
-    
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
+
+    QScreen *screen = QGuiApplication::primaryScreen();
+    QRect available = screen->availableGeometry();
 
     int width = available.width() * 3 / 5;
     int height = available.height() * 2 / 3;
--- a/widgets/LEDButton.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/LEDButton.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -49,7 +49,7 @@
     QColor col(Qt::green);
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
-    d->offcolor = col.dark(300);
+    d->offcolor = col.darker(300);
     
     setColor(col);
 }
@@ -61,7 +61,7 @@
 {
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
-    d->offcolor = col.dark(300);
+    d->offcolor = col.darker(300);
 
     setColor(col);
 }
@@ -72,7 +72,7 @@
 {
     d = new LEDButton::LEDButtonPrivate;
     d->dark_factor = 300;
-    d->offcolor = col.dark(300);
+    d->offcolor = col.darker(300);
 
     setColor(col);
 }
@@ -160,7 +160,7 @@
 
     // Now draw the bright spot on the LED:
     while (light_width) {
-        color = color.light( light_quote );                      // make color lighter
+        color = color.lighter( light_quote );                      // make color lighter
         pen.setColor( color );                                   // set color as pen color
         paint.setPen( pen );                                     // select the pen for drawing
         paint.drawEllipse( pos, pos, light_width, light_width ); // draw the ellipse (circle)
@@ -197,7 +197,7 @@
         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
+        color = color.darker(110); //FIXME: this should somehow use the contrast value
     }        // end for ( angle = 720; angle < 6480; angle += 160 )
 
     paint.end();
@@ -238,7 +238,7 @@
 {
     if(led_color!=col) {
         led_color = col;
-        d->offcolor = col.dark(d->dark_factor);
+        d->offcolor = col.darker(d->dark_factor);
         update();
     }
 }
@@ -248,7 +248,7 @@
 {
     if (d->dark_factor != darkfactor) {
         d->dark_factor = darkfactor;
-        d->offcolor = led_color.dark(darkfactor);
+        d->offcolor = led_color.darker(darkfactor);
         update();
     }
 }
--- a/widgets/LayerTree.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/LayerTree.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -77,7 +77,7 @@
 void
 ModelMetadataModel::rebuildModelSet()
 {
-    std::set<Model *> unfound = m_models;
+    std::set<ModelId> unfound = m_models;
 
     for (int i = 0; i < m_stack->getPaneCount(); ++i) {
 
@@ -89,26 +89,23 @@
             Layer *layer = pane->getLayer(j);
             if (!layer) continue;
 
-            Model *model = layer->getModel();
-            if (!model) continue;
+            ModelId modelId = layer->getModel();
+            if (modelId.isNone()) continue;
 
             if (m_waveModelsOnly) {
-                if (!dynamic_cast<WaveFileModel *>(model)) continue;
+                if (!ModelById::getAs<WaveFileModel>(modelId)) continue;
             }
 
-            if (m_models.find(model) == m_models.end()) {
-                connect(model, SIGNAL(aboutToBeDeleted()),
-                        this, SLOT(rebuildModelSet()));
-                m_models.insert(model);
+            if (m_models.find(modelId) == m_models.end()) {
+                m_models.insert(modelId);
             } else {
-                unfound.erase(model);
+                unfound.erase(modelId);
             }
         }
     }
 
-    for (std::set<Model *>::iterator i = unfound.begin();
-         i != unfound.end(); ++i) {
-        m_models.erase(*i);
+    for (ModelId m: unfound) {
+        m_models.erase(m);
     }
 
     SVDEBUG << "ModelMetadataModel::rebuildModelSet: " << m_models.size() << " models" << endl;
@@ -173,11 +170,12 @@
     int row = index.row(), col = index.column();
 
     //!!! not exactly the ideal use of a std::set
-    std::set<Model *>::iterator itr = m_models.begin();
+    std::set<ModelId>::iterator itr = m_models.begin();
     for (int i = 0; i < row && itr != m_models.end(); ++i, ++itr);
     if (itr == m_models.end()) return QVariant();
 
-    Model *model = *itr;
+    auto model = ModelById::get(*itr);
+    if (!model) return QVariant();
 
     if (role != Qt::DisplayRole) {
         if (m_waveModelsOnly && col == m_modelNameColumn &&
@@ -298,9 +296,9 @@
         for (int j = 0; j < pane->getLayerCount(); ++j) {
             Layer *layer = pane->getLayer(j);
             if (!layer) continue;
-            PlayParameters *params = layer->getPlayParameters();
+            auto params = layer->getPlayParameters();
             if (!params) continue;
-            connect(params, SIGNAL(playAudibleChanged(bool)),
+            connect(params.get(), SIGNAL(playAudibleChanged(bool)),
                     this, SLOT(playParametersAudibilityChanged(bool)));
         }
     }
@@ -380,7 +378,7 @@
         for (int j = 0; j < pane->getLayerCount(); ++j) {
             Layer *layer = pane->getLayer(j);
             if (!layer) continue;
-            if (layer->getPlayParameters() == params) {
+            if (layer->getPlayParameters().get() == params) { // ugh
                 SVDEBUG << "LayerTreeModel::playParametersAudibilityChanged("
                           << params << "," << a << "): row " << pane->getLayerCount() - j - 1 << ", col " << 2 << endl;
 
@@ -436,7 +434,7 @@
                 }
             } else if (col == m_layerPlayedColumn) {
                 if (role == Qt::CheckStateRole) {
-                    PlayParameters *params = layer->getPlayParameters();
+                    auto params = layer->getPlayParameters();
                     if (params) return QVariant(params->isPlayMuted() ?
                                                 Qt::Unchecked : Qt::Checked);
                     else return QVariant();
@@ -444,7 +442,7 @@
                     return QVariant(Qt::AlignHCenter);
                 }
             } else if (col == m_modelNameColumn) {
-                Model *model = layer->getModel();
+                auto model = ModelById::get(layer->getModel());
                 if (model && role == Qt::DisplayRole) {
                     return QVariant(model->objectName());
                 }
@@ -477,7 +475,7 @@
         }
     } else if (col == m_layerPlayedColumn) {
         if (role == Qt::CheckStateRole) {
-            PlayParameters *params = layer->getPlayParameters();
+            auto params = layer->getPlayParameters();
             if (params) {
                 params->setPlayMuted(value.toInt() == Qt::Unchecked);
                 emit dataChanged(index, index);
--- a/widgets/LayerTree.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/LayerTree.h	Wed Jul 17 14:25:16 2019 +0100
@@ -19,6 +19,8 @@
 
 #include <QAbstractItemModel>
 
+#include "data/model/Model.h"
+
 #include <set>
 
 class PaneStack;
@@ -26,7 +28,6 @@
 class Pane;
 class Layer;
 class PropertyContainer;
-class Model;
 
 class ModelMetadataModel : public QAbstractItemModel
 {
@@ -73,7 +74,7 @@
     int m_modelSourceColumn;
     int m_columnCount;
 
-    std::set<Model *> m_models;
+    std::set<ModelId> m_models;
 };
 
 class LayerTreeModel : public QAbstractItemModel
--- a/widgets/LayerTreeDialog.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/LayerTreeDialog.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -25,7 +25,7 @@
 #include <QDialogButtonBox>
 #include <QHeaderView>
 #include <QApplication>
-#include <QDesktopWidget>
+#include <QScreen>
 
 LayerTreeDialog::LayerTreeDialog(PaneStack *stack, QWidget *parent) :
     QDialog(parent),
@@ -89,8 +89,8 @@
     grid->addWidget(bb, 2, 0);
     grid->setRowStretch(2, 0);
     
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
+    QScreen *screen = QGuiApplication::primaryScreen();
+    QRect available = screen->availableGeometry();
 
     int width = available.width() / 2;
     int height = available.height() / 3;
--- a/widgets/ModelDataTableDialog.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/ModelDataTableDialog.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -30,13 +30,13 @@
 #include <QDialogButtonBox>
 #include <QHeaderView>
 #include <QApplication>
-#include <QDesktopWidget>
+#include <QScreen>
 #include <QAction>
 #include <QToolBar>
 
 #include <iostream>
 
-ModelDataTableDialog::ModelDataTableDialog(TabularModel *model,
+ModelDataTableDialog::ModelDataTableDialog(ModelId tabularModelId,
                                            QString title, QWidget *parent) :
     QMainWindow(parent),
     m_currentRow(0),
@@ -119,7 +119,7 @@
     m_tableView->setSortingEnabled(true);
     m_tableView->sortByColumn(0, Qt::AscendingOrder);
 
-    m_table = new ModelDataTableModel(model);
+    m_table = new ModelDataTableModel(tabularModelId);
     m_tableView->setModel(m_table);
 
     m_tableView->horizontalHeader()->setStretchLastSection(true);
@@ -144,8 +144,8 @@
     grid->addWidget(bb, 2, 0);
     grid->setRowStretch(2, 0);
     
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
+    QScreen *screen = QGuiApplication::primaryScreen();
+    QRect available = screen->availableGeometry();
 
     int width = available.width() / 3;
     int height = available.height() / 2;
--- a/widgets/ModelDataTableDialog.h	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/ModelDataTableDialog.h	Wed Jul 17 14:25:16 2019 +0100
@@ -20,7 +20,8 @@
 
 #include "base/BaseTypes.h"
 
-class TabularModel;
+#include "data/model/Model.h"
+
 class ModelDataTableModel;
 class QTableView;
 class QModelIndex;
@@ -33,7 +34,7 @@
     Q_OBJECT
     
 public:
-    ModelDataTableDialog(TabularModel *model,
+    ModelDataTableDialog(ModelId tabularModelId,
                          QString title, QWidget *parent =0);
     ~ModelDataTableDialog();
 
--- a/widgets/Panner.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/Panner.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -162,7 +162,7 @@
     QPainter paint(this);
     paint.setRenderHint(QPainter::Antialiasing, false);
 
-    QColor bg(palette().background().color());
+    QColor bg(palette().window().color());
     bg.setAlpha(m_backgroundAlpha);
 
     int penWidth = WidgetScale::scalePixelSize(1);
--- a/widgets/PluginReviewDialog.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/PluginReviewDialog.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -19,7 +19,7 @@
 #include <QDialogButtonBox>
 #include <QFileInfo>
 #include <QHeaderView>
-#include <QDesktopWidget>
+#include <QScreen>
 #include <QApplication>
 
 #include "plugin/FeatureExtractionPluginFactory.h"
@@ -129,8 +129,8 @@
     int twidth = m_table->horizontalHeader()->length();
     int theight = m_table->verticalHeader()->length();
     
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
+    QScreen *screen = QGuiApplication::primaryScreen();
+    QRect available = screen->availableGeometry();
 
     int width = std::min(twidth + 30, (available.width() * 3) / 4);
     int height = std::min(theight + 30, (available.height() * 3) / 4);
--- a/widgets/PropertyBox.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/PropertyBox.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -142,7 +142,7 @@
                 this, SLOT(populateViewPlayFrame()));
     }
 
-    PlayParameters *params = m_container->getPlayParameters();
+    auto params = m_container->getPlayParameters();
     if (!params && !layer) return;
 
     m_viewPlayFrame = new QFrame;
@@ -175,7 +175,7 @@
                 this, SLOT(mouseEnteredWidget()));
         connect(m_playButton, SIGNAL(mouseLeft()),
                 this, SLOT(mouseLeftWidget()));
-        connect(params, SIGNAL(playAudibleChanged(bool)),
+        connect(params.get(), SIGNAL(playAudibleChanged(bool)),
                 this, SLOT(playAudibleChanged(bool)));
 
         LevelPanToolButton *levelPan = new LevelPanToolButton;
@@ -186,9 +186,9 @@
                 this, SLOT(playGainControlChanged(float)));
         connect(levelPan, SIGNAL(panChanged(float)),
                 this, SLOT(playPanControlChanged(float)));
-        connect(params, SIGNAL(playGainChanged(float)),
+        connect(params.get(), SIGNAL(playGainChanged(float)),
                 levelPan, SLOT(setLevel(float)));
-        connect(params, SIGNAL(playPanChanged(float)),
+        connect(params.get(), SIGNAL(playPanChanged(float)),
                 levelPan, SLOT(setPan(float)));
         connect(levelPan, SIGNAL(mouseEntered()),
                 this, SLOT(mouseEnteredWidget()));
@@ -674,7 +674,7 @@
 void
 PropertyBox::playAudibleButtonChanged(bool audible)
 {
-    PlayParameters *params = m_container->getPlayParameters();
+    auto params = m_container->getPlayParameters();
     if (!params) return;
 
     if (params->isPlayAudible() != audible) {
@@ -690,7 +690,7 @@
 {
     QObject *obj = sender();
 
-    PlayParameters *params = m_container->getPlayParameters();
+    auto params = m_container->getPlayParameters();
     if (!params) return;
 
     if (params->getPlayGain() != gain) {
@@ -708,7 +708,7 @@
 {
     QObject *obj = sender();
 
-    PlayParameters *params = m_container->getPlayParameters();
+    auto params = m_container->getPlayParameters();
     if (!params) return;
 
     if (params->getPlayPan() != pan) {
@@ -724,7 +724,7 @@
 void
 PropertyBox::editPlayParameters()
 {
-    PlayParameters *params = m_container->getPlayParameters();
+    auto params = m_container->getPlayParameters();
     if (!params) return;
 
     QString clip = params->getPlayClipId();
@@ -774,7 +774,7 @@
 void
 PropertyBox::playClipChanged(QString id)
 {
-    PlayParameters *params = m_container->getPlayParameters();
+    auto params = m_container->getPlayParameters();
     if (!params) return;
 
     params->setPlayClipId(id);
@@ -823,7 +823,7 @@
 
     } else if (wname == "playParamButton") {
  
-        PlayParameters *params = m_container->getPlayParameters();
+        auto params = m_container->getPlayParameters();
         if (params) {
             help = tr("Change sound used for playback (currently \"%1\")")
                 .arg(params->getPlayClipId());
--- a/widgets/TextAbbrev.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/TextAbbrev.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -38,6 +38,11 @@
 int
 TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
 {
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     int width = metrics.width(ellipsis);
     return width * 2;
 }
--- a/widgets/Thumbwheel.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/Thumbwheel.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -466,7 +466,7 @@
 
     QPainter paint(&m_cache);
     paint.setClipRect(m_cache.rect());
-    paint.fillRect(subclip, palette().background().color());
+    paint.fillRect(subclip, palette().window().color());
 
     paint.setRenderHint(QPainter::Antialiasing, true);
 
@@ -564,7 +564,7 @@
         }
 
         paint.setPen(fc);
-        paint.setBrush(palette().background().color());
+        paint.setBrush(palette().window().color());
 
         if (m_orientation == Qt::Horizontal) {
             paint.drawRect(QRectF(x0, bw, x1 - x0, h - bw*2));
--- a/widgets/TransformFinder.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/TransformFinder.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -26,7 +26,7 @@
 #include <QDialogButtonBox>
 #include <QScrollArea>
 #include <QApplication>
-#include <QDesktopWidget>
+#include <QScreen>
 #include <QTimer>
 #include <QAction>
 
@@ -92,8 +92,8 @@
     connect(down, SIGNAL(triggered()), this, SLOT(down()));
     addAction(down);
 
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
+    QScreen *screen = QApplication::primaryScreen();
+    QRect available = screen->availableGeometry();
 
     int width = available.width() / 2;
     int height = available.height() / 2;
--- a/widgets/WindowShapePreview.cpp	Thu Jun 13 15:35:01 2019 +0100
+++ b/widgets/WindowShapePreview.cpp	Wed Jul 17 14:25:16 2019 +0100
@@ -113,6 +113,11 @@
     path.addRect(0, 0, w, h + 1);
     timePainter.drawPath(path);
 
+    // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
+    // replacement (horizontalAdvance) was only added in Qt 5.11
+    // which is too new for us
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
     QFont font;
     font.setPixelSize(int(10 * scaleRatio));
     font.setItalic(true);