changeset 1471:f2525e6cbdf1 by-id

More layer updates
author Chris Cannam
date Mon, 01 Jul 2019 14:17:13 +0100
parents 696e569ff21b
children dbff4b290bf0
files layer/Colour3DPlotLayer.cpp layer/Colour3DPlotRenderer.cpp layer/FlexiNoteLayer.cpp layer/HorizontalFrequencyScale.cpp layer/ImageLayer.cpp layer/LayerFactory.cpp layer/LayerFactory.h layer/LinearColourScale.cpp layer/LinearNumericalScale.cpp layer/LogColourScale.cpp layer/LogNumericalScale.cpp layer/NoteLayer.cpp layer/PaintAssistant.cpp layer/PianoScale.cpp layer/RegionLayer.cpp layer/SpectrogramLayer.cpp layer/SpectrumLayer.cpp layer/TextLayer.cpp layer/TimeInstantLayer.cpp layer/TimeRulerLayer.cpp layer/TimeValueLayer.cpp layer/WaveformLayer.cpp
diffstat 22 files changed, 494 insertions(+), 334 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/Colour3DPlotLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -143,32 +143,33 @@
 void
 Colour3DPlotLayer::setModel(ModelId modelId)
 {
-    SVDEBUG << "Colour3DPlotLayer::setModel(" << modelId << ")" << endl;
-
+    auto newModel = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not a DenseThreeDimensionalModel");
+    }
+    
     if (m_model == modelId) return;
-    
-    auto model = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
-    if (!model) throw std::logic_error("Not a DenseThreeDimensionalModel");
-    
-//!!!    const DenseThreeDimensionalModel *oldModel = m_model;
     m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+        connectSignals(m_model);
 
-    connect(model.get(), SIGNAL(modelChanged()),
-            this, SLOT(handleModelChanged()));
-    connect(model.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
-            this, SLOT(handleModelChangedWithin(sv_frame_t, sv_frame_t)));
+        connect(newModel.get(), SIGNAL(modelChanged()),
+                this, SLOT(handleModelChanged()));
+        connect(newModel.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+                this, SLOT(handleModelChangedWithin(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();
@@ -900,6 +901,11 @@
     auto model = ModelById::getAs<DenseThreeDimensionalModel>(m_model);
     if (!model) return 0;
 
+    // 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;
@@ -959,18 +965,20 @@
 
         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();
     }
--- a/layer/Colour3DPlotRenderer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/Colour3DPlotRenderer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -531,6 +531,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));
--- a/layer/FlexiNoteLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/FlexiNoteLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -74,14 +74,18 @@
 void
 FlexiNoteLayer::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;
-    
-    auto model = ModelById::getAs<NoteModel>(modelId);
-    if (!model) throw std::logic_error("Not a NoteModel");
-
     m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+        connectSignals(m_model);
+    }
 
     emit modelReplaced();
 }
--- a/layer/HorizontalFrequencyScale.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/HorizontalFrequencyScale.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/ImageLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -67,14 +67,18 @@
 void
 ImageLayer::setModel(ModelId modelId)
 {
+    auto newModel = ModelById::getAs<ImageModel>(modelId);
+    
+    if (!modelId.isNone() && !newModel) {
+        throw std::logic_error("Not an ImageModel");
+    }
+    
     if (m_model == modelId) return;
-    
-    auto model = ModelById::getAs<ImageModel>(modelId);
-    if (!model) throw std::logic_error("Not an ImageModel");
-    
     m_model = modelId;
 
-    connectSignals(m_model);
+    if (newModel) {
+        connectSignals(m_model);
+    }
 
     emit modelReplaced();
 }
@@ -355,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;
--- a/layer/LayerFactory.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/LayerFactory.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/LayerFactory.h	Mon Jul 01 14:17:13 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,12 @@
 
 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);
+        auto model = ModelById::getAs<ModelClass>(modelId);
         if (!model) return false;
-        layer->setModel(model);
+        layer->setModel(modelId);
         return true;
     }
 
--- a/layer/LinearColourScale.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/LinearColourScale.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/LinearNumericalScale.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/LogColourScale.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/LogNumericalScale.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/NoteLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -48,7 +48,6 @@
 
 NoteLayer::NoteLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_dragPointX(0),
     m_dragPointY(0),
@@ -74,15 +73,21 @@
 }
 
 void
-NoteLayer::setModel(ModelId 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;
 
@@ -126,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 "";
 }
 
@@ -147,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 {
@@ -182,8 +189,9 @@
     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();
         }
@@ -222,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";
@@ -233,7 +242,9 @@
     } else unit = getScaleUnits();
 
     if (m_verticalScale == MIDIRangeScale ||
-        m_verticalScale == LogScale) logarithmic = true;
+        m_verticalScale == LogScale) {
+        logarithmic = true;
+    }
 
     return true;
 }
@@ -241,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);
@@ -250,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;
@@ -272,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) {
@@ -296,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;
 }
@@ -306,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;
@@ -327,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;
@@ -378,7 +386,7 @@
 RangeMapper *
 NoteLayer::getNewVerticalZoomRangeMapper() const
 {
-    if (!m_model) return nullptr;
+    if (m_model.isNone()) return nullptr;
     
     RangeMapper *mapper;
 
@@ -401,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 {};
@@ -424,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;
@@ -449,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");
@@ -469,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;
         }
 
@@ -484,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;
 
@@ -538,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);
     }
 
@@ -549,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));
@@ -559,7 +571,7 @@
     }    
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -578,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();
@@ -586,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)));
@@ -689,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);
@@ -707,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());
@@ -716,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;
@@ -747,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;
         }
 
@@ -761,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,
@@ -769,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,
@@ -785,7 +806,7 @@
 int
 NoteLayer::getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &paint) const
 {
-    if (!m_model) {
+    if (m_model.isNone()) {
         return 0;
     }
 
@@ -803,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;
@@ -843,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());
 
@@ -855,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;
@@ -866,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());
 
@@ -895,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;
@@ -904,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;
 
@@ -924,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;
 
@@ -933,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);
 
@@ -947,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;
@@ -970,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;
@@ -979,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);
@@ -999,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) {
 
@@ -1026,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;
@@ -1034,7 +1065,7 @@
 //    Event note = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowDuration |
          ItemEditDialog::ShowValue |
@@ -1058,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);
@@ -1074,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);
@@ -1095,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());
@@ -1125,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);
@@ -1143,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())));
@@ -1157,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();
 
@@ -1181,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) {
@@ -1205,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;
@@ -1218,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);
             }
@@ -1241,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) {
 
@@ -1249,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/PaintAssistant.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/PaintAssistant.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/PianoScale.cpp	Mon Jul 01 14:17:13 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	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/RegionLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -46,7 +46,6 @@
 
 RegionLayer::RegionLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_dragPointX(0),
     m_dragPointY(0),
@@ -72,25 +71,35 @@
 }
 
 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();
 }
 
@@ -165,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 {
@@ -214,8 +224,9 @@
     } 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();
         }
@@ -265,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;
@@ -288,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;
@@ -301,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;
 }
@@ -314,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 {};
@@ -337,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;
@@ -360,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()) {
@@ -378,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");
@@ -401,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;
         }
 
@@ -416,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;
 
@@ -449,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);
     }
 
@@ -460,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));
@@ -477,7 +497,7 @@
     
     Event left;
     bool haveLeft = false;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame, [](Event) { return true; }, EventSeries::Backward, left)) {
         haveLeft = true;
     }
@@ -489,7 +509,7 @@
 
     Event right;
     bool haveRight = false;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame, [](Event) { return true; }, EventSeries::Forward, right)) {
         haveRight = true;
     }
@@ -523,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);
     }
 
@@ -531,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) {
@@ -547,7 +568,7 @@
 
     matchvalue = ref.getValue();
     
-    found = m_model->getNearestEventMatching
+    found = model->getNearestEventMatching
         (frame,
          [matchvalue](Event e) {
              double epsilon = 0.0001;
@@ -567,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 "";
 }
 
@@ -578,6 +600,9 @@
     max = 0.0;
     log = false;
 
+    auto model = ModelById::getAs<RegionModel>(m_model);
+    if (!model) return;
+
     QString queryUnits;
     queryUnits = getScaleUnits();
 
@@ -585,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;
 
@@ -611,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);
@@ -816,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);
@@ -828,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;
 
@@ -838,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;
@@ -886,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;
         }
 
@@ -925,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, 
@@ -934,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,
@@ -1017,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;
@@ -1039,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;
@@ -1082,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());
 
@@ -1094,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();
@@ -1106,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());
@@ -1137,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;
@@ -1148,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;
 
@@ -1169,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;
 
@@ -1179,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);
 
@@ -1192,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;
@@ -1217,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;
@@ -1226,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 ...
@@ -1238,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"));
     }
 
@@ -1253,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) {
 
@@ -1281,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 |
@@ -1308,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);
@@ -1322,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) {
@@ -1346,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());
@@ -1377,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) {
@@ -1400,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())));
@@ -1413,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();
 
@@ -1437,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) {
@@ -1461,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;
@@ -1474,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/SpectrogramLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/SpectrogramLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -208,24 +208,27 @@
 }
 
 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()),
+                this, SLOT(cacheInvalid()));
+        connect(newModel.get(), SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
+                this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t)));
+    }
+    
     emit modelReplaced();
 }
 
--- a/layer/SpectrumLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/SpectrumLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -64,14 +64,22 @@
 }
 
 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;
+    if (m_originModel == modelId) return;
+    m_originModel = modelId;
 
-    m_originModel = model;
-
+    if (newModel) {
+        //...
+    }
+    
+    //!!! todo - all of this
+    
     if (m_sliceableModel) {
         Model *m = const_cast<Model *>
             (static_cast<const Model *>(m_sliceableModel));
--- a/layer/TextLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/TextLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -52,14 +52,20 @@
 }
 
 void
-TextLayer::setModel(ModelId 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();
 }
--- a/layer/TimeInstantLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/TimeInstantLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -64,19 +64,19 @@
 void
 TimeInstantLayer::setModel(ModelId modelId)
 {
+    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;
 
-    auto newModel = ModelById::getAs<SparseOneDimensionalModel>(modelId);
-    
-    connectSignals(m_model);
-
-#ifdef DEBUG_TIME_INSTANT_LAYER
-    cerr << "TimeInstantLayer::setModel(" << modelId << ")" << endl;
-#endif
-
-    if (newModel && newModel->getRDFTypeURI().endsWith("Segment")) {
-        setPlotStyle(PlotSegmentation);
+    if (newModel) {
+        connectSignals(m_model);
+        if (newModel->getRDFTypeURI().endsWith("Segment")) {
+            setPlotStyle(PlotSegmentation);
+        }
     }
 
     emit modelReplaced();
--- a/layer/TimeRulerLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/TimeRulerLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -43,7 +43,7 @@
 }
 
 void
-TimeRulerLayer::setModel(Model *model)
+TimeRulerLayer::setModel(ModelId model)
 {
     if (m_model == model) return;
     m_model = model;
--- a/layer/TimeValueLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/TimeValueLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -82,27 +82,30 @@
 void
 TimeValueLayer::setModel(ModelId modelId)
 {
+    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;
 
-    auto newModel = ModelById::getAs<SparseOneDimensionalModel>(modelId);
+    if (newModel) {
+        
+        connectSignals(m_model);
 
-    connectSignals(m_model);
+        m_scaleMinimum = 0;
+        m_scaleMaximum = 0;
 
-    m_scaleMinimum = 0;
-    m_scaleMaximum = 0;
-
-    if (newModel && newModel->getRDFTypeURI().endsWith("Segment")) {
-        setPlotStyle(PlotSegmentation);
+        if (newModel->getRDFTypeURI().endsWith("Segment")) {
+            setPlotStyle(PlotSegmentation);
+        }
+        if (newModel->getRDFTypeURI().endsWith("Change")) {
+            setPlotStyle(PlotSegmentation);
+        }
     }
-    if (newModel && m_model->getRDFTypeURI().endsWith("Change")) {
-        setPlotStyle(PlotSegmentation);
-    }
-
-#ifdef DEBUG_TIME_VALUE_LAYER
-    cerr << "TimeValueLayer::setModel(" << model << ")" << endl;
-#endif
-
+    
     emit modelReplaced();
 }
 
--- a/layer/WaveformLayer.cpp	Fri Jun 28 17:37:22 2019 +0100
+++ b/layer/WaveformLayer.cpp	Mon Jul 01 14:17:13 2019 +0100
@@ -74,10 +74,15 @@
     auto oldModel = ModelById::getAs<RangeSummarisableTimeValueModel>(m_model);
     auto newModel = ModelById::getAs<RangeSummarisableTimeValueModel>(modelId);
 
-    if (!newModel) {
-        SVCERR << "WARNING: WaveformLayer::setModel: Model is not a RangeSummarisableTimeValueModel" << endl;
+    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 (!oldModel) {
@@ -92,12 +97,10 @@
         }
     }
 
-    m_model = modelId;
-    m_cacheValid = false;
-    if (!newModel || !newModel->isOK()) return;
-
-    connectSignals(m_model);
-
+    if (newModel) {
+        connectSignals(m_model);
+    }
+        
     emit modelReplaced();
 
     if (channelsChanged) emit layerParametersChanged();