changeset 770:734ee80286c3 tony_integration

Merge from default branch
author Chris Cannam
date Fri, 09 May 2014 17:15:50 +0100
parents 336ccf8fc3f8 (diff) 8b614632568c (current diff)
children a964151832a7 5d2966b7c201
files
diffstat 34 files changed, 3019 insertions(+), 663 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/FlexiNoteLayer.cpp	Fri May 09 17:15:50 2014 +0100
@@ -0,0 +1,1824 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "FlexiNoteLayer.h"
+
+#include "data/model/Model.h"
+#include "data/model/SparseTimeValueModel.h"
+#include "base/RealTime.h"
+#include "base/Profiler.h"
+#include "base/Pitch.h"
+#include "base/LogRange.h"
+#include "base/RangeMapper.h"
+#include "ColourDatabase.h"
+#include "view/View.h"
+
+#include "PianoScale.h"
+#include "LinearNumericalScale.h"
+#include "LogNumericalScale.h"
+
+#include "data/model/FlexiNoteModel.h"
+
+#include "widgets/ItemEditDialog.h"
+#include "widgets/TextAbbrev.h"
+
+#include <QPainter>
+#include <QPainterPath>
+#include <QMouseEvent>
+#include <QTextStream>
+#include <QMessageBox>
+
+#include <iostream>
+#include <cmath>
+#include <utility>
+#include <limits> // GF: included to compile std::numerical_limits on linux
+#include <vector>
+
+
+FlexiNoteLayer::FlexiNoteLayer() :
+    SingleColourLayer(),
+
+    // m_model(0),
+    // m_editing(false),
+    // m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    // m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    // m_editingCommand(0),
+    // m_verticalScale(AutoAlignScale),
+    // m_scaleMinimum(0),
+    // m_scaleMaximum(0)
+
+    m_model(0),
+    m_editing(false),
+    m_originalPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    m_editingPoint(0, 0.0, 0, 1.f, tr("New Point")),
+    m_editingCommand(0),
+    m_verticalScale(AutoAlignScale),
+    m_editMode(DragNote),
+    m_scaleMinimum(34), 
+    m_scaleMaximum(77),
+    m_intelligentActions(true)
+{
+}
+
+void
+FlexiNoteLayer::setModel(FlexiNoteModel *model) 
+{
+    if (m_model == model) return;
+    m_model = model;
+
+    connectSignals(m_model);
+
+    // m_scaleMinimum = 0;
+    // m_scaleMaximum = 0;
+
+    emit modelReplaced();
+}
+
+Layer::PropertyList
+FlexiNoteLayer::getProperties() const
+{
+    PropertyList list = SingleColourLayer::getProperties();
+    list.push_back("Vertical Scale");
+    list.push_back("Scale Units");
+    return list;
+}
+
+QString
+FlexiNoteLayer::getPropertyLabel(const PropertyName &name) const
+{
+    if (name == "Vertical Scale") return tr("Vertical Scale");
+    if (name == "Scale Units") return tr("Scale Units");
+    return SingleColourLayer::getPropertyLabel(name);
+}
+
+Layer::PropertyType
+FlexiNoteLayer::getPropertyType(const PropertyName &name) const
+{
+    if (name == "Scale Units") return UnitsProperty;
+    if (name == "Vertical Scale") return ValueProperty;
+    return SingleColourLayer::getPropertyType(name);
+}
+
+QString
+FlexiNoteLayer::getPropertyGroupName(const PropertyName &name) const
+{
+    if (name == "Vertical Scale" || name == "Scale Units") {
+        return tr("Scale");
+    }
+    return SingleColourLayer::getPropertyGroupName(name);
+}
+
+QString
+FlexiNoteLayer::getScaleUnits() const
+{
+    if (m_model) return m_model->getScaleUnits();
+    else return "";
+}
+
+int
+FlexiNoteLayer::getPropertyRangeAndValue(const PropertyName &name,
+                                         int *min, int *max, int *deflt) const
+{
+    int val = 0;
+
+    if (name == "Vertical Scale") {
+    
+        if (min) *min = 0;
+        if (max) *max = 3;
+        if (deflt) *deflt = int(AutoAlignScale);
+    
+        val = int(m_verticalScale);
+
+    } else if (name == "Scale Units") {
+
+        if (deflt) *deflt = 0;
+        if (m_model) {
+            val = UnitDatabase::getInstance()->getUnitId
+                (getScaleUnits());
+        }
+
+    } else {
+
+        val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
+    }
+
+    return val;
+}
+
+QString
+FlexiNoteLayer::getPropertyValueLabel(const PropertyName &name,
+                                      int value) const
+{
+    if (name == "Vertical Scale") {
+        switch (value) {
+        default:
+        case 0: return tr("Auto-Align");
+        case 1: return tr("Linear");
+        case 2: return tr("Log");
+        case 3: return tr("MIDI Notes");
+        }
+    }
+    return SingleColourLayer::getPropertyValueLabel(name, value);
+}
+
+void
+FlexiNoteLayer::setProperty(const PropertyName &name, int value)
+{
+    if (name == "Vertical Scale") {
+        setVerticalScale(VerticalScale(value));
+    } else if (name == "Scale Units") {
+        if (m_model) {
+            m_model->setScaleUnits
+                (UnitDatabase::getInstance()->getUnitById(value));
+            emit modelChanged();
+        }
+    } else {
+        return SingleColourLayer::setProperty(name, value);
+    }
+}
+
+void
+FlexiNoteLayer::setVerticalScale(VerticalScale scale)
+{
+    if (m_verticalScale == scale) return;
+    m_verticalScale = scale;
+    emit layerParametersChanged();
+}
+
+bool
+FlexiNoteLayer::isLayerScrollable(const View *v) const
+{
+    QPoint discard;
+    return !v->shouldIlluminateLocalFeatures(this, discard);
+}
+
+bool
+FlexiNoteLayer::shouldConvertMIDIToHz() const
+{
+    QString unit = getScaleUnits();
+    return (unit != "Hz");
+//    if (unit == "" ||
+//        unit.startsWith("MIDI") ||
+//        unit.startsWith("midi")) return true;
+//    return false;
+}
+
+bool
+FlexiNoteLayer::getValueExtents(float &min, float &max,
+                                bool &logarithmic, QString &unit) const
+{
+    if (!m_model) return false;
+    min = m_model->getValueMinimum();
+    max = m_model->getValueMaximum();
+
+    if (shouldConvertMIDIToHz()) {
+        unit = "Hz";
+        min = Pitch::getFrequencyForPitch(lrintf(min));
+        max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+    } else unit = getScaleUnits();
+
+    if (m_verticalScale == MIDIRangeScale ||
+        m_verticalScale == LogScale) logarithmic = true;
+
+    return true;
+}
+
+bool
+FlexiNoteLayer::getDisplayExtents(float &min, float &max) const
+{
+    if (!m_model || shouldAutoAlign()) {
+//        std::cerr << "No model or shouldAutoAlign()" << std::endl;
+        return false;
+    }
+
+    if (m_verticalScale == MIDIRangeScale) {
+        min = Pitch::getFrequencyForPitch(0);
+        max = Pitch::getFrequencyForPitch(127);
+        return true;
+    }
+
+    if (m_scaleMinimum == m_scaleMaximum) {
+        min = m_model->getValueMinimum();
+        max = m_model->getValueMaximum();
+    } else {
+        min = m_scaleMinimum;
+        max = m_scaleMaximum;
+    }
+
+    if (shouldConvertMIDIToHz()) {
+        min = Pitch::getFrequencyForPitch(lrintf(min));
+        max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+    }
+
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "NoteLayer::getDisplayExtents: min = " << min << ", max = " << max << " (m_scaleMinimum = " << m_scaleMinimum << ", m_scaleMaximum = " << m_scaleMaximum << ")" << endl;
+#endif
+
+    return true;
+}
+
+bool
+FlexiNoteLayer::setDisplayExtents(float min, float max)
+{
+    if (!m_model) return false;
+
+    if (min == max) {
+        if (min == 0.f) {
+            max = 1.f;
+        } else {
+            max = min * 1.0001;
+        }
+    }
+
+    m_scaleMinimum = min;
+    m_scaleMaximum = max;
+
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "FlexiNoteLayer::setDisplayExtents: min = " << min << ", max = " << max << endl;
+#endif
+    
+    emit layerParametersChanged();
+    return true;
+}
+
+int
+FlexiNoteLayer::getVerticalZoomSteps(int &defaultStep) const
+{
+    if (shouldAutoAlign()) return 0;
+    if (!m_model) return 0;
+
+    defaultStep = 0;
+    return 100;
+}
+
+int
+FlexiNoteLayer::getCurrentVerticalZoomStep() const
+{
+    if (shouldAutoAlign()) return 0;
+    if (!m_model) return 0;
+
+    RangeMapper *mapper = getNewVerticalZoomRangeMapper();
+    if (!mapper) return 0;
+
+    float dmin, dmax;
+    getDisplayExtents(dmin, dmax);
+
+    int nr = mapper->getPositionForValue(dmax - dmin);
+
+    delete mapper;
+
+    return 100 - nr;
+}
+
+//!!! lots of duplication with TimeValueLayer
+
+void
+FlexiNoteLayer::setVerticalZoomStep(int step)
+{
+    if (shouldAutoAlign()) return;
+    if (!m_model) return;
+
+    RangeMapper *mapper = getNewVerticalZoomRangeMapper();
+    if (!mapper) return;
+    
+    float min, max;
+    bool logarithmic;
+    QString unit;
+    getValueExtents(min, max, logarithmic, unit);
+    
+    float dmin, dmax;
+    getDisplayExtents(dmin, dmax);
+
+    float newdist = mapper->getValueForPosition(100 - step);
+
+    float newmin, newmax;
+
+    if (logarithmic) {
+
+        // see SpectrogramLayer::setVerticalZoomStep
+
+        newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2;
+        newmin = newmax - newdist;
+
+//        cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
+
+    } else {
+        float dmid = (dmax + dmin) / 2;
+        newmin = dmid - newdist / 2;
+        newmax = dmid + newdist / 2;
+    }
+
+    if (newmin < min) {
+        newmax += (min - newmin);
+        newmin = min;
+    }
+    if (newmax > max) {
+        newmax = max;
+    }
+    
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "FlexiNoteLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
+#endif
+
+    setDisplayExtents(newmin, newmax);
+}
+
+RangeMapper *
+FlexiNoteLayer::getNewVerticalZoomRangeMapper() const
+{
+    if (!m_model) return 0;
+    
+    RangeMapper *mapper;
+
+    float min, max;
+    bool logarithmic;
+    QString unit;
+    getValueExtents(min, max, logarithmic, unit);
+
+    if (min == max) return 0;
+    
+    if (logarithmic) {
+        mapper = new LogRangeMapper(0, 100, min, max, unit);
+    } else {
+        mapper = new LinearRangeMapper(0, 100, min, max, unit);
+    }
+
+    return mapper;
+}
+
+FlexiNoteModel::PointList
+FlexiNoteLayer::getLocalPoints(View *v, int x) const
+{
+    if (!m_model) return FlexiNoteModel::PointList();
+
+    long frame = v->getFrameForX(x);
+
+    FlexiNoteModel::PointList onPoints =
+        m_model->getPoints(frame);
+
+    if (!onPoints.empty()) {
+        return onPoints;
+    }
+
+    FlexiNoteModel::PointList prevPoints =
+        m_model->getPreviousPoints(frame);
+    FlexiNoteModel::PointList nextPoints =
+        m_model->getNextPoints(frame);
+
+    FlexiNoteModel::PointList usePoints = prevPoints;
+
+    if (prevPoints.empty()) {
+        usePoints = nextPoints;
+    } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
+               !(nextPoints.begin()->frame > v->getEndFrame())) {
+        usePoints = nextPoints;
+    } else if (long(nextPoints.begin()->frame) - frame <
+               frame - long(prevPoints.begin()->frame)) {
+        usePoints = nextPoints;
+    }
+
+    if (!usePoints.empty()) {
+        int fuzz = 2;
+        int px = v->getXForFrame(usePoints.begin()->frame);
+        if ((px > x && px - x > fuzz) ||
+            (px < x && x - px > fuzz + 1)) {
+            usePoints.clear();
+        }
+    }
+
+    return usePoints;
+}
+
+bool
+FlexiNoteLayer::getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &p) const
+{
+    if (!m_model) return false;
+
+    long frame = v->getFrameForX(x);
+
+    FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
+    if (onPoints.empty()) return false;
+
+//    cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << endl;
+
+    int nearestDistance = -1;
+
+    for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin();
+         i != onPoints.end(); ++i) {
+        
+        int distance = getYForValue(v, (*i).value) - y;
+        if (distance < 0) distance = -distance;
+        if (nearestDistance == -1 || distance < nearestDistance) {
+            nearestDistance = distance;
+            p = *i;
+        }
+    }
+
+    return true;
+}
+
+bool
+FlexiNoteLayer::getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &p) const
+{
+    // GF: find the note that is closest to the cursor
+    if (!m_model) return false;
+
+    long frame = v->getFrameForX(x);
+
+    FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
+    if (onPoints.empty()) return false;
+
+//    std::cerr << "frame " << frame << ": " << onPoints.size() << " candidate points" << std::endl;
+
+    int nearestDistance = -1;
+
+    for (FlexiNoteModel::PointList::const_iterator i = onPoints.begin();
+         i != onPoints.end(); ++i) {
+        
+        int distance = getYForValue(v, (*i).value) - y;
+        if (distance < 0) distance = -distance;
+        if (nearestDistance == -1 || distance < nearestDistance) {
+            nearestDistance = distance;
+            p = *i;
+        }
+    }
+
+    return true;
+}
+
+QString
+FlexiNoteLayer::getFeatureDescription(View *v, QPoint &pos) const
+{
+    int x = pos.x();
+
+    if (!m_model || !m_model->getSampleRate()) return "";
+
+    FlexiNoteModel::PointList points = getLocalPoints(v, x);
+
+    if (points.empty()) {
+        if (!m_model->isReady()) {
+            return tr("In progress");
+        } else {
+            return tr("No local points");
+        }
+    }
+
+    FlexiNote note(0);
+    FlexiNoteModel::PointList::iterator i;
+
+    for (i = points.begin(); i != points.end(); ++i) {
+
+        int y = getYForValue(v, i->value);
+        int h = NOTE_HEIGHT; // GF: larger notes
+
+        if (m_model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, i->value + m_model->getValueQuantization());
+            if (h < NOTE_HEIGHT) h = NOTE_HEIGHT;
+        }
+
+        // GF: this is not quite correct
+        if (pos.y() >= y - 4 && pos.y() <= y + h) {
+            note = *i;
+            break;
+        }
+    }
+
+    if (i == points.end()) return tr("No local points");
+
+    RealTime rt = RealTime::frame2RealTime(note.frame,
+                                           m_model->getSampleRate());
+    RealTime rd = RealTime::frame2RealTime(note.duration,
+                                           m_model->getSampleRate());
+    
+    QString pitchText;
+
+    if (shouldConvertMIDIToHz()) {
+
+        int mnote = lrintf(note.value);
+        int cents = lrintf((note.value - mnote) * 100);
+        float freq = Pitch::getFrequencyForPitch(mnote, cents);
+        pitchText = tr("%1 (%2, %3 Hz)")
+            .arg(Pitch::getPitchLabel(mnote, cents))
+            .arg(mnote)
+            .arg(freq);
+
+    } else if (getScaleUnits() == "Hz") {
+
+        pitchText = tr("%1 Hz (%2, %3)")
+            .arg(note.value)
+            .arg(Pitch::getPitchLabelForFrequency(note.value))
+            .arg(Pitch::getPitchForFrequency(note.value));
+
+    } else {
+        pitchText = tr("%1 %2")
+            .arg(note.value).arg(getScaleUnits());
+    }
+
+    QString text;
+
+    if (note.label == "") {
+        text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nNo label"))
+            .arg(rt.toText(true).c_str())
+            .arg(pitchText)
+            .arg(rd.toText(true).c_str());
+    } else {
+        text = QString(tr("Time:\t%1\nPitch:\t%2\nDuration:\t%3\nLabel:\t%4"))
+            .arg(rt.toText(true).c_str())
+            .arg(pitchText)
+            .arg(rd.toText(true).c_str())
+            .arg(note.label);
+    }
+
+    pos = QPoint(v->getXForFrame(note.frame),
+                 getYForValue(v, note.value));
+    return text;
+}
+
+bool
+FlexiNoteLayer::snapToFeatureFrame(View *v, int &frame,
+                                   size_t &resolution,
+                                   SnapType snap) const
+{
+    if (!m_model) {
+        return Layer::snapToFeatureFrame(v, frame, resolution, snap);
+    }
+
+    resolution = m_model->getResolution();
+    FlexiNoteModel::PointList points;
+
+    if (snap == SnapNeighbouring) {
+    
+        points = getLocalPoints(v, v->getXForFrame(frame));
+        if (points.empty()) return false;
+        frame = points.begin()->frame;
+        return true;
+    }    
+
+    points = m_model->getPoints(frame, frame);
+    int snapped = frame;
+    bool found = false;
+
+    for (FlexiNoteModel::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        cerr << "FlexiNoteModel: point at " << i->frame << endl;
+
+        if (snap == SnapRight) {
+
+            if (i->frame > frame) {
+                snapped = i->frame;
+                found = true;
+                break;
+            } else if (i->frame + i->duration >= frame) {
+                snapped = i->frame + i->duration;
+                found = true;
+                break;
+            }
+
+        } else if (snap == SnapLeft) {
+
+            if (i->frame <= frame) {
+                snapped = i->frame;
+                found = true; // don't break, as the next may be better
+            } else {
+                break;
+            }
+
+        } else { // nearest
+
+            FlexiNoteModel::PointList::const_iterator j = i;
+            ++j;
+
+            if (j == points.end()) {
+
+                snapped = i->frame;
+                found = true;
+                break;
+
+            } else if (j->frame >= frame) {
+
+                if (j->frame - frame < frame - i->frame) {
+                    snapped = j->frame;
+                } else {
+                    snapped = i->frame;
+                }
+                found = true;
+                break;
+            }
+        }
+    }
+
+    cerr << "snapToFeatureFrame: frame " << frame << " -> snapped " << snapped << ", found = " << found << endl;
+
+    frame = snapped;
+    return found;
+}
+
+void
+FlexiNoteLayer::getScaleExtents(View *v, float &min, float &max, bool &log) const
+{
+    min = 0.0;
+    max = 0.0;
+    log = false;
+
+    QString queryUnits;
+    if (shouldConvertMIDIToHz()) queryUnits = "Hz";
+    else queryUnits = getScaleUnits();
+
+    if (shouldAutoAlign()) {
+
+        if (!v->getValueExtents(queryUnits, min, max, log)) {
+
+            min = m_model->getValueMinimum();
+            max = m_model->getValueMaximum();
+
+            if (shouldConvertMIDIToHz()) {
+                min = Pitch::getFrequencyForPitch(lrintf(min));
+                max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+            }
+
+#ifdef DEBUG_NOTE_LAYER
+            cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
+#endif
+
+        } else if (log) {
+
+            LogRange::mapRange(min, max);
+
+#ifdef DEBUG_NOTE_LAYER
+            cerr << "FlexiNoteLayer[" << this << "]::getScaleExtents: min = " << min << ", max = " << max << ", log = " << log << endl;
+#endif
+        }
+
+    } else {
+
+        getDisplayExtents(min, max);
+
+        if (m_verticalScale == MIDIRangeScale) {
+            min = Pitch::getFrequencyForPitch(0);
+            max = Pitch::getFrequencyForPitch(70);
+        } else if (shouldConvertMIDIToHz()) {
+            min = Pitch::getFrequencyForPitch(lrintf(min));
+            max = Pitch::getFrequencyForPitch(lrintf(max + 1));
+        }
+
+        if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
+            LogRange::mapRange(min, max);
+            log = true;
+        }
+    }
+
+    if (max == min) max = min + 1.0;
+}
+
+int
+FlexiNoteLayer::getYForValue(View *v, float val) const
+{
+    float min = 0.0, max = 0.0;
+    bool logarithmic = false;
+    int h = v->height();
+
+    getScaleExtents(v, min, max, logarithmic);
+
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "FlexiNoteLayer[" << this << "]::getYForValue(" << val << "): min = " << min << ", max = " << max << ", log = " << logarithmic << endl;
+#endif
+
+    if (shouldConvertMIDIToHz()) {
+        val = Pitch::getFrequencyForPitch(lrintf(val),
+                                          lrintf((val - lrintf(val)) * 100));
+#ifdef DEBUG_NOTE_LAYER
+        cerr << "shouldConvertMIDIToHz true, val now = " << val << endl;
+#endif
+    }
+
+    if (logarithmic) {
+        val = LogRange::map(val);
+#ifdef DEBUG_NOTE_LAYER
+        cerr << "logarithmic true, val now = " << val << endl;
+#endif
+    }
+
+    int y = int(h - ((val - min) * h) / (max - min)) - 1;
+#ifdef DEBUG_NOTE_LAYER
+    cerr << "y = " << y << endl;
+#endif
+    return y;
+}
+
+float
+FlexiNoteLayer::getValueForY(View *v, int y) const
+{
+    float min = 0.0, max = 0.0;
+    bool logarithmic = false;
+    int h = v->height();
+
+    getScaleExtents(v, min, max, logarithmic);
+
+    float val = min + (float(h - y) * float(max - min)) / h;
+
+    if (logarithmic) {
+        val = powf(10.f, val);
+    }
+
+    if (shouldConvertMIDIToHz()) {
+        val = Pitch::getPitchForFrequency(val);
+    }
+
+    return val;
+}
+
+bool
+FlexiNoteLayer::shouldAutoAlign() const
+{
+    if (!m_model) return false;
+    return (m_verticalScale == AutoAlignScale);
+}
+
+void
+FlexiNoteLayer::paint(View *v, QPainter &paint, QRect rect) const
+{
+    if (!m_model || !m_model->isOK()) return;
+
+    int sampleRate = m_model->getSampleRate();
+    if (!sampleRate) return;
+
+//    Profiler profiler("FlexiNoteLayer::paint", true);
+
+    int x0 = rect.left(), x1 = rect.right();
+    long frame0 = v->getFrameForX(x0);
+    long frame1 = v->getFrameForX(x1);
+
+    FlexiNoteModel::PointList points(m_model->getPoints(frame0, frame1));
+    if (points.empty()) return;
+
+    paint.setPen(getBaseQColor());
+
+    QColor brushColour(getBaseQColor());
+    brushColour.setAlpha(80);
+
+//    SVDEBUG << "FlexiNoteLayer::paint: resolution is "
+//        << m_model->getResolution() << " frames" << endl;
+
+    float min = m_model->getValueMinimum();
+    float max = m_model->getValueMaximum();
+    if (max == min) max = min + 1.0;
+
+    QPoint localPos;
+    FlexiNoteModel::Point illuminatePoint(0);
+    bool shouldIlluminate = false;
+
+    if (v->shouldIlluminateLocalFeatures(this, localPos)) {
+        shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
+                                          illuminatePoint);
+    }
+
+    paint.save();
+    paint.setRenderHint(QPainter::Antialiasing, false);
+    
+    for (FlexiNoteModel::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        const FlexiNoteModel::Point &p(*i);
+
+        int x = v->getXForFrame(p.frame);
+        int y = getYForValue(v, p.value);
+        int w = v->getXForFrame(p.frame + p.duration) - x;
+        int h = NOTE_HEIGHT; //GF: larger notes
+    
+        if (m_model->getValueQuantization() != 0.0) {
+            h = y - getYForValue(v, p.value + m_model->getValueQuantization());
+            if (h < NOTE_HEIGHT) h = NOTE_HEIGHT; //GF: larger notes
+        }
+
+        if (w < 1) w = 1;
+        paint.setPen(getBaseQColor());
+        paint.setBrush(brushColour);
+
+        // if (shouldIlluminate &&
+        //         // "illuminatePoint == p"
+        //         !FlexiNoteModel::Point::Comparator()(illuminatePoint, p) &&
+        //         !FlexiNoteModel::Point::Comparator()(p, illuminatePoint)) {
+        // 
+        //         paint.setPen(v->getForeground());
+        //         paint.setBrush(v->getForeground());
+        // 
+        //         QString vlabel = QString("%1%2").arg(p.value).arg(m_model->getScaleUnits());
+        //         v->drawVisibleText(paint, 
+        //                            x - paint.fontMetrics().width(vlabel) - 2,
+        //                            y + paint.fontMetrics().height()/2
+        //                              - paint.fontMetrics().descent(), 
+        //                            vlabel, View::OutlinedText);
+        // 
+        //         QString hlabel = RealTime::frame2RealTime
+        //             (p.frame, m_model->getSampleRate()).toText(true).c_str();
+        //         v->drawVisibleText(paint, 
+        //                            x,
+        //                            y - h/2 - paint.fontMetrics().descent() - 2,
+        //                            hlabel, View::OutlinedText);
+        // }
+    
+        paint.drawRect(x, y - h/2, w, h);
+    }
+
+    paint.restore();
+}
+
+int
+FlexiNoteLayer::getVerticalScaleWidth(View *v, bool, QPainter &paint) const
+{
+    if (!m_model || shouldAutoAlign()) {
+        return 0;
+    } else  {
+        if (m_verticalScale == LogScale || m_verticalScale == MIDIRangeScale) {
+            return LogNumericalScale().getWidth(v, paint) + 10; // for piano
+        } else {
+            return LinearNumericalScale().getWidth(v, paint);
+        }
+    }
+}
+
+void
+FlexiNoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
+{
+    if (!m_model || m_model->getPoints().empty()) return;
+
+    QString unit;
+    float min, max;
+    bool logarithmic;
+
+    int w = getVerticalScaleWidth(v, false, paint);
+    int h = v->height();
+
+    getScaleExtents(v, min, max, logarithmic);
+
+    if (logarithmic) {
+        LogNumericalScale().paintVertical(v, this, paint, 0, min, max);
+    } else {
+        LinearNumericalScale().paintVertical(v, this, paint, 0, min, max);
+    }
+    
+    if (logarithmic && (getScaleUnits() == "Hz")) {
+        PianoScale().paintPianoVertical
+            (v, paint, QRect(w - 10, 0, 10, h), 
+             LogRange::unmap(min), 
+             LogRange::unmap(max));
+        paint.drawLine(w, 0, w, h);
+    }
+        
+    if (getScaleUnits() != "") {
+        int mw = w - 5;
+        paint.drawText(5,
+                       5 + paint.fontMetrics().ascent(),
+                       TextAbbrev::abbreviate(getScaleUnits(),
+                                              paint.fontMetrics(),
+                                              mw));
+    }
+}
+
+void
+FlexiNoteLayer::drawStart(View *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
+
+    if (!m_model) return;
+
+    long frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+
+    float value = getValueForY(v, e->y());
+
+    m_editingPoint = FlexiNoteModel::Point(frame, value, 0, 0.8, tr("New Point"));
+    m_originalPoint = m_editingPoint;
+
+    if (m_editingCommand) finish(m_editingCommand);
+    m_editingCommand = new FlexiNoteModel::EditCommand(m_model,
+                                                       tr("Draw Point"));
+    m_editingCommand->addPoint(m_editingPoint);
+
+    m_editing = true;
+}
+
+void
+FlexiNoteLayer::drawDrag(View *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
+
+    if (!m_model || !m_editing) return;
+
+    long frame = v->getFrameForX(e->x());
+    if (frame < 0) frame = 0;
+    frame = frame / m_model->getResolution() * m_model->getResolution();
+
+    float newValue = getValueForY(v, e->y());
+
+    long newFrame = m_editingPoint.frame;
+    long newDuration = frame - newFrame;
+    if (newDuration < 0) {
+        newFrame = frame;
+        newDuration = -newDuration;
+    } else if (newDuration == 0) {
+        newDuration = 1;
+    }
+
+    m_editingCommand->deletePoint(m_editingPoint);
+    m_editingPoint.frame = newFrame;
+    m_editingPoint.value = newValue;
+    m_editingPoint.duration = newDuration;
+    m_editingCommand->addPoint(m_editingPoint);
+}
+
+void
+FlexiNoteLayer::drawEnd(View *, QMouseEvent *)
+{
+//    SVDEBUG << "FlexiNoteLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
+    if (!m_model || !m_editing) return;
+    finish(m_editingCommand);
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+FlexiNoteLayer::eraseStart(View *v, QMouseEvent *e)
+{
+    if (!m_model) return;
+
+    if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
+
+    if (m_editingCommand) {
+        finish(m_editingCommand);
+        m_editingCommand = 0;
+    }
+
+    m_editing = true;
+}
+
+void
+FlexiNoteLayer::eraseDrag(View *v, QMouseEvent *e)
+{
+}
+
+void
+FlexiNoteLayer::eraseEnd(View *v, QMouseEvent *e)
+{
+    if (!m_model || !m_editing) return;
+
+    m_editing = false;
+
+    FlexiNoteModel::Point p(0);
+    if (!getPointToDrag(v, e->x(), e->y(), p)) return;
+    if (p.frame != m_editingPoint.frame || p.value != m_editingPoint.value) return;
+
+    m_editingCommand = new FlexiNoteModel::EditCommand(m_model, tr("Erase Point"));
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    finish(m_editingCommand);
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+FlexiNoteLayer::editStart(View *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
+    std::cerr << "FlexiNoteLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl;
+
+    if (!m_model) return;
+
+    if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
+    m_originalPoint = FlexiNote(m_editingPoint);
+    
+    if (m_editMode == RightBoundary) {
+        m_dragPointX = v->getXForFrame(m_editingPoint.frame + m_editingPoint.duration);
+    } else {
+        m_dragPointX = v->getXForFrame(m_editingPoint.frame);
+    }
+    m_dragPointY = getYForValue(v, m_editingPoint.value);
+
+    if (m_editingCommand) {
+        finish(m_editingCommand);
+        m_editingCommand = 0;
+    }
+
+    m_editing = true;
+    m_dragStartX = e->x();
+    m_dragStartY = e->y();
+    
+    long onset = m_originalPoint.frame;
+    long offset = m_originalPoint.frame + m_originalPoint.duration - 1;
+    
+    m_greatestLeftNeighbourFrame = -1;
+    m_smallestRightNeighbourFrame = std::numeric_limits<long>::max();
+    
+    for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin();
+         i != m_model->getPoints().end(); ++i) {
+        FlexiNote currentNote = *i;
+        
+        // left boundary
+        if (currentNote.frame + currentNote.duration - 1 < onset) {
+            m_greatestLeftNeighbourFrame = currentNote.frame + currentNote.duration - 1;
+        }
+        
+        // right boundary
+        if (currentNote.frame > offset) {
+            m_smallestRightNeighbourFrame = currentNote.frame;
+            break;
+        }
+    }
+    std::cerr << "editStart: mode is " << m_editMode << ", note frame: " << onset << ", left boundary: " << m_greatestLeftNeighbourFrame << ", right boundary: " << m_smallestRightNeighbourFrame << std::endl;
+}
+
+void
+FlexiNoteLayer::editDrag(View *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << endl;
+    std::cerr << "FlexiNoteLayer::editDrag(" << e->x() << "," << e->y() << ")" << std::endl;
+
+    if (!m_model || !m_editing) return;
+
+    int xdist = e->x() - m_dragStartX;
+    int ydist = e->y() - m_dragStartY;
+    int newx = m_dragPointX + xdist;
+    int newy = m_dragPointY + ydist;
+
+    long dragFrame = v->getFrameForX(newx);
+    if (dragFrame < 0) dragFrame = 0;
+    dragFrame = dragFrame / m_model->getResolution() * m_model->getResolution();
+    
+    float value = getValueForY(v, newy);
+
+    if (!m_editingCommand) {
+        m_editingCommand = new FlexiNoteModel::EditCommand(m_model,
+                                                           tr("Drag Point"));
+    }
+
+    m_editingCommand->deletePoint(m_editingPoint);
+
+    std::cerr << "edit mode: " << m_editMode << std::endl;
+    
+    switch (m_editMode) {
+    case LeftBoundary : {
+        // left 
+        if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1;
+        // right
+        if (m_intelligentActions && dragFrame >= m_originalPoint.frame + m_originalPoint.duration) {
+            dragFrame = m_originalPoint.frame + m_originalPoint.duration - 1;
+        }
+        m_editingPoint.frame = dragFrame;
+        m_editingPoint.duration = m_originalPoint.frame - dragFrame + m_originalPoint.duration;
+        break;
+    }
+    case RightBoundary : {
+        // left
+        if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1;
+        if (m_intelligentActions && dragFrame >= m_smallestRightNeighbourFrame) dragFrame = m_smallestRightNeighbourFrame - 1;
+        m_editingPoint.duration = dragFrame - m_originalPoint.frame + 1;
+        break;
+    }
+    case DragNote : {
+        // left
+        if (m_intelligentActions && dragFrame <= m_greatestLeftNeighbourFrame) dragFrame = m_greatestLeftNeighbourFrame + 1;
+        // right
+        if (m_intelligentActions && dragFrame + m_originalPoint.duration >= m_smallestRightNeighbourFrame) {
+            dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.duration;
+        }
+        m_editingPoint.frame = dragFrame;
+        m_editingPoint.value = value;
+        break;
+    }
+    }
+    updateNoteValue(v, m_editingPoint);
+    m_editingCommand->addPoint(m_editingPoint);
+    std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl;
+    
+}
+
+void
+FlexiNoteLayer::editEnd(View *v, QMouseEvent *e)
+{
+//    SVDEBUG << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
+    std::cerr << "FlexiNoteLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl;
+    
+    if (!m_model || !m_editing) return;
+
+    if (m_editingCommand) {
+
+        QString newName = m_editingCommand->getName();
+
+        if (m_editingPoint.frame != m_originalPoint.frame) {
+            if (m_editingPoint.value != m_originalPoint.value) {
+                newName = tr("Edit Point");
+            } else {
+                newName = tr("Relocate Point");
+            }
+        } else {
+            newName = tr("Change Point Value");
+        }
+
+        m_editingCommand->setName(newName);
+        finish(m_editingCommand);
+    }
+
+    m_editingCommand = 0;
+    m_editing = false;
+}
+
+void
+FlexiNoteLayer::splitStart(View *v, QMouseEvent *e)
+{
+    // GF: note splitting starts (!! remove printing soon)
+    std::cerr << "splitStart" << std::endl;
+    if (!m_model) return;
+
+    if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
+    // m_originalPoint = m_editingPoint;
+    // 
+    // m_dragPointX = v->getXForFrame(m_editingPoint.frame);
+    // m_dragPointY = getYForValue(v, m_editingPoint.value);
+
+    if (m_editingCommand) {
+        finish(m_editingCommand);
+        m_editingCommand = 0;
+    }
+
+    m_editing = true;
+    m_dragStartX = e->x();
+    m_dragStartY = e->y();
+    
+}
+
+void
+FlexiNoteLayer::splitEnd(View *v, QMouseEvent *e)
+{
+    // GF: note splitting ends. (!! remove printing soon)
+    std::cerr << "splitEnd" << std::endl;
+    if (!m_model || !m_editing || m_editMode != SplitNote) return;
+
+    int xdist = e->x() - m_dragStartX;
+    int ydist = e->y() - m_dragStartY;
+    if (xdist != 0 || ydist != 0) { 
+        std::cerr << "mouse moved" << std::endl;    
+        return; 
+    }
+
+    long frame = v->getFrameForX(e->x());
+
+    splitNotesAt(v, frame, e);
+}
+
+void
+FlexiNoteLayer::splitNotesAt(View *v, int frame)
+{
+    splitNotesAt(v, frame, 0);
+}
+
+void
+FlexiNoteLayer::splitNotesAt(View *v, int frame, QMouseEvent *e)
+{
+    FlexiNoteModel::PointList onPoints = m_model->getPoints(frame);
+    if (onPoints.empty()) return;
+    
+    FlexiNote note(*onPoints.begin());
+
+    FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+        (m_model, tr("Edit Point"));
+    command->deletePoint(note);
+
+    if (!e || !(e->modifiers() & Qt::ShiftModifier)) {
+
+        int gap = 0; // MM: I prefer a gap of 0, but we can decide later
+    
+        FlexiNote newNote1(note.frame, note.value, 
+                           frame - note.frame - gap, 
+                           note.level, note.label);
+    
+        FlexiNote newNote2(frame, note.value, 
+                           note.duration - newNote1.duration, 
+                           note.level, note.label);
+                       
+        if (m_intelligentActions) {
+            if (updateNoteValue(v, newNote1)) {
+                command->addPoint(newNote1);
+            }
+            if (updateNoteValue(v, newNote2)) {
+                command->addPoint(newNote2);
+            }
+        } else {
+            command->addPoint(newNote1);
+            command->addPoint(newNote2);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::addNote(View *v, QMouseEvent *e)
+{
+    std::cerr << "addNote" << std::endl;
+    if (!m_model) return;
+
+    long duration = 10000;
+    
+    long frame = v->getFrameForX(e->x());
+    float value = getValueForY(v, e->y());
+    
+    if (m_intelligentActions) {
+        long smallestRightNeighbourFrame = 0;
+        for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin();
+             i != m_model->getPoints().end(); ++i) {
+            FlexiNote currentNote = *i;
+            if (currentNote.frame > frame) {
+                smallestRightNeighbourFrame = currentNote.frame;
+                break;
+            }
+        }
+        
+        duration = std::min(smallestRightNeighbourFrame - frame + 1, duration);
+        duration = (duration > 0) ? duration : 0;
+    }
+
+    if (!m_intelligentActions || 
+        (m_model->getPoints(frame).empty() && duration > 0)) {
+        FlexiNote newNote(frame, value, duration, 100, "new note");
+        FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+            (m_model, tr("Add Point"));
+        command->addPoint(newNote);
+        finish(command);
+    }
+}
+
+SparseTimeValueModel *
+FlexiNoteLayer::getAssociatedPitchModel(View *v) const
+{
+    // Better than we used to do, but still not very satisfactory
+
+    cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
+
+    for (int i = 0; i < v->getLayerCount(); ++i) {
+        Layer *layer = v->getLayer(i);
+        if (layer && !layer->isLayerDormant(v) && 
+            layer->getLayerPresentationName() != "candidate") {
+            cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
+            SparseTimeValueModel *model = qobject_cast<SparseTimeValueModel *>
+                (layer->getModel());
+            cerr << "FlexiNoteLayer::getAssociatedPitchModel: and its model is " << model << endl;
+            if (model && model->getScaleUnits() == "Hz") {
+                cerr << "FlexiNoteLayer::getAssociatedPitchModel: it's good, returning " << model << endl;
+                return model;
+            }
+        }
+    }
+    return 0;
+}
+
+void
+FlexiNoteLayer::snapSelectedNotesToPitchTrack(View *v, Selection s)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+        (m_model, tr("Snap Notes"));
+
+    cerr << "snapSelectedNotesToPitchTrack: selection is from " << s.getStartFrame() << " to " << s.getEndFrame() << endl;
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        FlexiNote note(*i);
+
+        cerr << "snapSelectedNotesToPitchTrack: looking at note from " << note.frame << " to " << note.frame + note.duration << endl;
+
+        if (!s.contains(note.frame) &&
+            !s.contains(note.frame + note.duration - 1)) {
+            continue;
+        }
+
+        FlexiNote newNote(note);
+
+        command->deletePoint(note);
+
+
+    }
+    
+    finish(command);
+}
+
+void
+FlexiNoteLayer::mergeNotes(View *v, Selection s, bool inclusive)
+{
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    FlexiNoteModel::PointList::iterator i = points.begin();
+    if (inclusive) {
+        while (i != points.end() && i->frame + i->duration < s.getStartFrame()) {
+            ++i;
+        }
+    } else {
+        while (i != points.end() && i->frame < s.getStartFrame()) {
+            ++i;
+        }
+    }
+        
+    if (i == points.end()) return;
+
+    FlexiNoteModel::EditCommand *command = 
+        new FlexiNoteModel::EditCommand(m_model, tr("Merge Notes"));
+
+    FlexiNote newNote(*i);
+
+    while (i != points.end()) {
+
+        if (inclusive) {
+            if (i->frame >= s.getEndFrame()) break;
+        } else {
+            if (i->frame + i->duration > s.getEndFrame()) break;
+        }
+
+        newNote.duration = i->frame + i->duration - newNote.frame;
+        command->deletePoint(*i);
+
+        ++i;
+    }
+
+    updateNoteValue(v, newNote);
+    command->addPoint(newNote);
+    finish(command);
+}
+
+bool
+FlexiNoteLayer::updateNoteValue(View *v, FlexiNoteModel::Point &note) const
+{
+    SparseTimeValueModel *model = getAssociatedPitchModel(v);
+    if (!model) return false;
+        
+    std::cerr << model->getTypeName() << std::endl;
+
+    SparseModel<TimeValuePoint>::PointList dataPoints =
+        model->getPoints(note.frame, note.frame + note.duration);
+   
+    std::cerr << "frame " << note.frame << ": " << dataPoints.size() << " candidate points" << std::endl;
+   
+    if (dataPoints.empty()) return false;
+
+    std::vector<float> pitchValues;
+   
+    for (SparseModel<TimeValuePoint>::PointList::const_iterator i =
+             dataPoints.begin(); i != dataPoints.end(); ++i) {
+        if (i->frame >= note.frame &&
+            i->frame < note.frame + note.duration) {
+            pitchValues.push_back(i->value);
+        }
+    }
+        
+    if (pitchValues.empty()) return false;
+
+    sort(pitchValues.begin(), pitchValues.end());
+    size_t size = pitchValues.size();
+    double median;
+
+    if (size % 2 == 0) {
+        median = (pitchValues[size/2 - 1] + pitchValues[size/2]) / 2;
+    } else {
+        median = pitchValues[size/2];
+    }
+    
+    note.value = median;
+
+    return true;
+}
+
+void 
+FlexiNoteLayer::mouseMoveEvent(View *v, QMouseEvent *e)
+{
+    // GF: context sensitive cursors
+    // v->setCursor(Qt::ArrowCursor);
+    FlexiNoteModel::Point note(0);
+    if (!getNoteToEdit(v, e->x(), e->y(), note)) { 
+        // v->setCursor(Qt::UpArrowCursor);
+        return; 
+    }
+
+    bool closeToLeft = false, closeToRight = false, closeToTop = false, closeToBottom = false;
+    getRelativeMousePosition(v, note, e->x(), e->y(), closeToLeft, closeToRight, closeToTop, closeToBottom);
+    // if (!closeToLeft) return;
+    // if (closeToTop) v->setCursor(Qt::SizeVerCursor);
+    
+    if (closeToLeft) { v->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; return; }
+    if (closeToRight) { v->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; return; }
+    if (closeToTop) { v->setCursor(Qt::CrossCursor);  m_editMode = DragNote; return; }
+    if (closeToBottom) { v->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; return; }
+
+    v->setCursor(Qt::ArrowCursor);
+
+    // std::cerr << "Mouse moved in edit mode over FlexiNoteLayer" << std::endl;
+    // v->setCursor(Qt::SizeHorCursor);
+
+}
+
+void
+FlexiNoteLayer::getRelativeMousePosition(View *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const
+{
+    // GF: TODO: consoloidate the tolerance values
+    if (!m_model) return;
+
+    int ctol = 0;
+    int noteStartX = v->getXForFrame(note.frame);
+    int noteEndX = v->getXForFrame(note.frame + note.duration);
+    int noteValueY = getYForValue(v,note.value);
+    int noteStartY = noteValueY - (NOTE_HEIGHT / 2);
+    int noteEndY = noteValueY + (NOTE_HEIGHT / 2);
+    
+    bool closeToNote = false;
+    
+    if (y >= noteStartY-ctol && y <= noteEndY+ctol && x >= noteStartX-ctol && x <= noteEndX+ctol) closeToNote = true;
+    if (!closeToNote) return;
+    
+    int tol = NOTE_HEIGHT / 2;
+    
+    if (x >= noteStartX - tol && x <= noteStartX + tol) closeToLeft = true;
+    if (x >= noteEndX - tol && x <= noteEndX + tol) closeToRight = true;
+    if (y >= noteStartY - tol && y <= noteStartY + tol) closeToTop = true;
+    if (y >= noteEndY - tol && y <= noteEndY + tol) closeToBottom = true;
+
+//    cerr << "FlexiNoteLayer::getRelativeMousePosition: close to: left " << closeToLeft << " right " << closeToRight << " top " << closeToTop << " bottom " << closeToBottom << endl;
+}
+
+
+bool
+FlexiNoteLayer::editOpen(View *v, QMouseEvent *e)
+{
+    std::cerr << "Opening note editor dialog" << std::endl;
+    if (!m_model) return false;
+
+    FlexiNoteModel::Point note(0);
+    if (!getPointToDrag(v, e->x(), e->y(), note)) return false;
+
+//    FlexiNoteModel::Point note = *points.begin();
+
+    ItemEditDialog *dialog = new ItemEditDialog
+        (m_model->getSampleRate(),
+         ItemEditDialog::ShowTime |
+         ItemEditDialog::ShowDuration |
+         ItemEditDialog::ShowValue |
+         ItemEditDialog::ShowText,
+         getScaleUnits());
+
+    dialog->setFrameTime(note.frame);
+    dialog->setValue(note.value);
+    dialog->setFrameDuration(note.duration);
+    dialog->setText(note.label);
+
+    if (dialog->exec() == QDialog::Accepted) {
+
+        FlexiNoteModel::Point newNote = note;
+        newNote.frame = dialog->getFrameTime();
+        newNote.value = dialog->getValue();
+        newNote.duration = dialog->getFrameDuration();
+        newNote.label = dialog->getText();
+        
+        FlexiNoteModel::EditCommand *command = new FlexiNoteModel::EditCommand
+            (m_model, tr("Edit Point"));
+        command->deletePoint(note);
+        command->addPoint(newNote);
+        finish(command);
+    }
+
+    delete dialog;
+    return true;
+}
+
+void
+FlexiNoteLayer::moveSelection(Selection s, size_t newStartFrame)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Drag Selection"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        if (s.contains(i->frame)) {
+            FlexiNoteModel::Point newPoint(*i);
+            newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
+            command->deletePoint(*i);
+            command->addPoint(newPoint);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::resizeSelection(Selection s, Selection newSize)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Resize Selection"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    double ratio =
+        double(newSize.getEndFrame() - newSize.getStartFrame()) /
+        double(s.getEndFrame() - s.getStartFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        if (s.contains(i->frame)) {
+
+            double targetStart = i->frame;
+            targetStart = newSize.getStartFrame() + 
+                double(targetStart - s.getStartFrame()) * ratio;
+
+            double targetEnd = i->frame + i->duration;
+            targetEnd = newSize.getStartFrame() +
+                double(targetEnd - s.getStartFrame()) * ratio;
+
+            FlexiNoteModel::Point newPoint(*i);
+            newPoint.frame = lrint(targetStart);
+            newPoint.duration = lrint(targetEnd - targetStart);
+            command->deletePoint(*i);
+            command->addPoint(newPoint);
+        }
+    }
+
+    finish(command);
+}
+
+void
+FlexiNoteLayer::deleteSelection(Selection s)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Delete Selected Points"));
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+
+        if (s.contains(i->frame)) {
+            command->deletePoint(*i);
+        }
+    }
+
+    finish(command);
+}    
+
+void
+FlexiNoteLayer::copy(View *v, Selection s, Clipboard &to)
+{
+    if (!m_model) return;
+
+    FlexiNoteModel::PointList points =
+        m_model->getPoints(s.getStartFrame(), s.getEndFrame());
+
+    for (FlexiNoteModel::PointList::iterator i = points.begin();
+         i != points.end(); ++i) {
+        if (s.contains(i->frame)) {
+            Clipboard::Point point(i->frame, i->value, i->duration, i->level, i->label);
+            point.setReferenceFrame(alignToReference(v, i->frame));
+            to.addPoint(point);
+        }
+    }
+}
+
+bool
+FlexiNoteLayer::paste(View *v, const Clipboard &from, int frameOffset, bool /* interactive */)
+{
+    if (!m_model) return false;
+
+    const Clipboard::PointList &points = from.getPoints();
+
+    bool realign = false;
+
+    if (clipboardHasDifferentAlignment(v, from)) {
+
+        QMessageBox::StandardButton button =
+            QMessageBox::question(v, tr("Re-align pasted items?"),
+                                  tr("The items you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
+                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
+                                  QMessageBox::Yes);
+
+        if (button == QMessageBox::Cancel) {
+            return false;
+        }
+
+        if (button == QMessageBox::Yes) {
+            realign = true;
+        }
+    }
+
+    FlexiNoteModel::EditCommand *command =
+        new FlexiNoteModel::EditCommand(m_model, tr("Paste"));
+
+    for (Clipboard::PointList::const_iterator i = points.begin();
+         i != points.end(); ++i) {
+        
+        if (!i->haveFrame()) continue;
+        size_t frame = 0;
+
+        if (!realign) {
+            
+            frame = i->getFrame();
+
+        } else {
+
+            if (i->haveReferenceFrame()) {
+                frame = i->getReferenceFrame();
+                frame = alignFromReference(v, frame);
+            } else {
+                frame = i->getFrame();
+            }
+        }
+
+        FlexiNoteModel::Point newPoint(frame);
+  
+        if (i->haveLabel()) newPoint.label = i->getLabel();
+        if (i->haveValue()) newPoint.value = i->getValue();
+        else newPoint.value = (m_model->getValueMinimum() +
+                               m_model->getValueMaximum()) / 2;
+        if (i->haveLevel()) newPoint.level = i->getLevel();
+        if (i->haveDuration()) newPoint.duration = i->getDuration();
+        else {
+            size_t nextFrame = frame;
+            Clipboard::PointList::const_iterator j = i;
+            for (; j != points.end(); ++j) {
+                if (!j->haveFrame()) continue;
+                if (j != i) break;
+            }
+            if (j != points.end()) {
+                nextFrame = j->getFrame();
+            }
+            if (nextFrame == frame) {
+                newPoint.duration = m_model->getResolution();
+            } else {
+                newPoint.duration = nextFrame - frame;
+            }
+        }
+        
+        command->addPoint(newPoint);
+    }
+
+    finish(command);
+    return true;
+}
+
+void
+FlexiNoteLayer::addNoteOn(long frame, int pitch, int velocity)
+{
+    m_pendingNoteOns.insert(FlexiNote(frame, pitch, 0, float(velocity) / 127.0, ""));
+}
+
+void
+FlexiNoteLayer::addNoteOff(long frame, int pitch)
+{
+    for (FlexiNoteSet::iterator i = m_pendingNoteOns.begin();
+         i != m_pendingNoteOns.end(); ++i) {
+        if (lrintf((*i).value) == pitch) {
+            FlexiNote note(*i);
+            m_pendingNoteOns.erase(i);
+            note.duration = frame - note.frame;
+            if (m_model) {
+                FlexiNoteModel::AddPointCommand *c = new FlexiNoteModel::AddPointCommand
+                    (m_model, note, tr("Record FlexiNote"));
+                // execute and bundle:
+                CommandHistory::getInstance()->addCommand(c, true, true);
+            }
+            break;
+        }
+    }
+}
+
+void
+FlexiNoteLayer::abandonNoteOns()
+{
+    m_pendingNoteOns.clear();
+}
+
+int
+FlexiNoteLayer::getDefaultColourHint(bool darkbg, bool &impose)
+{
+    impose = false;
+    return ColourDatabase::getInstance()->getColourIndex
+        (QString(darkbg ? "White" : "Black"));
+}
+
+void
+FlexiNoteLayer::toXml(QTextStream &stream,
+                      QString indent, QString extraAttributes) const
+{
+    SingleColourLayer::toXml(stream, indent, extraAttributes +
+                             QString(" verticalScale=\"%1\" scaleMinimum=\"%2\" scaleMaximum=\"%3\" ")
+                             .arg(m_verticalScale)
+                             .arg(m_scaleMinimum)
+                             .arg(m_scaleMaximum));
+}
+
+void
+FlexiNoteLayer::setProperties(const QXmlAttributes &attributes)
+{
+    SingleColourLayer::setProperties(attributes);
+
+    bool ok, alsoOk;
+    VerticalScale scale = (VerticalScale)
+        attributes.value("verticalScale").toInt(&ok);
+    if (ok) setVerticalScale(scale);
+
+    float min = attributes.value("scaleMinimum").toFloat(&ok);
+    float max = attributes.value("scaleMaximum").toFloat(&alsoOk);
+//    if (ok && alsoOk && min != max) setDisplayExtents(min, max);
+}
+
+void
+FlexiNoteLayer::setVerticalRangeToNoteRange(View *v)
+{
+    float minf = std::numeric_limits<float>::max();
+    float maxf = 0;
+    bool hasNotes = 0;
+    for (FlexiNoteModel::PointList::const_iterator i = m_model->getPoints().begin();
+         i != m_model->getPoints().end(); ++i) {
+        hasNotes = 1;
+        FlexiNote note = *i;
+        if (note.value < minf) minf = note.value;
+        if (note.value > maxf) maxf = note.value;
+    }
+    
+    std::cerr << "min frequency:" << minf << ", max frequency: " << maxf << std::endl;
+    
+    if (hasNotes) {
+        v->getLayer(1)->setDisplayExtents(minf*0.66,maxf*1.5); 
+        // MM: this is a hack because we rely on 
+        // * this layer being automatically aligned to layer 1
+        // * layer one is a log frequency layer.
+    }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/FlexiNoteLayer.h	Fri May 09 17:15:50 2014 +0100
@@ -0,0 +1,210 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _FLEXINOTE_LAYER_H_
+#define _FLEXINOTE_LAYER_H_
+
+#define NOTE_HEIGHT 16
+
+#include "SingleColourLayer.h"
+#include "VerticalScaleLayer.h"
+
+#include "data/model/FlexiNoteModel.h"
+
+#include <QObject>
+#include <QColor>
+
+class View;
+class QPainter;
+class SparseTimeValueModel;
+
+class FlexiNoteLayer : public SingleColourLayer,
+                       public VerticalScaleLayer
+{
+    Q_OBJECT
+
+public:
+    FlexiNoteLayer();
+
+    virtual void paint(View *v, QPainter &paint, QRect rect) const;
+
+    virtual int getVerticalScaleWidth(View *v, bool, QPainter &) const;
+    virtual void paintVerticalScale(View *v, bool, QPainter &paint, QRect rect) const;
+
+    virtual QString getFeatureDescription(View *v, QPoint &) const;
+
+    virtual bool snapToFeatureFrame(View *v, int &frame,
+                    size_t &resolution,
+                    SnapType snap) const;
+
+    virtual void drawStart(View *v, QMouseEvent *);
+    virtual void drawDrag(View *v, QMouseEvent *);
+    virtual void drawEnd(View *v, QMouseEvent *);
+
+    virtual void eraseStart(View *v, QMouseEvent *);
+    virtual void eraseDrag(View *v, QMouseEvent *);
+    virtual void eraseEnd(View *v, QMouseEvent *);
+
+    virtual void editStart(View *v, QMouseEvent *);
+    virtual void editDrag(View *v, QMouseEvent *);
+    virtual void editEnd(View *v, QMouseEvent *);
+
+    virtual void splitStart(View *v, QMouseEvent *);
+    virtual void splitEnd(View *v, QMouseEvent *);
+    
+    virtual void addNote(View *v, QMouseEvent *e);
+
+    virtual void mouseMoveEvent(View *v, QMouseEvent *);
+
+    virtual bool editOpen(View *v, QMouseEvent *);
+
+    virtual void moveSelection(Selection s, size_t newStartFrame);
+    virtual void resizeSelection(Selection s, Selection newSize);
+    virtual void deleteSelection(Selection s);
+
+    virtual void copy(View *v, Selection s, Clipboard &to);
+    virtual bool paste(View *v, const Clipboard &from, int frameOffset,
+                       bool interactive);
+
+    void splitNotesAt(View *v, int frame);
+    void snapSelectedNotesToPitchTrack(View *v, Selection s);
+    void mergeNotes(View *v, Selection s, bool inclusive);
+
+    virtual const Model *getModel() const { return m_model; }
+    void setModel(FlexiNoteModel *model);
+
+    virtual PropertyList getProperties() const;
+    virtual QString getPropertyLabel(const PropertyName &) const;
+    virtual PropertyType getPropertyType(const PropertyName &) const;
+    virtual QString getPropertyGroupName(const PropertyName &) const;
+    virtual int getPropertyRangeAndValue(const PropertyName &,
+                                         int *min, int *max, int *deflt) const;
+    virtual QString getPropertyValueLabel(const PropertyName &,
+                      int value) const;
+    virtual void setProperty(const PropertyName &, int value);
+
+    enum VerticalScale {
+        AutoAlignScale,
+        LinearScale,
+        LogScale,
+        MIDIRangeScale
+    };
+    
+    //GF: Tonioni: context sensitive note edit actions (denoted clockwise from top).
+    enum EditMode {
+        DragNote,
+        RightBoundary,
+        SplitNote,
+        LeftBoundary
+    };
+    
+    void setIntelligentActions(bool on) { m_intelligentActions=on; }
+
+    void setVerticalScale(VerticalScale scale);
+    VerticalScale getVerticalScale() const { return m_verticalScale; }
+
+    virtual bool isLayerScrollable(const View *v) const;
+
+    virtual bool isLayerEditable() const { return true; }
+
+    virtual int getCompletion(View *) const { return m_model->getCompletion(); }
+
+    virtual bool getValueExtents(float &min, float &max,
+                                 bool &log, QString &unit) const;
+
+    virtual bool getDisplayExtents(float &min, float &max) const;
+    virtual bool setDisplayExtents(float min, float max);
+
+    virtual int getVerticalZoomSteps(int &defaultStep) const;
+    virtual int getCurrentVerticalZoomStep() const;
+    virtual void setVerticalZoomStep(int);
+    virtual RangeMapper *getNewVerticalZoomRangeMapper() const;
+
+    /**
+     * Add a note-on.  Used when recording MIDI "live".  The note will
+     * not be finally added to the layer until the corresponding
+     * note-off.
+     */
+    void addNoteOn(long frame, int pitch, int velocity);
+    
+    /**
+     * Add a note-off.  This will cause a note to appear, if and only
+     * if there is a matching pending note-on.
+     */
+    void addNoteOff(long frame, int pitch);
+
+    /**
+     * Abandon all pending note-on events.
+     */
+    void abandonNoteOns();
+
+    virtual void toXml(QTextStream &stream, QString indent = "",
+                       QString extraAttributes = "") const;
+
+    void setProperties(const QXmlAttributes &attributes);
+    
+    void setVerticalRangeToNoteRange(View *v);
+
+    /// VerticalScaleLayer methods
+    virtual int getYForValue(View *v, float value) const;
+    virtual float getValueForY(View *v, int y) const;
+    virtual QString getScaleUnits() const;
+
+protected:
+    void getScaleExtents(View *, float &min, float &max, bool &log) const;
+    bool shouldConvertMIDIToHz() const;
+
+    virtual int getDefaultColourHint(bool dark, bool &impose);
+
+    FlexiNoteModel::PointList getLocalPoints(View *v, int) const;
+
+    bool getPointToDrag(View *v, int x, int y, FlexiNoteModel::Point &) const;
+    bool getNoteToEdit(View *v, int x, int y, FlexiNoteModel::Point &) const;
+    void getRelativeMousePosition(View *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const;
+    SparseTimeValueModel *getAssociatedPitchModel(View *v) const;
+    bool updateNoteValue(View *v, FlexiNoteModel::Point &note) const;
+    void splitNotesAt(View *v, int frame, QMouseEvent *e);
+
+    FlexiNoteModel *m_model;
+    bool m_editing;
+    bool m_intelligentActions;
+    int m_dragPointX;
+    int m_dragPointY;
+    int m_dragStartX;
+    int m_dragStartY;
+    FlexiNoteModel::Point m_originalPoint;
+    FlexiNoteModel::Point m_editingPoint;
+    long m_greatestLeftNeighbourFrame;
+    long m_smallestRightNeighbourFrame;
+    FlexiNoteModel::EditCommand *m_editingCommand;
+    VerticalScale m_verticalScale;
+    EditMode m_editMode;
+
+    typedef std::set<FlexiNoteModel::Point, FlexiNoteModel::Point::Comparator> FlexiNoteSet;
+    FlexiNoteSet m_pendingNoteOns;
+
+    mutable float m_scaleMinimum;
+    mutable float m_scaleMaximum;
+
+    bool shouldAutoAlign() const;
+
+    void finish(FlexiNoteModel::EditCommand *command) {
+        Command *c = command->finish();
+        if (c) CommandHistory::getInstance()->addCommand(c, false);
+    }
+};
+
+#endif
+
--- a/layer/Layer.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/Layer.h	Fri May 09 17:15:50 2014 +0100
@@ -131,7 +131,7 @@
     virtual int getVerticalScaleWidth(View *, bool detailed,
                                       QPainter &) const = 0;
 
-    virtual void paintVerticalScale(View *, bool detailed,
+    virtual void paintVerticalScale(View *, bool /* detailed */,
                                     QPainter &, QRect) const { }
 
     virtual bool getCrosshairExtents(View *, QPainter &, QPoint /* cursorPos */,
@@ -150,7 +150,7 @@
 	return "";
     }
 
-    virtual QString getLabelPreceding(size_t frame) const {
+    virtual QString getLabelPreceding(size_t /* frame */) const {
         return "";
     }
 
@@ -229,6 +229,10 @@
     virtual void editDrag(View *, QMouseEvent *) { }
     virtual void editEnd(View *, QMouseEvent *) { }
 
+    virtual void splitStart(View *, QMouseEvent *) { }
+    virtual void splitEnd(View *, QMouseEvent *) { }
+    virtual void addNote(View *, QMouseEvent *) { };
+
     // Measurement rectangle (or equivalent).  Unlike draw and edit,
     // the base Layer class can provide working implementations of
     // these for most situations.
--- a/layer/LayerFactory.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/LayerFactory.cpp	Fri May 09 17:15:50 2014 +0100
@@ -21,6 +21,7 @@
 #include "TimeInstantLayer.h"
 #include "TimeValueLayer.h"
 #include "NoteLayer.h"
+#include "FlexiNoteLayer.h"
 #include "RegionLayer.h"
 #include "TextLayer.h"
 #include "ImageLayer.h"
@@ -36,6 +37,7 @@
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/SparseTimeValueModel.h"
 #include "data/model/NoteModel.h"
+#include "data/model/FlexiNoteModel.h"
 #include "data/model/RegionModel.h"
 #include "data/model/TextModel.h"
 #include "data/model/ImageModel.h"
@@ -73,6 +75,7 @@
     case TimeInstants: return Layer::tr("Time Instants");
     case TimeValues:   return Layer::tr("Time Values");
     case Notes:        return Layer::tr("Notes");
+    case FlexiNotes:   return Layer::tr("Flexible Notes");
     case Regions:      return Layer::tr("Regions");
     case Text:         return Layer::tr("Text");
     case Image:        return Layer::tr("Images");
@@ -161,6 +164,11 @@
 	types.insert(Notes);
     }
 
+	// NOTE: GF: types is a set, so order of insertion does not matter
+    if (dynamic_cast<FlexiNoteModel *>(model)) {
+	types.insert(FlexiNotes);
+    }
+
     if (dynamic_cast<RegionModel *>(model)) {
 	types.insert(Regions);
     }
@@ -189,6 +197,7 @@
     LayerTypeSet types;
     types.insert(TimeInstants);
     types.insert(TimeValues);
+    types.insert(FlexiNotes);
     types.insert(Notes);
     types.insert(Regions);
     types.insert(Text);
@@ -205,6 +214,7 @@
     if (dynamic_cast<const TimeRulerLayer *>(layer)) return TimeRuler;
     if (dynamic_cast<const TimeInstantLayer *>(layer)) return TimeInstants;
     if (dynamic_cast<const TimeValueLayer *>(layer)) return TimeValues;
+    if (dynamic_cast<const FlexiNoteLayer *>(layer)) return FlexiNotes;
     if (dynamic_cast<const NoteLayer *>(layer)) return Notes;
     if (dynamic_cast<const RegionLayer *>(layer)) return Regions;
     if (dynamic_cast<const TextLayer *>(layer)) return Text;
@@ -225,6 +235,7 @@
     case TimeInstants: return "instants";
     case TimeValues: return "values";
     case Notes: return "notes";
+    case FlexiNotes: return "flexinotes";
     case Regions: return "regions";
     case Text: return "text";
     case Image: return "image";
@@ -247,6 +258,7 @@
     case TimeInstants: return "timeinstants";
     case TimeValues: return "timevalues";
     case Notes: return "notes";
+    case FlexiNotes: return "flexinotes";
     case Regions: return "regions";
     case Text: return "text";
     case Image: return "image";
@@ -267,7 +279,7 @@
     if (name == "timeruler") return TimeRuler;
     if (name == "timeinstants") return TimeInstants;
     if (name == "timevalues") return TimeValues;
-    if (name == "notes") return Notes;
+    if (name == "flexinotes") return FlexiNotes;
     if (name == "regions") return Regions;
     if (name == "text") return Text;
     if (name == "image") return Image;
@@ -282,7 +294,7 @@
 {
 //    if (trySetModel<WaveformLayer, RangeSummarisableTimeValueModel>(layer, model))
 //	return;
-
+	
     if (trySetModel<WaveformLayer, WaveFileModel>(layer, model))
 	return;
 
@@ -301,9 +313,13 @@
     if (trySetModel<TimeValueLayer, SparseTimeValueModel>(layer, model))
 	return;
 
-    if (trySetModel<NoteLayer, NoteModel>(layer, model))
-	return;
+    if (trySetModel<NoteLayer, NoteModel>(layer, model)) 
+	return; 
 
+	// GF: added FlexiNoteLayer
+    if (trySetModel<FlexiNoteLayer, FlexiNoteModel>(layer, model)) 
+	return; 
+	
     if (trySetModel<RegionLayer, RegionModel>(layer, model))
 	return;
 
@@ -333,6 +349,8 @@
 	return new SparseOneDimensionalModel(baseModel->getSampleRate(), 1);
     } else if (layerType == TimeValues) {
 	return new SparseTimeValueModel(baseModel->getSampleRate(), 1, true);
+    } else if (layerType == FlexiNotes) {
+	return new FlexiNoteModel(baseModel->getSampleRate(), 1, true);
     } else if (layerType == Notes) {
 	return new NoteModel(baseModel->getSampleRate(), 1, true);
     } else if (layerType == Regions) {
@@ -402,6 +420,10 @@
 	layer = new TimeValueLayer;
 	break;
 
+    case FlexiNotes:
+	layer = new FlexiNoteLayer;
+	break;
+
     case Notes:
 	layer = new NoteLayer;
 	break;
--- a/layer/LayerFactory.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/LayerFactory.h	Fri May 09 17:15:50 2014 +0100
@@ -35,6 +35,7 @@
 	TimeInstants,
 	TimeValues,
 	Notes,
+	FlexiNotes,
 	Regions,
 	Text,
         Image,
--- a/layer/NoteLayer.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/NoteLayer.cpp	Fri May 09 17:15:50 2014 +0100
@@ -56,12 +56,12 @@
     m_scaleMinimum(0),
     m_scaleMaximum(0)
 {
-    
+  	SVDEBUG << "constructed NoteLayer" << endl;
 }
 
 void
 NoteLayer::setModel(NoteModel *model)
-{
+{	
     if (m_model == model) return;
     m_model = model;
 
@@ -842,7 +842,7 @@
 void
 NoteLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model) return;
+    if (!m_model || m_model->getPoints().empty()) return;
 
     QString unit;
     float min, max;
--- a/layer/RegionLayer.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/RegionLayer.cpp	Fri May 09 17:15:50 2014 +0100
@@ -1069,7 +1069,7 @@
 void
 RegionLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model) return;
+    if (!m_model || m_model->getPoints().empty()) return;
 
     QString unit;
     float min, max;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ShowLayerCommand.h	Fri May 09 17:15:50 2014 +0100
@@ -0,0 +1,42 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef _SHOW_LAYER_COMMAND_H_
+#define _SHOW_LAYER_COMMAND_H_
+
+#include "base/Command.h"
+
+class ShowLayerCommand : public Command
+{
+public:
+    ShowLayerCommand(View *view, Layer *layer, bool show, QString commandName) :
+        m_view(view), m_layer(layer), m_show(show), m_name(commandName) { }
+    void execute() {
+        m_layer->showLayer(m_view, m_show);
+    }
+    void unexecute() {
+        m_layer->showLayer(m_view, !m_show);
+    }
+    QString getName() const {
+        return m_name;
+    }
+protected:
+    View *m_view;
+    Layer *m_layer;
+    bool m_show;
+    QString m_name;
+};
+
+#endif
--- a/layer/SpectrogramLayer.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/SpectrogramLayer.cpp	Fri May 09 17:15:50 2014 +0100
@@ -3657,18 +3657,21 @@
 		 "colourScheme=\"%4\" "
 		 "colourRotation=\"%5\" "
 		 "frequencyScale=\"%6\" "
-		 "binDisplay=\"%7\" "
-		 "normalizeColumns=\"%8\" "
-                 "normalizeVisibleArea=\"%9\"")
+		 "binDisplay=\"%7\" ")
 	.arg(m_minFrequency)
 	.arg(m_maxFrequency)
 	.arg(m_colourScale)
 	.arg(m_colourMap)
 	.arg(m_colourRotation)
 	.arg(m_frequencyScale)
-	.arg(m_binDisplay)
+	.arg(m_binDisplay);
+
+    s += QString("normalizeColumns=\"%1\" "
+                 "normalizeVisibleArea=\"%2\" "
+                 "normalizeHybrid=\"%3\" ")
 	.arg(m_normalizeColumns ? "true" : "false")
-        .arg(m_normalizeVisibleArea ? "true" : "false");
+        .arg(m_normalizeVisibleArea ? "true" : "false")
+        .arg(m_normalizeHybrid ? "true" : "false");
 
     Layer::toXml(stream, indent, extraAttributes + " " + s);
 }
@@ -3741,5 +3744,9 @@
     bool normalizeVisibleArea =
 	(attributes.value("normalizeVisibleArea").trimmed() == "true");
     setNormalizeVisibleArea(normalizeVisibleArea);
+
+    bool normalizeHybrid =
+	(attributes.value("normalizeHybrid").trimmed() == "true");
+    setNormalizeHybrid(normalizeHybrid);
 }
     
--- a/layer/TimeValueLayer.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/TimeValueLayer.cpp	Fri May 09 17:15:50 2014 +0100
@@ -972,7 +972,7 @@
 	 i != points.end(); ++i) {
 
         if (m_derivative && i == points.begin()) continue;
-        
+
 	const SparseTimeValueModel::Point &p(*i);
 
         float value = p.value;
@@ -987,6 +987,10 @@
 
         bool gap = false;
         if (m_plotStyle == PlotDiscreteCurves) { 
+            if (value == 0.0) {
+                // Treat zeros as gaps
+                continue;
+            }
             gap = (p.frame > prevFrame &&
                    (p.frame - prevFrame >= m_model->getResolution() * 2));
         }
@@ -1000,6 +1004,7 @@
         }
 
 	bool haveNext = false;
+        float nvalue = 0.f;
         int nf = v->getModelsEndFrame();
 	int nx = v->getXForFrame(nf);
 	int ny = y;
@@ -1009,7 +1014,7 @@
 
 	if (j != points.end()) {
 	    const SparseTimeValueModel::Point &q(*j);
-            float nvalue = q.value;
+            nvalue = q.value;
             if (m_derivative) nvalue -= p.value;
             nf = q.frame;
 	    nx = v->getXForFrame(nf);
@@ -1116,7 +1121,9 @@
 		    float y1 = ny;
 
                     if (m_plotStyle == PlotDiscreteCurves) {
-                        bool nextGap = nf - p.frame >= m_model->getResolution() * 2;
+                        bool nextGap =
+                            (nvalue == 0.0) ||
+                            (nf - p.frame >= m_model->getResolution() * 2);
                         if (nextGap) {
                             x1 = x0;
                             y1 = y0;
@@ -1163,34 +1170,37 @@
 	    paint.drawRect(x, -1, nx - x, v->height() + 1);
 	}
 
-        QString label = p.label;
-        bool italic = false;
+        if (v->shouldShowFeatureLabels()) {
 
-        if (label == "" &&
-            (m_plotStyle == PlotPoints ||
-             m_plotStyle == PlotSegmentation ||
-             m_plotStyle == PlotConnectedPoints)) {
-            char lc[20];
-            snprintf(lc, 20, "%.3g", p.value);
-            label = lc;
-            italic = true;
+            QString label = p.label;
+            bool italic = false;
+
+            if (label == "" &&
+                (m_plotStyle == PlotPoints ||
+                 m_plotStyle == PlotSegmentation ||
+                 m_plotStyle == PlotConnectedPoints)) {
+                char lc[20];
+                snprintf(lc, 20, "%.3g", p.value);
+                label = lc;
+                italic = true;
+            }
+
+            if (label != "") {
+                // Quick test for 20px before we do the slower test using metrics
+                bool haveRoom = (nx > x + 20);
+                haveRoom = (haveRoom &&
+                            (nx > x + 6 + paint.fontMetrics().width(label)));
+                if (haveRoom ||
+                    (!haveNext &&
+                     (pointCount == 0 || !italic))) {
+                    v->drawVisibleText(paint, x + 5, textY, label,
+                                       italic ?
+                                       View::OutlinedItalicText :
+                                       View::OutlinedText);
+                }
+            }
         }
 
-	if (label != "") {
-            // Quick test for 20px before we do the slower test using metrics
-            bool haveRoom = (nx > x + 20);
-            haveRoom = (haveRoom &&
-                        (nx > x + 6 + paint.fontMetrics().width(label)));
-            if (haveRoom ||
-                (!haveNext &&
-                 (pointCount == 0 || !italic))) {
-                v->drawVisibleText(paint, x + 5, textY, label,
-                                   italic ?
-                                   View::OutlinedItalicText :
-                                   View::OutlinedText);
-            }
-	}
-
         prevFrame = p.frame;
         ++pointCount;
     }
@@ -1233,7 +1243,7 @@
 void
 TimeValueLayer::paintVerticalScale(View *v, bool, QPainter &paint, QRect) const
 {
-    if (!m_model) return;
+    if (!m_model || m_model->getPoints().empty()) return;
 
     QString unit;
     float min, max;
--- a/layer/WaveformLayer.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/WaveformLayer.cpp	Fri May 09 17:15:50 2014 +0100
@@ -43,6 +43,7 @@
     m_channelMode(SeparateChannels),
     m_channel(-1),
     m_scale(LinearScale),
+    m_middleLineHeight(0.5),
     m_aggressive(false),
     m_cache(0),
     m_cacheValid(false)
@@ -306,6 +307,15 @@
 }
 
 void
+WaveformLayer::setMiddleLineHeight(float height)
+{
+    if (m_middleLineHeight == height) return;
+    m_middleLineHeight = height;
+    m_cacheValid = false;
+    emit layerParametersChanged();
+}
+
+void
 WaveformLayer::setAggressiveCacheing(bool aggressive)
 {
     if (m_aggressive == aggressive) return;
@@ -532,6 +542,15 @@
 
     paint->setRenderHint(QPainter::Antialiasing, false);
 
+    if (m_middleLineHeight != 0.5) {
+        paint->save();
+        float space = m_middleLineHeight * 2;
+        if (space > 1.0) space = 2.0 - space;
+        float yt = h * (m_middleLineHeight - space/2);
+        paint->translate(QPointF(0, yt));
+        paint->scale(1.0, space);
+    }
+
     int x0 = 0, x1 = w - 1;
     int y0 = 0, y1 = h - 1;
 
@@ -915,6 +934,10 @@
 	}
     }
 
+    if (m_middleLineHeight != 0.5) {
+        paint->restore();
+    }
+
     if (m_aggressive) {
 
 	if (ready && rect == v->rect()) {
@@ -1314,14 +1337,16 @@
 		 "channelMode=\"%4\" "
 		 "channel=\"%5\" "
                  "scale=\"%6\" "
-		 "aggressive=\"%7\" "
-                 "autoNormalize=\"%8\"")
+                 "middleLineHeight=\"%7\" "
+		 "aggressive=\"%8\" "
+                 "autoNormalize=\"%9\"")
 	.arg(m_gain)
 	.arg(m_showMeans)
 	.arg(m_greyscale)
 	.arg(m_channelMode)
 	.arg(m_channel)
 	.arg(m_scale)
+        .arg(m_middleLineHeight)
 	.arg(m_aggressive)
         .arg(m_autoNormalize);
 
@@ -1353,10 +1378,12 @@
     int channel = attributes.value("channel").toInt(&ok);
     if (ok) setChannel(channel);
 
-    Scale scale = (Scale)
-	attributes.value("scale").toInt(&ok);
+    Scale scale = (Scale)attributes.value("scale").toInt(&ok);
     if (ok) setScale(scale);
 
+    float middleLineHeight = attributes.value("middleLineHeight").toFloat(&ok);
+    if (ok) setMiddleLineHeight(middleLineHeight);
+
     bool aggressive = (attributes.value("aggressive") == "1" ||
 		       attributes.value("aggressive") == "true");
     setUseGreyscale(aggressive);
--- a/layer/WaveformLayer.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/layer/WaveformLayer.h	Fri May 09 17:15:50 2014 +0100
@@ -147,6 +147,19 @@
     Scale getScale() const { return m_scale; }
 
     /**
+     * Specify the height of the middle of the waveform track or
+     * tracks within the layer, from 0.0 to 1.0.
+     *
+     * A value of 0.0 would indicate that the waveform occupies
+     * effectively no space at the very top of the layer; 1.0 would
+     * indicate that the waveform occupies no space at the very
+     * bottom; the default value of 0.5 indicates that it occupies the
+     * whole layer, centred at the middle.
+     */
+    void setMiddleLineHeight(float height);
+    float getMiddleLineHeight() const { return m_middleLineHeight; }
+
+    /**
      * Enable or disable aggressive pixmap cacheing.  If enabled,
      * waveforms will be rendered to an off-screen pixmap and
      * refreshed from there instead of being redrawn from the peak
@@ -216,6 +229,7 @@
     ChannelMode  m_channelMode;
     int          m_channel;
     Scale        m_scale;
+    float        m_middleLineHeight;
     bool         m_aggressive;
 
     mutable std::vector<float> m_effectiveGains;
--- a/layer/layer.pro	Sat Apr 12 01:07:05 2014 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-TEMPLATE = lib
-
-SV_UNIT_PACKAGES = fftw3f
-load(../prf/sv.prf)
-
-CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
-QT += xml
-
-TARGET = svlayer
-
-DEPENDPATH += . ..
-INCLUDEPATH += . ..
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += Colour3DPlotLayer.h \
-	   ColourDatabase.h \
-	   ColourMapper.h \
-           ImageLayer.h \
-           ImageRegionFinder.h \
-           Layer.h \
-           LayerFactory.h \
-           NoteLayer.h \
-           PaintAssistant.h \
-           RegionLayer.h \
-           SingleColourLayer.h \
-           SliceableLayer.h \
-           SliceLayer.h \
-           SpectrogramLayer.h \
-           SpectrumLayer.h \
-           TextLayer.h \
-           TimeInstantLayer.h \
-           TimeRulerLayer.h \
-           TimeValueLayer.h \
-           WaveformLayer.h
-SOURCES += Colour3DPlotLayer.cpp \
-	   ColourDatabase.cpp \
-	   ColourMapper.cpp \
-           ImageLayer.cpp \
-           ImageRegionFinder.cpp \
-           Layer.cpp \
-           LayerFactory.cpp \
-           NoteLayer.cpp \
-           PaintAssistant.cpp \
-           RegionLayer.cpp \
-           SingleColourLayer.cpp \
-           SliceLayer.cpp \
-           SpectrogramLayer.cpp \
-           SpectrumLayer.cpp \
-           TextLayer.cpp \
-           TimeInstantLayer.cpp \
-           TimeRulerLayer.cpp \
-           TimeValueLayer.cpp \
-           WaveformLayer.cpp
--- a/svgui.pro	Sat Apr 12 01:07:05 2014 -0700
+++ b/svgui.pro	Fri May 09 17:15:50 2014 +0100
@@ -4,10 +4,25 @@
 exists(config.pri) {
     include(config.pri)
 }
-win* {
-    !exists(config.pri) {
-        DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG
+!exists(config.pri) {
+
+    CONFIG += release
+    DEFINES += NDEBUG BUILD_RELEASE NO_TIMING
+
+    win32-g++ {
+        INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
+        LIBS += -L../sv-dependency-builds/win32-mingw/lib
     }
+    win32-msvc* {
+        INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
+        LIBS += -L../sv-dependency-builds/win32-msvc/lib
+    }
+    macx* {
+        INCLUDEPATH += ../sv-dependency-builds/osx/include
+        LIBS += -L../sv-dependency-builds/osx/lib
+    }
+
+    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_RUBBERBAND HAVE_LIBLO HAVE_MAD HAVE_ID3TAG
 }
 
 CONFIG += staticlib qt thread warn_on stl rtti exceptions
@@ -20,17 +35,11 @@
 OBJECTS_DIR = o
 MOC_DIR = o
 
-win32-g++ {
-    INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include
-}
-win32-msvc* {
-    INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include
-}
-
 HEADERS += layer/Colour3DPlotLayer.h \
 	   layer/ColourDatabase.h \
 	   layer/ColourMapper.h \
            layer/ColourScaleLayer.h \
+           layer/FlexiNoteLayer.h \
            layer/ImageLayer.h \
            layer/ImageRegionFinder.h \
            layer/Layer.h \
@@ -57,6 +66,7 @@
 SOURCES += layer/Colour3DPlotLayer.cpp \
 	   layer/ColourDatabase.cpp \
 	   layer/ColourMapper.cpp \
+           layer/FlexiNoteLayer.cpp \
            layer/ImageLayer.cpp \
            layer/ImageRegionFinder.cpp \
            layer/Layer.cpp \
--- a/view/Pane.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/Pane.cpp	Fri May 09 17:15:50 2014 +0100
@@ -25,6 +25,11 @@
 #include "base/Preferences.h"
 #include "layer/WaveformLayer.h"
 
+// GF: added so we can propagate the mouse move event to the note layer for context handling.
+#include "layer/LayerFactory.h"
+#include "layer/FlexiNoteLayer.h"
+
+
 //!!! ugh
 #include "data/model/WaveFileModel.h"
 
@@ -85,6 +90,9 @@
     
     updateHeadsUpDisplay();
 
+    connect(this, SIGNAL(regionOutlined(QRect)), 
+            this, SLOT(zoomToRegion(QRect)));
+
     cerr << "Pane::Pane(" << this << ") returning" << endl;
 }
 
@@ -167,7 +175,7 @@
         }
 
         m_reset = new NotifyingPushButton;
-	m_reset->setFlat(true);
+        m_reset->setFlat(true);
         m_reset->setCursor(Qt::ArrowCursor);
         m_reset->setFixedHeight(16);
         m_reset->setFixedWidth(16);
@@ -327,19 +335,19 @@
     QPoint discard;
     bool b0, b1;
 
-    if (m_manager && m_manager->getToolMode() == ViewManager::MeasureMode) {
+    if (m_manager && m_manager->getToolModeFor(this) == ViewManager::MeasureMode) {
         return false;
     }
-
+    
     if (m_manager && !m_manager->shouldIlluminateLocalFeatures()) {
         return false;
     }
 
     if (layer == getSelectedLayer() &&
-	!shouldIlluminateLocalSelection(discard, b0, b1)) {
-
-	pos = m_identifyPoint;
-	return m_identifyFeatures;
+        !shouldIlluminateLocalSelection(discard, b0, b1)) {
+
+        pos = m_identifyPoint;
+        return m_identifyFeatures;
     }
 
     return false;
@@ -347,25 +355,25 @@
 
 bool
 Pane::shouldIlluminateLocalSelection(QPoint &pos,
-				     bool &closeToLeft,
-				     bool &closeToRight) const
+                     bool &closeToLeft,
+                     bool &closeToRight) const
 {
     if (m_identifyFeatures &&
-	m_manager &&
-	m_manager->getToolMode() == ViewManager::EditMode &&
-	!m_manager->getSelections().empty() &&
-	!selectionIsBeingEdited()) {
-
-	Selection s(getSelectionAt(m_identifyPoint.x(),
-				   closeToLeft, closeToRight));
-
-	if (!s.isEmpty()) {
-	    if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) {
-		
-		pos = m_identifyPoint;
-		return true;
-	    }
-	}
+        m_manager &&
+        m_manager->getToolModeFor(this) == ViewManager::EditMode &&
+        !m_manager->getSelections().empty() &&
+        !selectionIsBeingEdited()) {
+
+        Selection s(getSelectionAt(m_identifyPoint.x(),
+                                   closeToLeft, closeToRight));
+
+        if (!s.isEmpty()) {
+            if (getSelectedLayer() && getSelectedLayer()->isLayerEditable()) {
+            
+                pos = m_identifyPoint;
+                return true;
+            }
+        }
     }
 
     return false;
@@ -375,10 +383,10 @@
 Pane::selectionIsBeingEdited() const
 {
     if (!m_editingSelection.isEmpty()) {
-	if (m_mousePos != m_clickPos &&
-	    getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
-	    return true;
-	}
+    if (m_mousePos != m_clickPos &&
+        getFrameForX(m_mousePos.x()) != getFrameForX(m_clickPos.x())) {
+        return true;
+    }
     }
     return false;
 }
@@ -407,7 +415,7 @@
 
     if (e) paint.setClipRect(r);
 
-    ViewManager::ToolMode toolMode = m_manager->getToolMode();
+    ViewManager::ToolMode toolMode = m_manager->getToolModeFor(this);
 
     if (m_manager &&
 //        !m_manager->isPlaying() &&
@@ -457,6 +465,10 @@
 
     m_scaleWidth = 0;
 
+    if (workModel) {
+        drawModelTimeExtents(r, paint, workModel);
+    }
+
     if (m_manager && m_manager->shouldShowVerticalScale() && topLayer) {
         drawVerticalScale(r, topLayer, paint);
     }
@@ -625,9 +637,9 @@
         
     if (m_scaleWidth > 0 && r.left() < m_scaleWidth) {
 
-//	    Profiler profiler("Pane::paintEvent - painting vertical scale", true);
-
-//	    SVDEBUG << "Pane::paintEvent: calling paint.save() in vertical scale block" << endl;
+//      Profiler profiler("Pane::paintEvent - painting vertical scale", true);
+
+//      SVDEBUG << "Pane::paintEvent: calling paint.save() in vertical scale block" << endl;
         paint.save();
             
         paint.setPen(getForeground());
@@ -648,7 +660,7 @@
 {
     QPoint pos = m_identifyPoint;
     QString desc = topLayer->getFeatureDescription(this, pos);
-	    
+        
     if (desc != "") {
         
         paint.save();
@@ -725,7 +737,7 @@
     LayerList::iterator vi = m_layers.end();
     
     if (vi != m_layers.begin()) {
-	    
+        
         switch ((*--vi)->getPreferredFrameCountPosition()) {
             
         case Layer::PositionTop:
@@ -767,6 +779,39 @@
 }
 
 void
+Pane::drawModelTimeExtents(QRect r, QPainter &paint, const Model *model)
+{
+    int x0 = getXForFrame(model->getStartFrame());
+    int x1 = getXForFrame(model->getEndFrame());
+
+    int lw = 10;
+
+    paint.save();
+
+    QBrush brush;
+
+    if (hasLightBackground()) {
+        brush = QBrush(QColor("#f8f8f8"));
+        paint.setPen(Qt::black);
+    } else {
+        brush = QBrush(QColor("#101010"));
+        paint.setPen(Qt::white);
+    }
+
+    if (x0 > r.x()) {
+        paint.fillRect(0, 0, x0, height(), brush);
+        paint.drawLine(x0, 0, x0, height());
+    }
+
+    if (x1 < r.x() + r.width()) {
+        paint.fillRect(x1, 0, width() - x1, height(), brush);
+        paint.drawLine(x1, 0, x1, height());
+    }
+
+    paint.restore();
+}
+
+void
 Pane::drawAlignmentStatus(QRect r, QPainter &paint, const Model *model,
                           bool down)
 {
@@ -892,7 +937,7 @@
     }
     
     if (r.x() + r.width() >= llx - fontAscent - 3) {
-	
+    
         for (size_t i = 0; i < texts.size(); ++i) {
 
 //            cerr << "Pane "<< this << ": text " << i << ": " << texts[i] << endl;
@@ -1146,8 +1191,8 @@
 
     long testFrame = getFrameForX(x - 5);
     if (testFrame < 0) {
-	testFrame = getFrameForX(x);
-	if (testFrame < 0) return Selection();
+    testFrame = getFrameForX(x);
+    if (testFrame < 0) return Selection();
     }
 
     Selection selection = m_manager->getContainingSelection(testFrame, true);
@@ -1225,47 +1270,18 @@
                         tr("Double-click middle button to relocate with any tool"));
     kr.registerShortcut(tr("Menu"), tr("Right"),
                         tr("Show pane context menu"));
-    
-    kr.setCategory(tr("Navigate Tool Mouse Actions"));
-    
-    kr.registerShortcut(tr("Navigate"), tr("Left"), 
-                        tr("Click left button and drag to move around"));
-    kr.registerShortcut(tr("Zoom to Area"), tr("Shift+Left"), 
-                        tr("Shift-click left button and drag to zoom to a rectangular area"));
-    kr.registerShortcut(tr("Relocate"), tr("Double-Click Left"), 
-                        tr("Double-click left button to jump to clicked location"));
-    kr.registerShortcut(tr("Edit"), tr("Double-Click Left"), 
-                        tr("Double-click left button on an item to edit it"));
-        
-    kr.setCategory(tr("Select Tool Mouse Actions"));
-    kr.registerShortcut(tr("Select"), tr("Left"), 
-                        tr("Click left button and drag to select region; drag region edge to resize"));
-    kr.registerShortcut(tr("Multi Select"), tr("Ctrl+Left"), 
-#ifdef Q_OS_MAC
-                        tr("Cmd-click left button and drag to select an additional region"));
-#else
-                        tr("Ctrl-click left button and drag to select an additional region"));
-#endif
-    kr.registerShortcut(tr("Fine Select"), tr("Shift+Left"), 
-                        tr("Shift-click left button and drag to select without snapping to items or grid"));
-    
-    kr.setCategory(tr("Edit Tool Mouse Actions"));
-    kr.registerShortcut(tr("Move"), tr("Left"), 
-                        tr("Click left button on an item or selected region and drag to move"));
-    kr.registerShortcut(tr("Edit"), tr("Double-Click Left"), 
-                        tr("Double-click left button on an item to edit it"));
-    
-    kr.setCategory(tr("Draw Tool Mouse Actions"));
-    kr.registerShortcut(tr("Draw"), tr("Left"), 
-                        tr("Click left button and drag to create new item"));
-
-    kr.setCategory(tr("Measure Tool Mouse Actions"));
-    kr.registerShortcut(tr("Measure Area"), tr("Left"), 
-                        tr("Click left button and drag to measure a rectangular area"));
-    kr.registerShortcut(tr("Measure Item"), tr("Double-Click Left"), 
-                        tr("Click left button and drag to measure extents of an item or shape"));
-    kr.registerShortcut(tr("Zoom to Area"), tr("Shift+Left"), 
-                        tr("Shift-click left button and drag to zoom to a rectangular area"));
+}
+
+Layer *
+Pane::getTopFlexiNoteLayer()
+{
+    for (int i = int(m_layers.size()) - 1; i >= 0; --i) {
+        if (LayerFactory::getInstance()->getLayerType(m_layers[i]) ==
+            LayerFactory::FlexiNotes) {
+            return m_layers[i];
+        }
+    }
+    return 0;
 }
 
 void
@@ -1290,7 +1306,7 @@
     m_dragMode = UnresolvedDrag;
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     m_navigating = false;
     m_resizing = false;
@@ -1302,12 +1318,12 @@
         (mode == ViewManager::MeasureMode &&
          (e->buttons() & Qt::LeftButton) && m_shiftPressed)) {
 
-	if (mode != ViewManager::NavigateMode) {
-	    setCursor(Qt::PointingHandCursor);
-	}
-
-	m_navigating = true;
-	m_dragCentreFrame = m_centreFrame;
+        if (mode != ViewManager::NavigateMode) {
+            setCursor(Qt::PointingHandCursor);
+        }
+
+        m_navigating = true;
+        m_dragCentreFrame = m_centreFrame;
         m_dragStartMinValue = 0;
         
         float vmin, vmax, dmin, dmax;
@@ -1319,61 +1335,70 @@
 
         if (!hasTopLayerTimeXAxis()) return;
 
-	bool closeToLeft = false, closeToRight = false;
-	Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight);
-
-	if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
-
-	    m_manager->removeSelection(selection);
-
-	    if (closeToLeft) {
-		m_selectionStartFrame = selection.getEndFrame();
-	    } else {
-		m_selectionStartFrame = selection.getStartFrame();
-	    }
-
-	    m_manager->setInProgressSelection(selection, false);
-	    m_resizing = true;
-	
-	} else {
-
-	    int mouseFrame = getFrameForX(e->x());
-	    size_t resolution = 1;
-	    int snapFrame = mouseFrame;
-	
-	    Layer *layer = getSelectedLayer();
-	    if (layer && !m_shiftPressed) {
-		layer->snapToFeatureFrame(this, snapFrame,
-					  resolution, Layer::SnapLeft);
-	    }
-	    
-	    if (snapFrame < 0) snapFrame = 0;
-	    m_selectionStartFrame = snapFrame;
-	    if (m_manager) {
-		m_manager->setInProgressSelection
+        bool closeToLeft = false, closeToRight = false;
+        Selection selection = getSelectionAt(e->x(), closeToLeft, closeToRight);
+
+        if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
+
+            m_manager->removeSelection(selection);
+
+            if (closeToLeft) {
+                m_selectionStartFrame = selection.getEndFrame();
+            } else {
+                m_selectionStartFrame = selection.getStartFrame();
+            }
+            
+            m_manager->setInProgressSelection(selection, false);
+            m_resizing = true;
+            
+        } else {
+            
+            int mouseFrame = getFrameForX(e->x());
+            size_t resolution = 1;
+            int snapFrame = mouseFrame;
+    
+            Layer *layer = getSelectedLayer();
+            if (layer && !m_shiftPressed) {
+                layer->snapToFeatureFrame(this, snapFrame,
+                                          resolution, Layer::SnapLeft);
+            }
+        
+            if (snapFrame < 0) snapFrame = 0;
+            m_selectionStartFrame = snapFrame;
+            if (m_manager) {
+                m_manager->setInProgressSelection
                     (Selection(alignToReference(snapFrame),
                                alignToReference(snapFrame + resolution)),
                      !m_ctrlPressed);
-	    }
-
-	    m_resizing = false;
-	}
-
-	update();
+            }
+
+            m_resizing = false;
+        }
+
+        update();
 
     } else if (mode == ViewManager::DrawMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawStart(this, e);
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->drawStart(this, e);
+        }
 
     } else if (mode == ViewManager::EraseMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->eraseStart(this, e);
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->eraseStart(this, e);
+        }
+
+        // GF: handle mouse press for NoteEditMode 
+    } else if (mode == ViewManager::NoteEditMode) {
+
+        std::cerr << "mouse pressed in note edit mode" << std::endl;
+        Layer *layer = getTopFlexiNoteLayer();
+        if (layer) {
+            layer->splitStart(this, e); 
+        }
 
     } else if (mode == ViewManager::EditMode) {
 
@@ -1400,33 +1425,33 @@
 //    cerr << "mouseReleaseEvent" << endl;
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     m_releasing = true;
 
     if (m_clickedInRange) {
-	mouseMoveEvent(e);
+        mouseMoveEvent(e);
     }
 
     if (m_navigating || mode == ViewManager::NavigateMode) {
 
-	m_navigating = false;
-
-	if (mode != ViewManager::NavigateMode) {
-	    // restore cursor
-	    toolModeChanged();
-	}
-
-	if (m_shiftPressed) {
-
-	    int x0 = std::min(m_clickPos.x(), m_mousePos.x());
-	    int x1 = std::max(m_clickPos.x(), m_mousePos.x());
-
-	    int y0 = std::min(m_clickPos.y(), m_mousePos.y());
-	    int y1 = std::max(m_clickPos.y(), m_mousePos.y());
-
-            zoomToRegion(x0, y0, x1, y1);
-	}
+        m_navigating = false;
+
+        if (mode != ViewManager::NavigateMode) {
+            // restore cursor
+            toolModeChanged();
+        }
+
+        if (m_shiftPressed) {
+
+            int x0 = std::min(m_clickPos.x(), m_mousePos.x());
+            int x1 = std::max(m_clickPos.x(), m_mousePos.x());
+
+            int y0 = std::min(m_clickPos.y(), m_mousePos.y());
+            int y1 = std::max(m_clickPos.y(), m_mousePos.y());
+
+            emit regionOutlined(QRect(x0, y0, x1 - x0, y1 - y0));
+        }
 
     } else if (mode == ViewManager::SelectMode) {
 
@@ -1435,44 +1460,72 @@
             return;
         }
 
-	if (m_manager && m_manager->haveInProgressSelection()) {
-
-	    bool exclusive;
-	    Selection selection = m_manager->getInProgressSelection(exclusive);
-	    
-	    if (selection.getEndFrame() < selection.getStartFrame() + 2) {
-		selection = Selection();
-	    }
-	    
-	    m_manager->clearInProgressSelection();
-	    
-	    if (exclusive) {
-		m_manager->setSelection(selection);
-	    } else {
-		m_manager->addSelection(selection);
-	    }
-	}
-	
-	update();
+        if (m_manager && m_manager->haveInProgressSelection()) {
+
+            //cerr << "JTEST: release with selection" << endl;
+            bool exclusive;
+            Selection selection = m_manager->getInProgressSelection(exclusive);
+        
+            if (selection.getEndFrame() < selection.getStartFrame() + 2) {
+                selection = Selection();
+            }
+        
+            m_manager->clearInProgressSelection();
+        
+            if (exclusive) {
+                m_manager->setSelection(selection);
+            } else {
+                m_manager->addSelection(selection);
+            }
+        }
+        else if (m_manager && !m_manager->haveInProgressSelection()) {
+            
+            //cerr << "JTEST: release without selection" << endl;
+            // Get frame location of mouse
+            int mouseFrame = getFrameForX(e->x());
+            //cerr << "JTEST: frame location of click is " << mouseFrame << endl;
+            // Move play head to that frame location
+            int playbackFrame = fmax(0,mouseFrame);
+            m_manager->setPlaybackFrame(playbackFrame);
+        }
+    
+        update();
 
     } else if (mode == ViewManager::DrawMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawEnd(this, e);
-	    update();
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->drawEnd(this, e);
+            update();
+        }
 
     } else if (mode == ViewManager::EraseMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->eraseEnd(this, e);
-	    update();
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->eraseEnd(this, e);
+            update();
+        }
+
+    } else if (mode == ViewManager::NoteEditMode) {
+    
+        //GF: handle mouse release for NoteEditMode (note: works but will need to re-think this a bit later)
+        Layer *layer = getTopFlexiNoteLayer();
+
+        if (layer) {
+            layer->splitEnd(this, e);
+            update();
+
+            if (m_editing) {
+                if (!editSelectionEnd(e)) {
+                    layer->editEnd(this, e);
+                    update();
+                }
+            }
+        } 
 
     } else if (mode == ViewManager::EditMode) {
-
+        
         if (m_editing) {
             if (!editSelectionEnd(e)) {
                 Layer *layer = getSelectedLayer();
@@ -1481,7 +1534,7 @@
                     update();
                 }
             }
-        }
+        } 
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -1524,22 +1577,32 @@
     }
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     QPoint prevPoint = m_identifyPoint;
     m_identifyPoint = e->pos();
 
     if (!m_clickedInRange) {
-	
-	if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) {
-	    bool closeToLeft = false, closeToRight = false;
-	    getSelectionAt(e->x(), closeToLeft, closeToRight);
-	    if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
-		setCursor(Qt::SizeHorCursor);
-	    } else {
-		setCursor(Qt::ArrowCursor);
-	    }
-	}
+    
+        // GF: handle mouse move for context sensitive cursor switching in NoteEditMode.
+        // GF: Propagate the event to FlexiNoteLayer. I somehow feel it's best handeled there rather than here, but perhaps not if this will be needed elsewhere too.
+        if (mode == ViewManager::NoteEditMode) {
+            FlexiNoteLayer *layer = qobject_cast<FlexiNoteLayer *>(getTopFlexiNoteLayer());
+            if (layer) {
+                layer->mouseMoveEvent(this, e); //!!! ew
+                return;
+            }
+        }   
+    
+        if (mode == ViewManager::SelectMode && hasTopLayerTimeXAxis()) {
+            bool closeToLeft = false, closeToRight = false;
+            getSelectionAt(e->x(), closeToLeft, closeToRight);
+            if ((closeToLeft || closeToRight) && !(closeToLeft && closeToRight)) {
+                setCursor(Qt::SizeHorCursor);
+            } else {
+                setCursor(Qt::ArrowCursor);
+            }
+        }
 
         if (!m_manager->isPlaying()) {
 
@@ -1569,17 +1632,17 @@
             }
         }
 
-	return;
+        return;
     }
 
     if (m_navigating || mode == ViewManager::NavigateMode) {
 
-	if (m_shiftPressed) {
-
-	    m_mousePos = e->pos();
-	    update();
-
-	} else {
+        if (m_shiftPressed) {
+
+            m_mousePos = e->pos();
+            update();
+
+        } else {
 
             dragTopLayer(e);
         }
@@ -1592,17 +1655,82 @@
 
     } else if (mode == ViewManager::DrawMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->drawDrag(this, e);
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->drawDrag(this, e);
+        }
 
     } else if (mode == ViewManager::EraseMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    layer->eraseDrag(this, e);
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->eraseDrag(this, e);
+        }
+
+        // GF: handling NoteEditMode dragging and boundary actions for mouseMoveEvent
+    } else if (mode == ViewManager::NoteEditMode) {
+
+        bool resist = true;
+
+        if ((e->modifiers() & Qt::ShiftModifier)) {
+            m_shiftPressed = true;
+        }
+
+        if (m_shiftPressed) resist = false;
+
+        m_dragMode = updateDragMode
+            (m_dragMode,
+             m_clickPos,
+             e->pos(),
+             true,    // can move horiz
+             true,    // can move vert
+             resist,  // resist horiz
+             resist); // resist vert
+
+        if (!m_editing) {
+
+            if (m_dragMode != UnresolvedDrag) {
+
+                m_editing = true;
+
+                QMouseEvent clickEvent(QEvent::MouseButtonPress,
+                                       m_clickPos,
+                                       Qt::NoButton,
+                                       e->buttons(),
+                                       e->modifiers());
+
+                if (!editSelectionStart(&clickEvent)) {
+                    Layer *layer = getTopFlexiNoteLayer();
+                    if (layer) {
+                        std::cerr << "calling edit start" << std::endl;
+                        layer->editStart(this, &clickEvent);
+                    }
+                }
+            }
+
+        } else {
+
+            if (!editSelectionDrag(e)) {
+
+                Layer *layer = getSelectedLayer();
+
+                if (layer && layer->isLayerEditable()) {
+
+                    int x = e->x();
+                    int y = e->y();
+                    if (m_dragMode == VerticalDrag) x = m_clickPos.x();
+                    else if (m_dragMode == HorizontalDrag) y = m_clickPos.y();
+
+                    QMouseEvent moveEvent(QEvent::MouseMove,
+                                          QPoint(x, y),
+                                          Qt::NoButton,
+                                          e->buttons(),
+                                          e->modifiers());
+                    std::cerr << "calling editDrag" << std::endl;
+                    layer->editDrag(this, &moveEvent);
+                }
+            }
+        }
 
     } else if (mode == ViewManager::EditMode) {
 
@@ -1685,21 +1813,26 @@
 }
 
 void
-Pane::zoomToRegion(int x0, int y0, int x1, int y1)
+Pane::zoomToRegion(QRect r)
 {
+    int x0 = r.x();
+    int y0 = r.y();
+    int x1 = r.x() + r.width();
+    int y1 = r.y() + r.height();
+
     int w = x1 - x0;
-	    
+        
     long newStartFrame = getFrameForX(x0);
-	    
+        
     long visibleFrames = getEndFrame() - getStartFrame();
     if (newStartFrame <= -visibleFrames) {
         newStartFrame  = -visibleFrames + 1;
     }
-	    
+        
     if (newStartFrame >= long(getModelsEndFrame())) {
         newStartFrame  = getModelsEndFrame() - 1;
     }
-	    
+        
     float ratio = float(w) / float(width());
 //	cerr << "ratio: " << ratio << endl;
     size_t newZoomLevel = (size_t)nearbyint(m_zoomLevel * ratio);
@@ -1784,7 +1917,7 @@
         long frameOff = getFrameForX(e->x()) - getFrameForX(m_clickPos.x());
 
         size_t newCentreFrame = m_dragCentreFrame;
-	    
+        
         if (frameOff < 0) {
             newCentreFrame -= frameOff;
         } else if (newCentreFrame >= size_t(frameOff)) {
@@ -1793,7 +1926,7 @@
             newCentreFrame = 0;
         }
 
-#ifdef DEBUG_PANE	    
+#ifdef DEBUG_PANE       
         SVDEBUG << "Pane::dragTopLayer: newCentreFrame = " << newCentreFrame <<
             ", models end frame = " << getModelsEndFrame() << endl;
 #endif
@@ -1912,7 +2045,7 @@
     size_t resolution = 1;
     int snapFrameLeft = mouseFrame;
     int snapFrameRight = mouseFrame;
-	
+    
     Layer *layer = getSelectedLayer();
     if (layer && !m_shiftPressed) {
         layer->snapToFeatureFrame(this, snapFrameLeft,
@@ -1925,9 +2058,9 @@
 
     if (snapFrameLeft < 0) snapFrameLeft = 0;
     if (snapFrameRight < 0) snapFrameRight = 0;
-	
+    
     size_t min, max;
-	
+    
     if (m_selectionStartFrame > size_t(snapFrameLeft)) {
         min = snapFrameLeft;
         max = m_selectionStartFrame;
@@ -1993,18 +2126,25 @@
     m_altPressed = (e->modifiers() & Qt::AltModifier);
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     bool relocate = (mode == ViewManager::NavigateMode ||
                      (e->buttons() & Qt::MidButton));
 
+    if (mode == ViewManager::SelectMode) {
+        m_clickedInRange = false;
+        m_manager->clearInProgressSelection();
+        emit doubleClickSelectInvoked(getFrameForX(e->x()));
+        return;
+    }
+
     if (mode == ViewManager::NavigateMode ||
         mode == ViewManager::EditMode) {
 
-	Layer *layer = getSelectedLayer();
-	if (layer && layer->isLayerEditable()) {
-	    if (layer->editOpen(this, e)) relocate = false;
-	}
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            if (layer->editOpen(this, e)) relocate = false;
+        }
 
     } else if (mode == ViewManager::MeasureMode) {
 
@@ -2028,6 +2168,14 @@
             m_dragStartMinValue = dmin;
         }
     }
+    
+    if (mode == ViewManager::NoteEditMode) {
+        std::cerr << "double click in note edit mode" << std::endl;
+        Layer *layer = getSelectedLayer();
+        if (layer && layer->isLayerEditable()) {
+            layer->addNote(this, e); 
+        }
+    }
 
     m_clickedInRange = false; // in case mouseReleaseEvent is not properly called
 }
@@ -2062,31 +2210,31 @@
     int count = e->delta();
 
     if (count > 0) {
-	if (count >= 120) count /= 120;
-	else count = 1;
+    if (count >= 120) count /= 120;
+    else count = 1;
     } 
 
     if (count < 0) {
-	if (count <= -120) count /= 120;
-	else count = -1;
+    if (count <= -120) count /= 120;
+    else count = -1;
     }
 
     if (e->modifiers() & Qt::ControlModifier) {
 
-	// Scroll left or right, rapidly
-
-	if (getStartFrame() < 0 && 
-	    getEndFrame() >= getModelsEndFrame()) return;
-
-	long delta = ((width() / 2) * count * m_zoomLevel);
-
-	if (int(m_centreFrame) < delta) {
-	    setCentreFrame(0);
-	} else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
-	    setCentreFrame(getModelsEndFrame());
-	} else {
-	    setCentreFrame(m_centreFrame - delta);
-	}
+    // Scroll left or right, rapidly
+
+    if (getStartFrame() < 0 && 
+        getEndFrame() >= getModelsEndFrame()) return;
+
+    long delta = ((width() / 2) * count * m_zoomLevel);
+
+    if (int(m_centreFrame) < delta) {
+        setCentreFrame(0);
+    } else if (int(m_centreFrame) - delta >= int(getModelsEndFrame())) {
+        setCentreFrame(getModelsEndFrame());
+    } else {
+        setCentreFrame(m_centreFrame - delta);
+    }
 
     } else if (e->modifiers() & Qt::ShiftModifier) {
 
@@ -2106,29 +2254,29 @@
 
     } else {
 
-	// Zoom in or out
-
-	int newZoomLevel = m_zoomLevel;
+    // Zoom in or out
+
+    int newZoomLevel = m_zoomLevel;
   
-	while (count > 0) {
-	    if (newZoomLevel <= 2) {
-		newZoomLevel = 1;
-		break;
-	    }
-	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
-						      ZoomConstraint::RoundDown);
-	    --count;
-	}
-	
-	while (count < 0) {
-	    newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
-						      ZoomConstraint::RoundUp);
-	    ++count;
-	}
-	
-	if (newZoomLevel != m_zoomLevel) {
-	    setZoomLevel(newZoomLevel);
-	}
+    while (count > 0) {
+        if (newZoomLevel <= 2) {
+        newZoomLevel = 1;
+        break;
+        }
+        newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, 
+                              ZoomConstraint::RoundDown);
+        --count;
+    }
+    
+    while (count < 0) {
+        newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1,
+                              ZoomConstraint::RoundUp);
+        ++count;
+    }
+    
+    if (newZoomLevel != m_zoomLevel) {
+        setZoomLevel(newZoomLevel);
+    }
     }
 
     emit paneInteractedWith();
@@ -2306,9 +2454,9 @@
 Pane::editSelectionStart(QMouseEvent *e)
 {
     if (!m_identifyFeatures ||
-	!m_manager ||
-	m_manager->getToolMode() != ViewManager::EditMode) {
-	return false;
+        !m_manager ||
+        m_manager->getToolModeFor(this) != ViewManager::EditMode) {
+        return false;
     }
 
     bool closeToLeft, closeToRight;
@@ -2338,8 +2486,8 @@
     Layer *layer = getSelectedLayer();
 
     if (offset == 0 || !layer) {
-	m_editingSelection = Selection();
-	return true;
+        m_editingSelection = Selection();
+        return true;
     }
 
     int p0 = getXForFrame(m_editingSelection.getStartFrame()) + offset;
@@ -2351,25 +2499,25 @@
     Selection newSelection(f0, f1);
     
     if (m_editingSelectionEdge == 0) {
-	
+    
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Drag Selection"), true);
 
-	layer->moveSelection(m_editingSelection, f0);
-	
+        layer->moveSelection(m_editingSelection, f0);
+    
     } else {
-	
+    
         CommandHistory::getInstance()->startCompoundOperation
             (tr("Resize Selection"), true);
 
-	if (m_editingSelectionEdge < 0) {
-	    f1 = m_editingSelection.getEndFrame();
-	} else {
-	    f0 = m_editingSelection.getStartFrame();
-	}
-
-	newSelection = Selection(f0, f1);
-	layer->resizeSelection(m_editingSelection, newSelection);
+        if (m_editingSelectionEdge < 0) {
+            f1 = m_editingSelection.getEndFrame();
+        } else {
+            f0 = m_editingSelection.getStartFrame();
+        }
+
+        newSelection = Selection(f0, f1);
+        layer->resizeSelection(m_editingSelection, newSelection);
     }
     
     m_manager->removeSelection(m_editingSelection);
@@ -2384,7 +2532,7 @@
 void
 Pane::toolModeChanged()
 {
-    ViewManager::ToolMode mode = m_manager->getToolMode();
+    ViewManager::ToolMode mode = m_manager->getToolModeFor(this);
 //    SVDEBUG << "Pane::toolModeChanged(" << mode << ")" << endl;
 
     if (mode == ViewManager::MeasureMode && !m_measureCursor1) {
@@ -2399,33 +2547,38 @@
     switch (mode) {
 
     case ViewManager::NavigateMode:
-	setCursor(Qt::PointingHandCursor);
-	break;
-	
+        setCursor(Qt::PointingHandCursor);
+        break;
+    
     case ViewManager::SelectMode:
-	setCursor(Qt::ArrowCursor);
-	break;
-	
+        setCursor(Qt::ArrowCursor);
+        break;
+    
     case ViewManager::EditMode:
-	setCursor(Qt::UpArrowCursor);
-	break;
-	
+        setCursor(Qt::UpArrowCursor);
+        break;
+    
     case ViewManager::DrawMode:
-	setCursor(Qt::CrossCursor);
-	break;
-	
+        setCursor(Qt::CrossCursor);
+        break;
+    
     case ViewManager::EraseMode:
-	setCursor(Qt::CrossCursor);
-	break;
+        setCursor(Qt::CrossCursor);
+        break;
 
     case ViewManager::MeasureMode:
         if (m_measureCursor1) setCursor(*m_measureCursor1);
-	break;
-
-/*	
+        break;
+
+        // GF: NoteEditMode uses the same default cursor as EditMode, but it will change in a context sensitive manner.
+    case ViewManager::NoteEditMode:
+        setCursor(Qt::UpArrowCursor);
+        break;
+
+/*  
     case ViewManager::TextMode:
-	setCursor(Qt::IBeamCursor);
-	break;
+    setCursor(Qt::IBeamCursor);
+    break;
 */
     }
 }
@@ -2509,7 +2662,7 @@
     }
 
     ViewManager::ToolMode mode = ViewManager::NavigateMode;
-    if (m_manager) mode = m_manager->getToolMode();
+    if (m_manager) mode = m_manager->getToolModeFor(this);
 
     bool editable = false;
     Layer *layer = getSelectedLayer();
@@ -2561,21 +2714,21 @@
     } else if (mode == ViewManager::DrawMode) {
         
         //!!! could call through to a layer function to find out exact meaning
-	if (editable) {
+        if (editable) {
             help = tr("Click to add a new item in the active layer");
         }
 
     } else if (mode == ViewManager::EraseMode) {
         
         //!!! could call through to a layer function to find out exact meaning
-	if (editable) {
+        if (editable) {
             help = tr("Click to erase an item from the active layer");
         }
         
     } else if (mode == ViewManager::EditMode) {
         
         //!!! could call through to layer
-	if (editable) {
+        if (editable) {
             help = tr("Click and drag an item in the active layer to move it; hold Shift to override initial resistance");
             if (pos) {
                 bool closeToLeft = false, closeToRight = false;
@@ -2619,8 +2772,8 @@
 {
     View::toXml
         (stream, indent,
-	 QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3")
-	 .arg(m_centreLineVisible).arg(height()).arg(extraAttributes));
+     QString("type=\"pane\" centreLineVisible=\"%1\" height=\"%2\" %3")
+     .arg(m_centreLineVisible).arg(height()).arg(extraAttributes));
 }
 
 
--- a/view/Pane.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/Pane.h	Fri May 09 17:15:50 2014 +0100
@@ -67,6 +67,8 @@
     void rightButtonMenuRequested(QPoint position);
     void dropAccepted(QStringList uriList);
     void dropAccepted(QString text);
+    void doubleClickSelectInvoked(size_t frame);
+    void regionOutlined(QRect rect);
 
 public slots:
     virtual void toolModeChanged();
@@ -84,6 +86,8 @@
 
     virtual void propertyContainerSelected(View *, PropertyContainer *pc);
 
+    void zoomToRegion(QRect r);
+
     void mouseEnteredWidget();
     void mouseLeftWidget();
 
@@ -103,6 +107,7 @@
     void drawVerticalScale(QRect r, Layer *, QPainter &);
     void drawFeatureDescription(Layer *, QPainter &);
     void drawCentreLine(int, QPainter &, bool omitLine);
+    void drawModelTimeExtents(QRect, QPainter &, const Model *);
     void drawDurationAndRate(QRect, const Model *, int, QPainter &);
     void drawWorkTitle(QRect, QPainter &, const Model *);
     void drawLayerNames(QRect, QPainter &);
@@ -129,10 +134,11 @@
 
     void dragTopLayer(QMouseEvent *e);
     void dragExtendSelection(QMouseEvent *e);
-    void zoomToRegion(int x0, int y0, int x1, int y1);
     void updateContextHelp(const QPoint *pos);
     void edgeScrollMaybe(int x);
 
+    Layer *getTopFlexiNoteLayer();
+
     bool m_identifyFeatures;
     QPoint m_identifyPoint;
     QPoint m_clickPos;
--- a/view/PaneStack.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/PaneStack.cpp	Fri May 09 17:15:50 2014 +0100
@@ -39,6 +39,7 @@
 PaneStack::PaneStack(QWidget *parent, ViewManager *viewManager) :
     QFrame(parent),
     m_currentPane(0),
+    m_showAccessories(true),
     m_splitter(new QSplitter),
     m_propertyStackStack(new QStackedWidget),
     m_viewManager(viewManager),
@@ -60,6 +61,12 @@
     setLayout(layout);
 }
 
+void
+PaneStack::setShowPaneAccessories(bool show)
+{
+    m_showAccessories = show;
+}
+
 Pane *
 PaneStack::addPane(bool suppressPropertyBox)
 {
@@ -79,6 +86,7 @@
     xButton->setIcon(IconLoader().load("cross"));
     xButton->setFixedSize(QSize(16, 16));
     xButton->setFlat(true);
+    xButton->setVisible(m_showAccessories);
     layout->addWidget(xButton, 0, 0);
     connect(xButton, SIGNAL(clicked()), this, SLOT(paneDeleteButtonClicked()));
 
@@ -88,6 +96,7 @@
     layout->setRowStretch(1, 20);
     currentIndicator->setMinimumWidth(8);
     currentIndicator->setScaledContents(true);
+    currentIndicator->setVisible(m_showAccessories);
 
     long initialCentreFrame = -1;
     for (int i = 0; i < m_panes.size(); ++i) {
@@ -149,6 +158,8 @@
             this, SLOT(paneDropAccepted(QStringList)));
     connect(pane, SIGNAL(dropAccepted(QString)),
             this, SLOT(paneDropAccepted(QString)));
+    connect(pane, SIGNAL(doubleClickSelectInvoked(size_t)),
+            this, SIGNAL(doubleClickSelectInvoked(size_t)));
 
     emit paneAdded(pane);
     emit paneAdded();
@@ -306,8 +317,8 @@
     bool multi = (getPaneCount() > 1);
     for (std::vector<PaneRec>::iterator i = m_panes.begin();
          i != m_panes.end(); ++i) {
-        i->xButton->setVisible(multi);
-        i->currentIndicator->setVisible(multi);
+        i->xButton->setVisible(multi && m_showAccessories);
+        i->currentIndicator->setVisible(multi && m_showAccessories);
     }
 }
 
@@ -598,24 +609,44 @@
 
     int count = sizes.size();
 
-    int total = 0;
+    int fixed = 0, variable = 0, total = 0;
+    int varicount = 0;
+
     for (int i = 0; i < count; ++i) {
         total += sizes[i];
     }
 
+    variable = total;
+
+    for (int i = 0; i < count; ++i) {
+        int minh = m_panes[i].pane->minimumSize().height();
+        if (minh == m_panes[i].pane->maximumSize().height()) {
+            fixed += minh;
+            variable -= minh;
+        } else {
+            varicount++;
+        }
+    }
+
     if (total == 0) return;
 
     sizes.clear();
 
-    int each = total / count;
+    int each = (varicount > 0 ? (variable / varicount) : 0);
     int remaining = total;
 
     for (int i = 0; i < count; ++i) {
         if (i == count - 1) {
             sizes.push_back(remaining);
         } else {
-            sizes.push_back(each);
-            remaining -= each;
+            int minh = m_panes[i].pane->minimumSize().height();
+            if (minh == m_panes[i].pane->maximumSize().height()) {
+                sizes.push_back(minh);
+                remaining -= minh;
+            } else {
+                sizes.push_back(each);
+                remaining -= each;
+            }
         }
     }
 
@@ -630,4 +661,3 @@
     m_splitter->setSizes(sizes);
 }
 
-
--- a/view/PaneStack.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/PaneStack.h	Fri May 09 17:15:50 2014 +0100
@@ -68,6 +68,8 @@
     void setLayoutStyle(LayoutStyle style);
 
     void setPropertyStackMinWidth(int mw);
+    
+    void setShowPaneAccessories(bool show); // current indicator, close button
 
     void sizePanesEqually();
 
@@ -91,6 +93,8 @@
 
     void paneDeleteButtonClicked(Pane *pane);
 
+    void doubleClickSelectInvoked(size_t frame);
+
 public slots:
     void propertyContainerAdded(PropertyContainer *);
     void propertyContainerRemoved(PropertyContainer *);
@@ -119,6 +123,8 @@
     std::vector<PaneRec> m_panes;
     std::vector<PaneRec> m_hiddenPanes;
 
+    bool m_showAccessories;
+
     QSplitter *m_splitter;
     QStackedWidget *m_propertyStackStack;
 
--- a/view/View.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/View.cpp	Fri May 09 17:15:50 2014 +0100
@@ -626,7 +626,15 @@
 View::getSelectedLayer()
 {
     if (m_haveSelectedLayer && !m_layers.empty()) {
-	return getLayer(getLayerCount() - 1);
+        int n = getLayerCount();
+        while (n > 0) {
+            --n;
+            Layer *layer = getLayer(n);
+            if (!(layer->isLayerDormant(this))) {
+                return layer;
+            }
+        }
+        return 0;
     } else {
 	return 0;
     }
--- a/view/View.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/View.h	Fri May 09 17:15:50 2014 +0100
@@ -200,6 +200,9 @@
     virtual void drawMeasurementRect(QPainter &p, const Layer *,
                                      QRect rect, bool focus) const;
 
+    virtual bool shouldShowFeatureLabels() const {
+        return m_manager && m_manager->shouldShowFeatureLabels();
+    }
     virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const {
 	return false;
     }
--- a/view/ViewManager.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/ViewManager.cpp	Fri May 09 17:15:50 2014 +0100
@@ -43,7 +43,7 @@
     m_playSelectionMode(false),
     m_playSoloMode(false),
     m_alignMode(false),
-    m_overlayMode(MinimalOverlays),
+    m_overlayMode(StandardOverlays),
     m_zoomWheelsEnabled(true),
     m_showCentreLine(true),
     m_illuminateLocalFeatures(true),
@@ -57,9 +57,9 @@
         (settings.value("overlay-mode", int(m_overlayMode)).toInt());
 
     if (m_overlayMode != NoOverlays &&
-        m_overlayMode != MinimalOverlays &&
+        m_overlayMode != StandardOverlays &&
         m_overlayMode != AllOverlays) {
-        m_overlayMode = MinimalOverlays;
+        m_overlayMode = StandardOverlays;
     }
 
     m_zoomWheelsEnabled =
@@ -258,6 +258,14 @@
 }
 
 void
+ViewManager::addSelectionQuietly(const Selection &selection)
+{
+    MultiSelection ms(m_selections);
+    ms.addSelection(selection);
+    setSelections(ms, true);
+}
+
+void
 ViewManager::removeSelection(const Selection &selection)
 {
     MultiSelection ms(m_selections);
@@ -274,11 +282,14 @@
 }
 
 void
-ViewManager::setSelections(const MultiSelection &ms)
+ViewManager::setSelections(const MultiSelection &ms, bool quietly)
 {
     if (m_selections.getSelections() == ms.getSelections()) return;
     SetSelectionCommand *command = new SetSelectionCommand(this, ms);
     CommandHistory::getInstance()->addCommand(command);
+    if (!quietly) {
+        emit selectionChangedByUser();
+    }
 }
 
 size_t
@@ -360,9 +371,32 @@
     case DrawMode: emit activity(tr("Enter Draw mode")); break;
     case EraseMode: emit activity(tr("Enter Erase mode")); break;
     case MeasureMode: emit activity(tr("Enter Measure mode")); break;
+    case NoteEditMode: emit activity(tr("Enter NoteEdit mode")); break; // GF: NoteEditMode activity (I'm not yet certain why we need to emit this.)
     };
 }
 
+ViewManager::ToolMode
+ViewManager::getToolModeFor(const View *v) const
+{
+    if (m_toolModeOverrides.find(v) == m_toolModeOverrides.end()) {
+        return getToolMode();
+    } else {
+        return m_toolModeOverrides.find(v)->second;
+    }
+}
+
+void
+ViewManager::setToolModeFor(const View *v, ToolMode mode)
+{
+    m_toolModeOverrides[v] = mode;
+}
+
+void
+ViewManager::clearToolModeOverrides()
+{
+    m_toolModeOverrides.clear();
+}
+
 void
 ViewManager::setPlayLoopMode(bool mode)
 {
@@ -646,11 +680,13 @@
         m_lightPalette = QApplication::palette();
     }
 
+#ifndef Q_OS_MAC
     if (dark) {
         QApplication::setPalette(m_darkPalette);
     } else {
         QApplication::setPalette(m_lightPalette);
     }
+#endif
 }
 
 bool
--- a/view/ViewManager.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/view/ViewManager.h	Fri May 09 17:15:50 2014 +0100
@@ -86,6 +86,13 @@
     size_t constrainFrameToSelection(size_t frame) const;
 
     /**
+     * Adding a selection normally emits the selectionChangedByUser
+     * signal. Call this to add a selection without emitting that signal.
+     * This is used in session file load, for example.
+     */
+    void addSelectionQuietly(const Selection &selection);
+
+    /**
      * Return the selection that contains a given frame.
      * If defaultToFollowing is true, and if the frame is not in a
      * selected area, return the next selection after the given frame.
@@ -101,11 +108,19 @@
         EditMode,
 	DrawMode,
 	EraseMode,
-	MeasureMode
+	MeasureMode,
+	NoteEditMode //GF: Tonioni: this tool mode will be context sensitive.
     };
     ToolMode getToolMode() const { return m_toolMode; }
     void setToolMode(ToolMode mode);
 
+    /// Override the tool mode for a specific view 
+    void setToolModeFor(const View *v, ToolMode mode);
+    /// Return override mode if it exists for this view or global mode otherwise
+    ToolMode getToolModeFor(const View *v) const;
+    /// Clear all current view-specific overrides
+    void clearToolModeOverrides();
+
     bool getPlayLoopMode() const { return m_playLoopMode; }
     void setPlayLoopMode(bool on);
 
@@ -147,7 +162,8 @@
 
     enum OverlayMode {
         NoOverlays,
-        MinimalOverlays,
+        GlobalOverlays,
+        StandardOverlays,
         AllOverlays
     };
     void setOverlayMode(OverlayMode mode);
@@ -169,7 +185,7 @@
         return m_overlayMode == AllOverlays;
     }
     bool shouldShowSelectionExtents() const {
-        return m_overlayMode != NoOverlays;
+        return m_overlayMode != NoOverlays && m_overlayMode != GlobalOverlays;
     }
     bool shouldShowLayerNames() const {
         return m_overlayMode == AllOverlays;
@@ -183,6 +199,9 @@
     bool shouldIlluminateLocalFeatures() const {
         return m_illuminateLocalFeatures;
     }
+    bool shouldShowFeatureLabels() const {
+        return m_overlayMode != NoOverlays && m_overlayMode != GlobalOverlays;
+    }
 
     void setZoomWheelsEnabled(bool enable);
     bool getZoomWheelsEnabled() const { return m_zoomWheelsEnabled; }
@@ -206,9 +225,14 @@
     /** Emitted when the output levels change. Values in range 0.0 -> 1.0. */
     void outputLevelsChanged(float left, float right);
 
-    /** Emitted when the selection has changed. */
+    /** Emitted whenever the selection has changed. */
     void selectionChanged();
 
+    /** Emitted when the selection has been changed through an
+     * explicit selection-editing action. *Not* emitted when the
+     * selection has been changed through undo or redo. */
+    void selectionChangedByUser();
+
     /** Emitted when the in-progress (rubberbanding) selection has changed. */
     void inProgressSelectionChanged();
 
@@ -273,13 +297,14 @@
     Clipboard m_clipboard;
 
     ToolMode m_toolMode;
+    std::map<const View *, ToolMode> m_toolModeOverrides;
 
     bool m_playLoopMode;
     bool m_playSelectionMode;
     bool m_playSoloMode;
     bool m_alignMode;
 
-    void setSelections(const MultiSelection &ms);
+    void setSelections(const MultiSelection &ms, bool quietly = false);
     void signalSelectionChange();
 
     class SetSelectionCommand : public Command
--- a/view/view.pro	Sat Apr 12 01:07:05 2014 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-TEMPLATE = lib
-
-SV_UNIT_PACKAGES =
-load(../prf/sv.prf)
-
-CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
-QT += xml
-
-TARGET = svview
-
-DEPENDPATH += . ..
-INCLUDEPATH += . ..
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += Overview.h \
-           Pane.h \
-           PaneStack.h \
-           View.h \
-           ViewManager.h
-SOURCES += Overview.cpp \
-           Pane.cpp \
-           PaneStack.cpp \
-           View.cpp \
-           ViewManager.cpp
--- a/widgets/ActivityLog.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/ActivityLog.cpp	Fri May 09 17:15:50 2014 +0100
@@ -25,6 +25,13 @@
 
 #include <iostream>
 
+#include "base/Debug.h"
+
+using std::cerr;
+using std::endl;
+
+#define PRINT_ACTIVITY 1
+
 ActivityLog::ActivityLog() : QDialog()
 {
     setWindowTitle(tr("Activity Log"));
@@ -53,9 +60,16 @@
 ActivityLog::activityHappened(QString name)
 {
     name = name.replace("&", "");
-//    SVDEBUG << "ActivityLog::activityHappened(" << name << ")" << endl;
+
+#ifdef PRINT_ACTIVITY
+    cerr << "ActivityLog: " << name;
     if (name == m_prevName) {
-//        cerr << "(ignoring duplicate)" << endl;
+        cerr << " (duplicate)";
+    }
+    cerr << endl;
+#endif
+
+    if (name == m_prevName) {
         return;
     }
     m_prevName = name;
--- a/widgets/AudioDial.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/AudioDial.cpp	Fri May 09 17:15:50 2014 +0100
@@ -231,31 +231,31 @@
 	c = c.light(110);
     }
 
-    // Scale shadow...
+    // Scale shadow, omitting the bottom part...
 
     shadowAngle = 2160;
-    c = palette().dark().color();
-    for (int arc = 120; arc < 2880; arc += 240) {
+    c = palette().shadow().color();
+    for (int i = 0; i < 5; ++i) {
 	pen.setColor(c);
 	paint.setPen(pen);
+        int arc = i * 240 + 120;
 	paint.drawArc(scale/2, scale/2,
 		      width-scale, width-scale, shadowAngle + arc, 240);
+	c = c.light(110);
+    }
+    c = palette().shadow().color();
+    for (int i = 0; i < 12; ++i) {
+	pen.setColor(c);
+	paint.setPen(pen);
+        int arc = i * 240 + 120;
 	paint.drawArc(scale/2, scale/2,
 		      width-scale, width-scale, shadowAngle - arc, 240);
-	c = c.light(108);
+	c = c.light(110);
     }
 
-    // Undraw the bottom part...
-
-    pen.setColor(palette().background().color());
-    pen.setWidth(scale * 4);
-    paint.setPen(pen);
-    paint.drawArc(scale/2, scale/2,
-		  width-scale, width-scale, -45 * 16, -92 * 16);
-
     // Scale ends...
 
-    pen.setColor(palette().dark().color());
+    pen.setColor(palette().shadow().color());
     pen.setWidth(scale);
     paint.setPen(pen);
     for (int i = 0; i < numTicks; ++i) {
--- a/widgets/CommandHistory.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/CommandHistory.cpp	Fri May 09 17:15:50 2014 +0100
@@ -37,7 +37,7 @@
 
 #include <typeinfo>
 
-//#define DEBUG_COMMAND_HISTORY 1
+#define DEBUG_COMMAND_HISTORY 1
 
 CommandHistory *CommandHistory::m_instance = 0;
 
@@ -101,7 +101,7 @@
 CommandHistory::clear()
 {
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::clear()" << endl;
+    cerr << "CommandHistory::clear()" << endl;
 #endif
     closeBundle();
     m_savedAt = -1;
@@ -143,7 +143,7 @@
     if (!command) return;
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::addCommand: " << command->getName() << " of type " << typeid(*command).name() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << endl;
+    cerr << "CommandHistory::addCommand: " << command->getName() << " of type " << typeid(*command).name() << " at " << command << ": execute = " << execute << ", bundle = " << bundle << " (m_currentCompound = " << m_currentCompound << ", m_currentBundle = " << m_currentBundle << ")" << endl;
 #endif
 
     if (m_currentCompound) {
@@ -160,7 +160,7 @@
 
 #ifdef DEBUG_COMMAND_HISTORY
     if (!m_redoStack.empty()) {
-        SVDEBUG << "CommandHistory::clearing redo stack" << endl;
+        cerr << "CommandHistory::clearing redo stack" << endl;
     }
 #endif
 
@@ -192,8 +192,8 @@
     if (m_currentBundle) {
 	if (!command || (command->getName() != m_currentBundleName)) {
 #ifdef DEBUG_COMMAND_HISTORY
-            SVDEBUG << "CommandHistory::addToBundle: "
-                      << command->getName()                      << ": closing current bundle" << endl;
+            cerr << "CommandHistory::addToBundle: " << command->getName()
+                 << ": closing current bundle" << endl;
 #endif
 	    closeBundle();
 	}
@@ -204,8 +204,8 @@
     if (!m_currentBundle) {
 
 #ifdef DEBUG_COMMAND_HISTORY
-        SVDEBUG << "CommandHistory::addToBundle: "
-                  << command->getName()                  << ": creating new bundle" << endl;
+        cerr << "CommandHistory::addToBundle: " << command->getName()
+             << ": creating new bundle" << endl;
 #endif
 
 	// need to addCommand before setting m_currentBundle, as addCommand
@@ -219,8 +219,8 @@
     }
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::addToBundle: "
-              << command->getName()              << ": adding to bundle" << endl;
+    cerr << "CommandHistory::addToBundle: " << command->getName()
+         << ": adding to bundle" << endl;
 #endif
 
     if (execute) command->execute();
@@ -242,11 +242,12 @@
 void
 CommandHistory::closeBundle()
 {
+    if (m_currentBundle) {
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::closeBundle" << endl;
+        cerr << "CommandHistory::closeBundle" << endl;
 #endif
-
-    if (m_currentBundle) emit activity(m_currentBundle->getName());
+        emit activity(m_currentBundle->getName());
+    }
     m_currentBundle = 0;
     m_currentBundleName = "";
 }
@@ -255,7 +256,7 @@
 CommandHistory::bundleTimerTimeout()
 {
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << endl;
+    cerr << "CommandHistory::bundleTimerTimeout: bundle is " << m_currentBundle << endl;
 #endif
 
     closeBundle();
@@ -264,14 +265,15 @@
 void
 CommandHistory::addToCompound(Command *command, bool execute)
 {
-#ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::addToCompound: " << command->getName() << endl;
-#endif
     if (!m_currentCompound) {
-	SVDEBUG << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << endl;
+	cerr << "CommandHistory::addToCompound: ERROR: no compound operation in progress!" << endl;
         return;
     }
 
+#ifdef DEBUG_COMMAND_HISTORY
+    cerr << "CommandHistory::addToCompound[" << m_currentCompound->getName() << "]: " << command->getName() << " (exec: " << execute << ")" << endl;
+#endif
+
     if (execute) command->execute();
     m_currentCompound->addCommand(command);
 }
@@ -280,13 +282,17 @@
 CommandHistory::startCompoundOperation(QString name, bool execute)
 {
     if (m_currentCompound) {
-	SVDEBUG << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << endl;
+	cerr << "CommandHistory::startCompoundOperation: ERROR: compound operation already in progress!" << endl;
 	cerr << "(name is " << m_currentCompound->getName() << ")" << endl;
         return;
     }
  
+#ifdef DEBUG_COMMAND_HISTORY
+    cerr << "CommandHistory::startCompoundOperation: " << name << " (exec: " << execute << ")" << endl;
+#endif
+   
     closeBundle();
-   
+
     m_currentCompound = new MacroCommand(name);
     m_executeCompound = execute;
 }
@@ -295,9 +301,13 @@
 CommandHistory::endCompoundOperation()
 {
     if (!m_currentCompound) {
-	SVDEBUG << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << endl;
+	cerr << "CommandHistory::endCompoundOperation: ERROR: no compound operation in progress!" << endl;
         return;
     }
+ 
+#ifdef DEBUG_COMMAND_HISTORY
+    cerr << "CommandHistory::endCompoundOperation: " << m_currentCompound->getName() << endl;
+#endif
 
     MacroCommand *toAdd = m_currentCompound;
     m_currentCompound = 0;
@@ -329,7 +339,7 @@
     if (m_undoStack.empty()) return;
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::undo()" << endl;
+    cerr << "CommandHistory::undo()" << endl;
 #endif
 
     closeBundle();
@@ -355,7 +365,7 @@
     if (m_redoStack.empty()) return;
 
 #ifdef DEBUG_COMMAND_HISTORY
-    SVDEBUG << "CommandHistory::redo()" << endl;
+    cerr << "CommandHistory::redo()" << endl;
 #endif
 
     closeBundle();
@@ -436,7 +446,7 @@
 	for (i = 0; i < limit; ++i) {
 #ifdef DEBUG_COMMAND_HISTORY
 	    Command *command = stack.top();
-	    SVDEBUG << "CommandHistory::clipStack: Saving recent command: " << command->getName() << " at " << command << endl;
+	    cerr << "CommandHistory::clipStack: Saving recent command: " << command->getName() << " at " << command << endl;
 #endif
 	    tempStack.push(stack.top());
 	    stack.pop();
@@ -458,7 +468,7 @@
 	Command *command = stack.top();
 	// Not safe to call getName() on a command about to be deleted
 #ifdef DEBUG_COMMAND_HISTORY
-	SVDEBUG << "CommandHistory::clearStack: About to delete command " << command << endl;
+	cerr << "CommandHistory::clearStack: About to delete command " << command << endl;
 #endif
 	delete command;
 	stack.pop();
--- a/widgets/InteractiveFileFinder.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/InteractiveFileFinder.cpp	Fri May 09 17:15:50 2014 +0100
@@ -33,6 +33,7 @@
 InteractiveFileFinder::m_instance;
 
 InteractiveFileFinder::InteractiveFileFinder() :
+    m_sessionExtension("sv"),
     m_lastLocatedLocation("")
 {
     SVDEBUG << "Registering interactive file finder" << endl;
@@ -43,6 +44,12 @@
 {
 }
 
+void
+InteractiveFileFinder::setApplicationSessionExtension(QString extension)
+{
+    m_sessionExtension = extension;
+}
+
 QString
 InteractiveFileFinder::getOpenFileName(FileType type, QString fallbackLocation)
 {
@@ -57,7 +64,10 @@
     case SessionFile:
         settingsKey = "sessionpath";
         title = tr("Select a session file");
-        filter = tr("Sonic Visualiser session files (*.sv)\nRDF files (%1)\nAll files (*.*)").arg(RDFImporter::getKnownExtensions());
+        filter = tr("%1 session files (*.%1)\nRDF files (%3)\nAll files (*.*)")
+            .arg(QApplication::applicationName())
+            .arg(m_sessionExtension)
+            .arg(RDFImporter::getKnownExtensions());
         break;
 
     case AudioFile:
@@ -81,11 +91,27 @@
             .arg(RDFImporter::getKnownExtensions());
         break;
 
+    case LayerFileNonSV:
+        settingsKey = "layerpath";
+        filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
+            .arg(DataFileReaderFactory::getKnownExtensions())
+            .arg(RDFImporter::getKnownExtensions());
+        break;
+
+    case LayerFileNoMidiNonSV:
+        settingsKey = "layerpath";
+        filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
+            .arg(DataFileReaderFactory::getKnownExtensions())
+            .arg(RDFImporter::getKnownExtensions());
+        break;
+
     case SessionOrAudioFile:
         settingsKey = "lastpath";
-        filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)")
+        filter = tr("All supported files (*.sv %1 %2)\n%3 session files (*.%4)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)")
             .arg(RDFImporter::getKnownExtensions())
-            .arg(AudioFileReaderFactory::getKnownExtensions());
+            .arg(AudioFileReaderFactory::getKnownExtensions())
+            .arg(QApplication::applicationName())
+            .arg(m_sessionExtension);
         break;
 
     case ImageFile:
@@ -109,10 +135,12 @@
 
     case AnyFile:
         settingsKey = "lastpath";
-        filter = tr("All supported files (*.sv %1 %2 %3)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)")
+        filter = tr("All supported files (*.sv %1 %2 %3)\n%4 session files (*.%5)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)")
             .arg(AudioFileReaderFactory::getKnownExtensions())
             .arg(DataFileReaderFactory::getKnownExtensions())
-            .arg(RDFImporter::getKnownExtensions());
+            .arg(RDFImporter::getKnownExtensions())
+            .arg(QApplication::applicationName())
+            .arg(m_sessionExtension);
         break;
     };
 
@@ -202,7 +230,8 @@
     case SessionFile:
         settingsKey = "savesessionpath";
         title = tr("Select a session file");
-        filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
+        filter = tr("%1 session files (*.%2)\nAll files (*.*)")
+            .arg(QApplication::applicationName()).arg(m_sessionExtension);
         break;
 
     case AudioFile:
@@ -224,6 +253,18 @@
         filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
         break;
 
+    case LayerFileNonSV:
+        settingsKey = "savelayerpath";
+        title = tr("Select a file to export to");
+        filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
+        break;
+
+    case LayerFileNoMidiNonSV:
+        settingsKey = "savelayerpath";
+        title = tr("Select a file to export to");
+        filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
+        break;
+
     case SessionOrAudioFile:
         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << endl;
         abort();
@@ -274,7 +315,7 @@
     dialog.setConfirmOverwrite(false); // we'll do that
         
     if (type == SessionFile) {
-        dialog.setDefaultSuffix("sv");
+        dialog.setDefaultSuffix(m_sessionExtension);
     } else if (type == AudioFile) {
         dialog.setDefaultSuffix("wav");
     } else if (type == ImageFile) {
@@ -299,7 +340,8 @@
 
         cerr << "type = " << type << ", suffix = " << fi.suffix() << endl;
         
-        if ((type == LayerFile || type == LayerFileNoMidi)
+        if ((type == LayerFile || type == LayerFileNoMidi || 
+             type == LayerFileNonSV || type == LayerFileNoMidiNonSV)
             && fi.suffix() == "") {
             QString expectedExtension;
             QString selectedFilter = dialog.selectedNameFilter();
@@ -369,6 +411,14 @@
         settingsKey = "layerpath";
         break;
 
+    case LayerFileNonSV:
+        settingsKey = "layerpath";
+        break;
+
+    case LayerFileNoMidiNonSV:
+        settingsKey = "layerpath";
+        break;
+
     case SessionOrAudioFile:
         settingsKey = "lastpath";
         break;
--- a/widgets/InteractiveFileFinder.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/InteractiveFileFinder.h	Fri May 09 17:15:50 2014 +0100
@@ -18,6 +18,7 @@
 
 #include "data/fileio/FileFinder.h"
 
+#include <QApplication>
 #include <QString>
 #include <QObject>
 
@@ -29,6 +30,14 @@
 public:
     virtual ~InteractiveFileFinder();
 
+    /// Specify the extension for this application's session files
+    /// (without the dot)
+    void setApplicationSessionExtension(QString extension);
+
+    QString getApplicationSessionExtension() const {
+        return m_sessionExtension;
+    }
+
     QString getOpenFileName(FileType type, QString fallbackLocation = "");
     QString getSaveFileName(FileType type, QString fallbackLocation = "");
     void registerLastOpenedFilePath(FileType type, QString path);
@@ -44,6 +53,7 @@
     QString findRelative(QString location, QString relativeTo);
     QString locateInteractive(FileType type, QString thing);
 
+    QString m_sessionExtension;
     QString m_lastLocatedLocation;
 };
 
--- a/widgets/KeyReference.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/KeyReference.cpp	Fri May 09 17:15:50 2014 +0100
@@ -49,7 +49,7 @@
     QString name = action->text();
     if (overrideName != "") name = overrideName;
 
-    QString shortcut = action->shortcut().toString();
+    QString shortcut = action->shortcut().toString(QKeySequence::NativeText);
     QString tip = action->statusTip();
 
     registerShortcut(name, shortcut, tip);
@@ -87,6 +87,13 @@
 }
 
 void
+KeyReference::registerAlternativeShortcut(QAction *action, QKeySequence shortcut)
+{
+    QString name = action->text();
+    registerAlternativeShortcut(name, shortcut.toString(QKeySequence::NativeText));
+}
+
+void
 KeyReference::registerAlternativeShortcut(QString name, QString alternative)
 {
     name.replace(tr("&"), "");
@@ -102,6 +109,12 @@
 }
 
 void
+KeyReference::registerAlternativeShortcut(QString name, QKeySequence shortcut)
+{
+    registerAlternativeShortcut(name, shortcut.toString(QKeySequence::NativeText));
+}
+
+void
 KeyReference::show()
 {
     if (m_dialog) {
@@ -147,7 +160,7 @@
                 altdesc = tr("</b>&nbsp;(%1)<b>").arg(altdesc);
             }
 
-            text += QString("<tr><td>&nbsp;<b>%1%2</b></td><td>&nbsp;%3</td><td>%4</td></tr>\n")
+            text += QString("<tr><td width=\"12%\">&nbsp;<b>%1%2</b></td><td>&nbsp;%3</td><td>%4</td></tr>\n")
                 .arg(shortcut).arg(altdesc).arg(actionName).arg(tip);
         }
     }
@@ -159,7 +172,8 @@
     m_text->setReadOnly(true);
 
     m_dialog = new QDialog;
-    m_dialog->setWindowTitle(tr("Sonic Visualiser: Key and Mouse Reference"));
+    m_dialog->setWindowTitle(tr("%1: Key and Mouse Reference")
+                             .arg(QApplication::applicationName()));
 
     QVBoxLayout *layout = new QVBoxLayout;
     m_dialog->setLayout(layout);
--- a/widgets/KeyReference.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/KeyReference.h	Fri May 09 17:15:50 2014 +0100
@@ -20,6 +20,7 @@
 #include <QString>
 #include <vector>
 #include <map>
+#include <QKeySequence>
 
 class QAction;
 class QTextEdit;
@@ -38,9 +39,11 @@
 
     void registerShortcut(QAction *, QString overrideName = "");
     void registerAlternativeShortcut(QAction *, QString alternative);
+    void registerAlternativeShortcut(QAction *, QKeySequence alternative);
 
     void registerShortcut(QString actionName, QString shortcut, QString tipText);
     void registerAlternativeShortcut(QString actionName, QString alternative);
+    void registerAlternativeShortcut(QString actionName, QKeySequence alternative);
 
     void show();
     void hide();
--- a/widgets/PropertyBox.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/PropertyBox.cpp	Fri May 09 17:15:50 2014 +0100
@@ -24,10 +24,6 @@
 #include "base/UnitDatabase.h"
 #include "base/RangeMapper.h"
 
-#include "plugin/RealTimePluginFactory.h"
-#include "plugin/RealTimePluginInstance.h"
-#include "plugin/PluginXml.h"
-
 #include "AudioDial.h"
 #include "LEDButton.h"
 #include "IconLoader.h"
@@ -46,6 +42,7 @@
 #include <QApplication>
 #include <QColorDialog>
 #include <QInputDialog>
+#include <QDir>
 
 #include <cassert>
 #include <iostream>
@@ -188,13 +185,14 @@
 
 	layout->insertStretch(-1, 10);
 
-        if (params->getPlayPluginId() != "") {
-            QPushButton *pluginButton = new QPushButton(QIcon(":icons/faders.png"), "");
-            pluginButton->setFixedWidth(24);
-            pluginButton->setFixedHeight(24);
-            layout->addWidget(pluginButton);
-            connect(pluginButton, SIGNAL(clicked()),
-                    this, SLOT(editPlugin()));
+        if (params->getPlayClipId() != "") {
+            QPushButton *playParamButton =
+                new QPushButton(QIcon(":icons/faders.png"), "");
+            playParamButton->setFixedWidth(24);
+            playParamButton->setFixedHeight(24);
+            layout->addWidget(playParamButton);
+            connect(playParamButton, SIGNAL(clicked()),
+                    this, SLOT(editPlayParameters()));
         }
 
 	AudioDial *gainDial = new AudioDial;
@@ -461,7 +459,7 @@
                 // manages its own Add New Colour entry...
                 
                 ColourDatabase *db = ColourDatabase::getInstance();
-                for (size_t i = 0; i < db->getColourCount(); ++i) {
+                for (int i = 0; i < db->getColourCount(); ++i) {
                     QString name = db->getColourName(i);
                     cb->addItem(db->getExamplePixmap(i, QSize(12, 12)), name);
                 }
@@ -746,55 +744,56 @@
 }
 
 void
-PropertyBox::editPlugin()
+PropertyBox::editPlayParameters()
 {
-    //!!! should probably just emit and let something else do this
-
     PlayParameters *params = m_container->getPlayParameters();
     if (!params) return;
 
-    QString pluginId = params->getPlayPluginId();
-    QString configurationXml = params->getPlayPluginConfiguration();
+    QString clip = params->getPlayClipId();
 
     PlayParameterRepository::EditCommand *command = 
         new PlayParameterRepository::EditCommand(params);
     
-    RealTimePluginFactory *factory =
-	RealTimePluginFactory::instanceFor(pluginId);
-    if (!factory) return;
+    QInputDialog *dialog = new QInputDialog(this);
 
-    RealTimePluginInstance *instance =
-        factory->instantiatePlugin(pluginId, 0, 0, 48000, 1024, 1);
-    if (!instance) return;
+    QDir dir(":/samples");
+    QStringList clipFiles = dir.entryList(QStringList() << "*.wav", QDir::Files);
 
-    PluginXml(instance).setParametersFromXml(configurationXml);
+    QStringList clips;
+    foreach (QString str, clipFiles) {
+        clips.push_back(str.replace(".wav", ""));
+    }
+    dialog->setComboBoxItems(clips);
 
-    PluginParameterDialog *dialog = new PluginParameterDialog(instance);
-    connect(dialog, SIGNAL(pluginConfigurationChanged(QString)),
-            this, SLOT(pluginConfigurationChanged(QString)));
+    dialog->setLabelText(tr("Set playback clip:"));
+
+    QComboBox *cb = dialog->findChild<QComboBox *>();
+    if (cb) cb->setCurrentText(clip);
+
+    connect(dialog, SIGNAL(textValueChanged(QString)), 
+            this, SLOT(playClipChanged(QString)));
 
     if (dialog->exec() == QDialog::Accepted) {
-        QString newConfiguration = PluginXml(instance).toXmlString();
-        command->setPlayPluginConfiguration(newConfiguration);
+        QString newClip = dialog->textValue();
+        command->setPlayClipId(newClip);
         CommandHistory::getInstance()->addCommand(command, true);
     } else {
         delete command;
         // restore in case we mucked about with the configuration
         // as a consequence of signals from the dialog
-        params->setPlayPluginConfiguration(configurationXml);
+        params->setPlayClipId(clip);
     }
 
     delete dialog;
-    delete instance;
 }
 
 void
-PropertyBox::pluginConfigurationChanged(QString configurationXml)
+PropertyBox::playClipChanged(QString id)
 {
     PlayParameters *params = m_container->getPlayParameters();
     if (!params) return;
 
-    params->setPlayPluginConfiguration(configurationXml);
+    params->setPlayClipId(id);
 }    
 
 void
--- a/widgets/PropertyBox.h	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/PropertyBox.h	Fri May 09 17:15:50 2014 +0100
@@ -47,7 +47,7 @@
 public slots:
     void propertyContainerPropertyChanged(PropertyContainer *);
     void propertyContainerPropertyRangeChanged(PropertyContainer *);
-    void pluginConfigurationChanged(QString);
+    void playClipChanged(QString);
     void layerVisibilityChanged(bool);
 
 protected slots:
@@ -66,7 +66,7 @@
     void unitDatabaseChanged();
     void colourDatabaseChanged();
 
-    void editPlugin();
+    void editPlayParameters();
 
     void mouseEnteredWidget();
     void mouseLeftWidget();
--- a/widgets/PropertyStack.cpp	Sat Apr 12 01:07:05 2014 -0700
+++ b/widgets/PropertyStack.cpp	Fri May 09 17:15:50 2014 +0100
@@ -23,6 +23,7 @@
 #include "widgets/IconLoader.h"
 #include "base/Command.h"
 #include "widgets/CommandHistory.h"
+#include "layer/ShowLayerCommand.h"
 
 #include <QIcon>
 #include <QTabWidget>
@@ -157,9 +158,19 @@
 int
 PropertyStack::getContainerIndex(PropertyContainer *pc) const
 {
-    for (size_t i = 0; i < m_client->getPropertyContainerCount(); ++i) {
-	PropertyContainer *container = m_client->getPropertyContainer(i);
-	if (pc == container) return i;
+    // This is used to obtain an index to be passed to setCurrentIndex
+    // -- which is the index of the property container's box in our
+    // stack of boxes. That is not the same thing as the index of the
+    // container (i.e. the layer) in the view: the view reorders its
+    // containers whenever one is raised to the top, while our boxes
+    // remain in the same order. So we must find this container in the
+    // box list, not in the view.
+
+    for (size_t i = 0; i < m_boxes.size(); ++i) {
+	PropertyContainer *container = m_boxes[i]->getContainer();
+	if (pc == container) {
+            return i;
+        }
     }
 
     return false;
@@ -211,27 +222,6 @@
     repopulate();
 }
 
-class ShowLayerCommand : public QObject, public Command
-{
-public:
-    ShowLayerCommand(View *view, Layer *layer, bool show, QString name) :
-        m_view(view), m_layer(layer), m_show(show), m_name(name) { }
-    void execute() {
-        m_layer->showLayer(m_view, m_show);
-    }
-    void unexecute() {
-        m_layer->showLayer(m_view, !m_show);
-    }
-    QString getName() const {
-        return m_name;
-    }
-protected:
-    View *m_view;
-    Layer *m_layer;
-    bool m_show;
-    QString m_name;
-};
-
 void
 PropertyStack::showLayer(bool show)
 {
--- a/widgets/widgets.pro	Sat Apr 12 01:07:05 2014 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-TEMPLATE = lib
-
-SV_UNIT_PACKAGES = vamp-hostsdk fftw3f
-load(../prf/sv.prf)
-
-CONFIG += sv staticlib qt thread warn_on stl rtti exceptions
-QT += xml
-
-TARGET = svwidgets
-
-DEPENDPATH += . ..
-INCLUDEPATH += . ..
-OBJECTS_DIR = tmp_obj
-MOC_DIR = tmp_moc
-
-# Input
-HEADERS += ActivityLog.h \
-           AudioDial.h \
-           ClickableLabel.h \
-           ColourNameDialog.h \
-           CommandHistory.h \
-           CSVFormatDialog.h \
-           Fader.h \
-           InteractiveFileFinder.h \
-           IconLoader.h \
-           ImageDialog.h \
-           ItemEditDialog.h \
-           KeyReference.h \
-           LabelCounterInputDialog.h \
-           LayerTree.h \
-           LayerTreeDialog.h \
-           LEDButton.h \
-           ListInputDialog.h \
-           MIDIFileImportDialog.h \
-           ModelDataTableDialog.h \
-           NotifyingCheckBox.h \
-           NotifyingComboBox.h \
-           NotifyingPushButton.h \
-           NotifyingTabBar.h \
-           Panner.h \
-           PluginParameterBox.h \
-           PluginParameterDialog.h \
-           ProgressDialog.h \
-           PropertyBox.h \
-           PropertyStack.h \
-           RangeInputDialog.h \
-           SelectableLabel.h \
-           SubdividingMenu.h \
-           TextAbbrev.h \
-           Thumbwheel.h \
-           TipDialog.h \
-           TransformFinder.h \
-           WindowShapePreview.h \
-           WindowTypeSelector.h
-SOURCES += ActivityLog.cpp \
-           AudioDial.cpp \
-           ColourNameDialog.cpp \
-           CommandHistory.cpp \
-           CSVFormatDialog.cpp \
-           Fader.cpp \
-           InteractiveFileFinder.cpp \
-           IconLoader.cpp \
-           ImageDialog.cpp \
-           ItemEditDialog.cpp \
-           KeyReference.cpp \
-           LabelCounterInputDialog.cpp \
-           LayerTree.cpp \
-           LayerTreeDialog.cpp \
-           LEDButton.cpp \
-           ListInputDialog.cpp \
-           MIDIFileImportDialog.cpp \
-           ModelDataTableDialog.cpp \
-           NotifyingCheckBox.cpp \
-           NotifyingComboBox.cpp \
-           NotifyingPushButton.cpp \
-           NotifyingTabBar.cpp \
-           Panner.cpp \
-           PluginParameterBox.cpp \
-           PluginParameterDialog.cpp \
-           ProgressDialog.cpp \
-           PropertyBox.cpp \
-           PropertyStack.cpp \
-           RangeInputDialog.cpp \
-           SelectableLabel.cpp \
-           SubdividingMenu.cpp \
-           TextAbbrev.cpp \
-           Thumbwheel.cpp \
-           TipDialog.cpp \
-           TransformFinder.cpp \
-           WindowShapePreview.cpp \
-           WindowTypeSelector.cpp