changeset 1474:36ad3cdabf55 by-id

Further layer updates for ModelById
author Chris Cannam
date Tue, 02 Jul 2019 14:08:44 +0100
parents 886c1cd48f9d
children 84c4ddb38415
files layer/TextLayer.cpp layer/TimeInstantLayer.cpp layer/TimeRulerLayer.cpp layer/TimeValueLayer.cpp layer/WaveformLayer.cpp
diffstat 5 files changed, 326 insertions(+), 214 deletions(-) [+]
line wrap: on
line diff
--- a/layer/TextLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
+++ b/layer/TextLayer.cpp	Tue Jul 02 14:08:44 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")),
@@ -125,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());
@@ -170,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;
@@ -200,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 "";
@@ -214,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;
 
@@ -238,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);
     }
 
@@ -249,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);
@@ -259,7 +262,7 @@
     }    
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -288,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);
@@ -300,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());
@@ -313,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);
@@ -393,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());
 
@@ -408,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;
@@ -419,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());
 
@@ -438,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"),
@@ -461,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;
 
@@ -481,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;
 
@@ -490,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;
@@ -502,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;
@@ -522,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());
@@ -533,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);
@@ -550,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) {
 
@@ -577,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;
@@ -590,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);
@@ -602,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);
@@ -623,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());
@@ -651,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);
@@ -669,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())));
@@ -682,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();
 
@@ -706,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/TimeInstantLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
+++ b/layer/TimeInstantLayer.cpp	Tue Jul 02 14:08:44 2019 +0100
@@ -41,7 +41,6 @@
 
 TimeInstantLayer::TimeInstantLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_editing(false),
     m_editingPoint(0, tr("New Point")),
     m_editingCommand(nullptr),
@@ -162,7 +161,7 @@
 TimeInstantLayer::needsTextLabelHeight() const
 {
     auto model = ModelById::getAs<SparseOneDimensionalModel>(m_model);
-    if (model) return m_model->hasTextLabels();
+    if (model) return model->hasTextLabels();
     else return false;
 }
 
@@ -176,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
@@ -184,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;
@@ -213,7 +213,7 @@
     }
 
     if (have) {
-        return m_model->getEventsStartingAt(suitable);
+        return model->getEventsStartingAt(suitable);
     } else {
         return {};
     }
@@ -222,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,
@@ -241,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");
@@ -255,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;
 
@@ -277,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);
     }
 
@@ -288,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));
@@ -298,7 +301,7 @@
     }
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -313,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);
 
@@ -329,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);
     }
 
@@ -355,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;
@@ -387,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;
@@ -423,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) {
@@ -442,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
 
@@ -472,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;
@@ -494,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);
@@ -510,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);
@@ -524,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;
@@ -547,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;
 
@@ -555,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;
@@ -569,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;
@@ -591,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);
@@ -612,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);
@@ -628,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;
@@ -636,7 +654,7 @@
     Event point = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowText);
 
@@ -650,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);
@@ -663,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
@@ -684,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());
@@ -712,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);
@@ -730,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())));
@@ -743,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();
 
@@ -767,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/TimeRulerLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
+++ b/layer/TimeRulerLayer.cpp	Tue Jul 02 14:08:44 2019 +0100
@@ -36,7 +36,6 @@
 
 TimeRulerLayer::TimeRulerLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_labelHeight(LabelTop)
 {
     
@@ -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/TimeValueLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
+++ b/layer/TimeValueLayer.cpp	Tue Jul 02 14:08:44 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")),
@@ -168,7 +167,7 @@
 bool
 TimeValueLayer::needsTextLabelHeight() const
 {
-    auto model = ModelById::get(m_model);
+    auto model = ModelById::getAs<SparseTimeValueModel>(m_model);
     if (!model) return false;
     return m_plotStyle == PlotSegmentation && model->hasTextLabels();
 }
@@ -176,7 +175,8 @@
 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 "";
 }
 
@@ -213,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());
         }
@@ -279,8 +280,9 @@
     } 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();
         }
@@ -356,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);
 
@@ -396,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;
@@ -422,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) {
@@ -447,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;
@@ -457,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;
@@ -480,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;
@@ -532,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;
 
@@ -555,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
@@ -565,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;
@@ -594,7 +604,7 @@
     }
 
     if (have) {
-        return m_model->getEventsStartingAt(suitable);
+        return model->getEventsStartingAt(suitable);
     } else {
         return {};
     }
@@ -603,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,
@@ -622,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");
@@ -636,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();
@@ -677,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);
     }
 
@@ -688,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));
@@ -698,7 +711,7 @@
     }
 
     Event e;
-    if (m_model->getNearestEventMatching
+    if (model->getNearestEventMatching
         (frame,
          [](Event) { return true; },
          snap == SnapLeft ? EventSeries::Backward : EventSeries::Forward,
@@ -716,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) {
@@ -738,7 +752,7 @@
 
     matchvalue = ref.getValue();
     
-    found = m_model->getNearestEventMatching
+    found = model->getNearestEventMatching
         (frame,
          [matchvalue](Event e) {
              double epsilon = 0.0001;
@@ -762,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);
         }
@@ -833,7 +850,6 @@
 bool
 TimeValueLayer::shouldAutoAlign() const
 {
-    if (!m_model) return false;
     QString unit = getScaleUnits();
     return (m_verticalScale == AutoAlignScale && unit != "");
 }
@@ -872,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);
@@ -886,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());
@@ -897,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() -
@@ -921,7 +938,7 @@
     }
 
     int w =
-        v->getXForFrame(frame0 + m_model->getResolution()) -
+        v->getXForFrame(frame0 + model->getResolution()) -
         v->getXForFrame(frame0);
 
     if (m_plotStyle == PlotStems) {
@@ -974,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) {
@@ -1098,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;
@@ -1161,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);
@@ -1200,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;
@@ -1222,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;
@@ -1278,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;
 
@@ -1311,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);
     }
@@ -1326,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;
 
@@ -1387,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;
@@ -1396,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;
@@ -1419,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;
 
@@ -1428,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;
@@ -1442,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;
@@ -1465,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);
@@ -1490,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) {
 
@@ -1517,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;
@@ -1525,7 +1558,7 @@
     Event point = *points.begin();
 
     ItemEditDialog *dialog = new ItemEditDialog
-        (m_model->getSampleRate(),
+        (model->getSampleRate(),
          ItemEditDialog::ShowTime |
          ItemEditDialog::ShowValue |
          ItemEditDialog::ShowText,
@@ -1543,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);
@@ -1556,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) {
 
@@ -1578,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());
@@ -1606,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);
@@ -1624,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())));
@@ -1638,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();
 
@@ -1662,7 +1700,7 @@
     }
 
     ChangeEventsCommand *command =
-        new ChangeEventsCommand(m_model, tr("Paste"));
+        new ChangeEventsCommand(m_model.untyped, tr("Paste"));
 
     enum ValueAvailability {
         UnknownAvailability,
@@ -1675,7 +1713,7 @@
 
     bool haveUsableLabels = false;
     Labeller labeller;
-    labeller.setSampleRate(m_model->getSampleRate());
+    labeller.setSampleRate(model->getSampleRate());
 
     if (interactive) {
 
--- a/layer/WaveformLayer.cpp	Tue Jul 02 11:49:52 2019 +0100
+++ b/layer/WaveformLayer.cpp	Tue Jul 02 14:08:44 2019 +0100
@@ -41,7 +41,6 @@
 
 WaveformLayer::WaveformLayer() :
     SingleColourLayer(),
-    m_model(nullptr),
     m_gain(1.0f),
     m_autoNormalize(false),
     m_showMeans(true),
@@ -64,7 +63,7 @@
 WaveformLayer::getZoomConstraint() const
 {
     auto model = ModelById::get(m_model);
-    if (model) return m_model->getZoomConstraint();
+    if (model) return model->getZoomConstraint();
     else return nullptr;
 }
 
@@ -114,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");
     }
 
@@ -341,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;
 }
 
@@ -379,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;
@@ -424,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;
@@ -444,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;
             
@@ -468,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;
@@ -478,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()));
@@ -490,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;
     }
   
@@ -594,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;
@@ -643,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;
         }
@@ -660,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;
@@ -672,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);
         }
     }
@@ -687,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
@@ -704,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) {
@@ -724,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;
@@ -756,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();
 
@@ -777,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];
@@ -1051,7 +1069,7 @@
         penWidth = 0.0;
     }
     
-    if (m_model->isReady()) {
+    if (model->isReady()) {
         paint->setPen(QPen(baseColour, penWidth));
     } else {
         paint->setPen(QPen(midColour, penWidth));
@@ -1154,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();
 
@@ -1163,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")
@@ -1192,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;
         
@@ -1392,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 {
@@ -1403,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;
     }