changeset 945:bb80983c9e61 osx-retina

Merge from default branch
author Chris Cannam
date Mon, 20 Apr 2015 09:18:55 +0100
parents e39d5d2734ed (current diff) 78c152e4db95 (diff)
children 8bf05426d950
files layer/FlexiNoteLayer.cpp layer/FlexiNoteLayer.h svgui.pro
diffstat 15 files changed, 868 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/configure	Wed Mar 18 15:10:36 2015 +0000
+++ b/configure	Mon Apr 20 09:18:55 2015 +0100
@@ -4343,8 +4343,8 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-   	CXXFLAGS_ANY="-Wall -Wextra -Werror -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
-        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -g"
+   	CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
+        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g"
    	CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2"
    	CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0"
 fi
--- a/configure.ac	Wed Mar 18 15:10:36 2015 +0000
+++ b/configure.ac	Mon Apr 20 09:18:55 2015 +0100
@@ -53,8 +53,8 @@
 CXXFLAGS_MINIMAL="$AUTOCONF_CXXFLAGS"
 
 if test "x$GCC" = "xyes"; then
-   	CXXFLAGS_ANY="-Wall -Wextra -Werror -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
-        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -g"
+   	CXXFLAGS_ANY="-Wall -Wextra -Woverloaded-virtual -Wformat-nonliteral -Wformat-security -Winit-self -Wswitch-enum -Wconversion -pipe"
+        CXXFLAGS_DEBUG="$CXXFLAGS_ANY -Werror -g"
    	CXXFLAGS_RELEASE="$CXXFLAGS_ANY -g0 -O2"
    	CXXFLAGS_MINIMAL="$CXXFLAGS_ANY -g0 -O0"
 fi
--- a/layer/FlexiNoteLayer.cpp	Wed Mar 18 15:10:36 2015 +0000
+++ b/layer/FlexiNoteLayer.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -1135,7 +1135,8 @@
 
     m_editingCommand->deletePoint(m_editingPoint);
 
-    std::cerr << "edit mode: " << m_editMode << std::endl;
+    std::cerr << "edit mode: " << m_editMode << " intelligent actions = "
+              << m_intelligentActions << std::endl;
     
     switch (m_editMode) {
     case LeftBoundary : {
@@ -1164,16 +1165,28 @@
             dragFrame = m_smallestRightNeighbourFrame - m_originalPoint.duration;
         }
         m_editingPoint.frame = dragFrame;
+
         m_editingPoint.value = float(value);
+
+        // Re-analyse region within +/- 1 semitone of the dragged value
+        float cents = 0;
+        int midiPitch = Pitch::getPitchForFrequency(m_editingPoint.value, &cents);
+        double lower = Pitch::getFrequencyForPitch(midiPitch - 1, cents);
+        double higher = Pitch::getFrequencyForPitch(midiPitch + 1, cents);
+        
+        emit reAnalyseRegion(m_editingPoint.frame,
+                             m_editingPoint.frame + m_editingPoint.duration,
+                             float(lower), float(higher));
         break;
     }
     case SplitNote: // nothing
         break;
     }
-    updateNoteValue(v, m_editingPoint);
+
+//    updateNoteValueFromPitchCurve(v, m_editingPoint);
     m_editingCommand->addPoint(m_editingPoint);
+
     std::cerr << "added new point(" << m_editingPoint.frame << "," << m_editingPoint.duration << ")" << std::endl;
-    
 }
 
 void
@@ -1188,6 +1201,15 @@
 
         QString newName = m_editingCommand->getName();
 
+        if (m_editMode == DragNote) {
+            //!!! command nesting is wrong?
+            emit materialiseReAnalysis();
+        }
+
+        m_editingCommand->deletePoint(m_editingPoint);
+        updateNoteValueFromPitchCurve(v, m_editingPoint);
+        m_editingCommand->addPoint(m_editingPoint);
+
         if (m_editingPoint.frame != m_originalPoint.frame) {
             if (m_editingPoint.value != m_originalPoint.value) {
                 newName = tr("Edit Point");
@@ -1210,7 +1232,7 @@
 FlexiNoteLayer::splitStart(LayerGeometryProvider *v, QMouseEvent *e)
 {
     // GF: note splitting starts (!! remove printing soon)
-    std::cerr << "splitStart" << std::endl;
+    std::cerr << "splitStart (n.b. editStart will be called later, if the user drags the mouse)" << std::endl;
     if (!m_model) return;
 
     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
@@ -1227,7 +1249,6 @@
     m_editing = true;
     m_dragStartX = e->x();
     m_dragStartY = e->y();
-    
 }
 
 void
@@ -1280,10 +1301,10 @@
                            note.level, note.label);
                        
         if (m_intelligentActions) {
-            if (updateNoteValue(v, newNote1)) {
+            if (updateNoteValueFromPitchCurve(v, newNote1)) {
                 command->addPoint(newNote1);
             }
-            if (updateNoteValue(v, newNote2)) {
+            if (updateNoteValueFromPitchCurve(v, newNote2)) {
                 command->addPoint(newNote2);
             }
         } else {
@@ -1339,22 +1360,23 @@
 {
     // Better than we used to do, but still not very satisfactory
 
-    cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
+//    cerr << "FlexiNoteLayer::getAssociatedPitchModel()" << endl;
 
     for (int i = 0; i < v->getView()->getLayerCount(); ++i) {
         Layer *layer = v->getView()->getLayer(i);
         if (layer &&
             layer->getLayerPresentationName() != "candidate") {
-            cerr << "FlexiNoteLayer::getAssociatedPitchModel: looks like our layer is " << layer << endl;
+//            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;
+//            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;
             }
         }
     }
+    cerr << "FlexiNoteLayer::getAssociatedPitchModel: failed to find a model" << endl;
     return 0;
 }
 
@@ -1388,7 +1410,7 @@
 
         command->deletePoint(note);
 
-        if (updateNoteValue(v, newNote)) {
+        if (updateNoteValueFromPitchCurve(v, newNote)) {
             command->addPoint(newNote);
         }
     }
@@ -1434,13 +1456,13 @@
         ++i;
     }
 
-    updateNoteValue(v, newNote);
+    updateNoteValueFromPitchCurve(v, newNote);
     command->addPoint(newNote);
     finish(command);
 }
 
 bool
-FlexiNoteLayer::updateNoteValue(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const
+FlexiNoteLayer::updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const
 {
     SparseTimeValueModel *model = getAssociatedPitchModel(v);
     if (!model) return false;
@@ -1475,6 +1497,8 @@
     } else {
         median = pitchValues[size/2];
     }
+
+    std::cerr << "updateNoteValueFromPitchCurve: corrected from " << note.value << " to median " << median << std::endl;
     
     note.value = float(median);
 
@@ -1492,21 +1516,31 @@
         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->getView()->setCursor(Qt::SizeVerCursor);
+    bool closeToLeft = false, closeToRight = false,
+        closeToTop = false, closeToBottom = false;
+    getRelativeMousePosition(v, note, e->x(), e->y(),
+                             closeToLeft, closeToRight,
+                             closeToTop, closeToBottom);
     
-    if (closeToLeft) { v->getView()->setCursor(Qt::SizeHorCursor); m_editMode = LeftBoundary; return; }
-    if (closeToRight) { v->getView()->setCursor(Qt::SizeHorCursor); m_editMode = RightBoundary; return; }
-    if (closeToTop) { v->getView()->setCursor(Qt::CrossCursor);  m_editMode = DragNote; return; }
-    if (closeToBottom) { v->getView()->setCursor(Qt::UpArrowCursor); m_editMode = SplitNote; return; }
-
-    v->getView()->setCursor(Qt::ArrowCursor);
-
-    std::cerr << "Mouse moved in edit mode over FlexiNoteLayer" << std::endl;
-    // v->getView()->setCursor(Qt::SizeHorCursor);
-
+    if (closeToLeft) {
+        v->getView()->setCursor(Qt::SizeHorCursor);
+        m_editMode = LeftBoundary;
+        cerr << "edit mode -> LeftBoundary" << endl;
+    } else if (closeToRight) {
+        v->getView()->setCursor(Qt::SizeHorCursor);
+        m_editMode = RightBoundary;
+        cerr << "edit mode -> RightBoundary" << endl;
+    } else if (closeToTop) {
+        v->getView()->setCursor(Qt::CrossCursor);
+        m_editMode = DragNote;
+        cerr << "edit mode -> DragNote" << endl;
+    } else if (closeToBottom) {
+        v->getView()->setCursor(Qt::UpArrowCursor);
+        m_editMode = SplitNote;
+        cerr << "edit mode -> SplitNote" << endl;
+    } else {
+        v->getView()->setCursor(Qt::ArrowCursor);
+    }
 }
 
 void
--- a/layer/FlexiNoteLayer.h	Wed Mar 18 15:10:36 2015 +0000
+++ b/layer/FlexiNoteLayer.h	Mon Apr 20 09:18:55 2015 +0100
@@ -163,6 +163,10 @@
     virtual double getValueForY(LayerGeometryProvider *v, int y) const;
     virtual QString getScaleUnits() const;
 
+signals:
+    void reAnalyseRegion(sv_frame_t, sv_frame_t, float, float);
+    void materialiseReAnalysis();
+    
 protected:
     void getScaleExtents(LayerGeometryProvider *, double &min, double &max, bool &log) const;
     bool shouldConvertMIDIToHz() const;
@@ -175,7 +179,7 @@
     bool getNoteToEdit(LayerGeometryProvider *v, int x, int y, FlexiNoteModel::Point &) const;
     void getRelativeMousePosition(LayerGeometryProvider *v, FlexiNoteModel::Point &note, int x, int y, bool &closeToLeft, bool &closeToRight, bool &closeToTop, bool &closeToBottom) const;
     SparseTimeValueModel *getAssociatedPitchModel(LayerGeometryProvider *v) const;
-    bool updateNoteValue(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const;
+    bool updateNoteValueFromPitchCurve(LayerGeometryProvider *v, FlexiNoteModel::Point &note) const;
     void splitNotesAt(LayerGeometryProvider *v, sv_frame_t frame, QMouseEvent *e);
 
     FlexiNoteModel *m_model;
--- a/svgui.pro	Wed Mar 18 15:10:36 2015 +0000
+++ b/svgui.pro	Mon Apr 20 09:18:55 2015 +0100
@@ -117,6 +117,8 @@
            widgets/LayerTree.h \
            widgets/LayerTreeDialog.h \
            widgets/LEDButton.h \
+           widgets/LevelPanToolButton.h \
+           widgets/LevelPanWidget.h \
            widgets/ListInputDialog.h \
            widgets/MIDIFileImportDialog.h \
            widgets/ModelDataTableDialog.h \
@@ -155,6 +157,8 @@
            widgets/LayerTree.cpp \
            widgets/LayerTreeDialog.cpp \
            widgets/LEDButton.cpp \
+           widgets/LevelPanToolButton.cpp \
+           widgets/LevelPanWidget.cpp \
            widgets/ListInputDialog.cpp \
            widgets/MIDIFileImportDialog.cpp \
            widgets/ModelDataTableDialog.cpp \
--- a/view/Overview.cpp	Wed Mar 18 15:10:36 2015 +0000
+++ b/view/Overview.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -144,6 +144,20 @@
     if (changed) update();
 }
 
+QColor
+Overview::getFillWithin() const
+{
+    return Qt::transparent;
+}
+
+QColor
+Overview::getFillWithout() const
+{
+    QColor c = palette().window().color();
+    c.setAlpha(100);
+    return c;
+}
+
 void
 Overview::paintEvent(QPaintEvent *e)
 {
@@ -184,21 +198,23 @@
 
     QPainter paint;
     paint.begin(this);
-
+    paint.setClipRegion(e->region());
+    paint.setRenderHints(QPainter::Antialiasing);
+    
     QRect r(rect());
 
-    if (e) {
-	r = e->rect();
-	paint.setClipRect(r);
-    }
+    // We paint a rounded rect for each distinct set of view extents,
+    // and we colour in the inside and outside of the rect that
+    // corresponds to the current view. (One small caveat -- we don't
+    // know which rect that is yet. We'll have to figure it out
+    // somehow...)
 
-    paint.setPen(getForeground());
+    std::set<std::pair<int, int> > extents;
+    std::vector<QRect> rects;
+    QRect primary;
 
     int y = 0;
 
-    sv_frame_t prevx0 = -10;
-    sv_frame_t prevx1 = -10;
-
     for (ViewSet::iterator i = m_views.begin(); i != m_views.end(); ++i) {
 	if (!*i) continue;
 
@@ -219,15 +235,36 @@
 	int x0 = getXForFrame(f0);
 	int x1 = getXForFrame(f1);
 
-	if (x0 != prevx0 || x1 != prevx1) {
-	    y += height() / 10 + 1;
-	    prevx0 = x0;
-	    prevx1 = x1;
-	}
 
 	if (x1 <= x0) x1 = x0 + 1;
-	
-	paint.drawRect(x0, y, x1 - x0, height() - 2 * y);
+
+        std::pair<int, int> extent(x0, x1);
+
+        if (extents.find(extent) == extents.end()) {
+
+    	    y += height() / 10 + 1;
+            extents.insert(extent);
+
+            QRect vr(x0, y, x1 - x0, height() - 2 * y);
+            rects.push_back(vr);
+            primary = vr; //!!! for now
+        }
+    }
+
+    QPainterPath without;
+    without.addRoundedRect(primary, 4, 4);
+    without.addRect(rect());
+    paint.setPen(Qt::NoPen);
+    paint.setBrush(getFillWithout());
+    paint.drawPath(without);
+
+    paint.setBrush(getFillWithin());
+    paint.drawRoundedRect(primary, 4, 4);
+    
+    foreach (QRect vr, rects) {
+        paint.setBrush(Qt::NoBrush);
+        paint.setPen(QPen(Qt::gray, 2));
+        paint.drawRoundedRect(vr, 4, 4);
     }
 
     paint.end();
--- a/view/Overview.h	Wed Mar 18 15:10:36 2015 +0000
+++ b/view/Overview.h	Mon Apr 20 09:18:55 2015 +0100
@@ -59,6 +59,9 @@
     virtual void leaveEvent(QEvent *);
     virtual bool shouldLabelSelections() const { return false; }
 
+    QColor getFillWithin() const;
+    QColor getFillWithout() const;
+    
     QPoint m_clickPos;
     QPoint m_mousePos;
     bool m_clickedInRange;
--- a/view/Pane.cpp	Wed Mar 18 15:10:36 2015 +0000
+++ b/view/Pane.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -24,6 +24,7 @@
 #include "widgets/TextAbbrev.h"
 #include "base/Preferences.h"
 #include "layer/WaveformLayer.h"
+#include "layer/TimeRulerLayer.h"
 
 // GF: added so we can propagate the mouse move event to the note layer for context handling.
 #include "layer/LayerFactory.h"
@@ -1367,7 +1368,8 @@
             sv_frame_t snapFrame = mouseFrame;
     
             Layer *layer = getInteractionLayer();
-            if (layer && !m_shiftPressed) {
+            if (layer && !m_shiftPressed &&
+                !qobject_cast<TimeRulerLayer *>(layer)) { // don't snap to secs
                 layer->snapToFeatureFrame(this, snapFrame,
                                           resolution, Layer::SnapLeft);
             }
@@ -1740,9 +1742,9 @@
 
             if (!editSelectionDrag(e)) {
 
-                Layer *layer = getInteractionLayer();
-
-                if (layer && layer->isLayerEditable()) {
+                Layer *layer = getTopFlexiNoteLayer();
+
+                if (layer) {
 
                     int x = e->x();
                     int y = e->y();
@@ -2083,7 +2085,8 @@
     sv_frame_t snapFrameRight = mouseFrame;
     
     Layer *layer = getInteractionLayer();
-    if (layer && !m_shiftPressed) {
+    if (layer && !m_shiftPressed &&
+        !qobject_cast<TimeRulerLayer *>(layer)) { // don't snap to secs
         layer->snapToFeatureFrame(this, snapFrameLeft,
                                   resolution, Layer::SnapLeft);
         layer->snapToFeatureFrame(this, snapFrameRight,
--- a/widgets/ItemEditDialog.cpp	Wed Mar 18 15:10:36 2015 +0000
+++ b/widgets/ItemEditDialog.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -76,7 +76,7 @@
         m_frameTimeSpinBox->setSuffix(tr(" frames"));
         subgrid->addWidget(m_frameTimeSpinBox, subrow, 1, 1, 2);
         connect(m_frameTimeSpinBox, SIGNAL(valueChanged(int)),
-                this, SLOT(frameTimeChanged(sv_frame_t)));
+                this, SLOT(frameTimeChanged(int)));
 
         ++subrow;
 
@@ -107,7 +107,7 @@
         m_frameDurationSpinBox->setSuffix(tr(" frames"));
         subgrid->addWidget(m_frameDurationSpinBox, subrow, 1, 1, 2);
         connect(m_frameDurationSpinBox, SIGNAL(valueChanged(int)),
-                this, SLOT(frameDurationChanged(sv_frame_t)));
+                this, SLOT(frameDurationChanged(int)));
 
         ++subrow;
 
@@ -287,7 +287,7 @@
 }
 
 void
-ItemEditDialog::frameTimeChanged(sv_frame_t i)
+ItemEditDialog::frameTimeChanged(int i)
 {
     m_realTimeSecsSpinBox->blockSignals(true);
     m_realTimeUSecsSpinBox->blockSignals(true);
@@ -322,7 +322,7 @@
 }
 
 void
-ItemEditDialog::frameDurationChanged(sv_frame_t i)
+ItemEditDialog::frameDurationChanged(int i)
 {
     m_realDurationSecsSpinBox->blockSignals(true);
     m_realDurationUSecsSpinBox->blockSignals(true);
--- a/widgets/ItemEditDialog.h	Wed Mar 18 15:10:36 2015 +0000
+++ b/widgets/ItemEditDialog.h	Mon Apr 20 09:18:55 2015 +0100
@@ -59,10 +59,10 @@
     QString getText() const;
 
 protected slots:
-    void frameTimeChanged(sv_frame_t);
+    void frameTimeChanged(int); // must be int as invoked from int signal
     void realTimeSecsChanged(int);
     void realTimeUSecsChanged(int);
-    void frameDurationChanged(sv_frame_t);
+    void frameDurationChanged(int); // must be int as invoked from int signal
     void realDurationSecsChanged(int);
     void realDurationUSecsChanged(int);
     void valueChanged(double);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanToolButton.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -0,0 +1,173 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LevelPanToolButton.h"
+#include "LevelPanWidget.h"
+
+#include <QMenu>
+#include <QWidgetAction>
+#include <QImage>
+#include <QStylePainter>
+#include <QStyleOptionToolButton>
+
+#include <iostream>
+using std::cerr;
+using std::endl;
+
+LevelPanToolButton::LevelPanToolButton(QWidget *parent) :
+    QToolButton(parent),
+    m_pixels(32),
+    m_pixelsBig(32 * 3),
+    m_muted(false),
+    m_savedLevel(1.f)
+{
+    m_lpw = new LevelPanWidget();
+
+    connect(m_lpw, SIGNAL(levelChanged(float)), this, SIGNAL(levelChanged(float)));
+    connect(m_lpw, SIGNAL(levelChanged(float)), this, SLOT(selfLevelChanged(float)));
+
+    connect(m_lpw, SIGNAL(panChanged(float)), this, SIGNAL(panChanged(float)));
+    connect(m_lpw, SIGNAL(panChanged(float)), this, SLOT(update()));
+
+    connect(this, SIGNAL(clicked(bool)), this, SLOT(selfClicked()));
+    
+    QMenu *menu = new QMenu();
+    QWidgetAction *wa = new QWidgetAction(menu);
+    wa->setDefaultWidget(m_lpw);
+    menu->addAction(wa);
+
+    setPopupMode(InstantPopup);
+    setMenu(menu);
+
+    setImageSize(m_pixels);
+    setBigImageSize(m_pixelsBig);
+}
+
+LevelPanToolButton::~LevelPanToolButton()
+{
+}
+
+float
+LevelPanToolButton::getLevel() const
+{
+    return m_lpw->getLevel();
+}
+
+float
+LevelPanToolButton::getPan() const
+{
+    return m_lpw->getPan();
+}
+
+bool
+LevelPanToolButton::includesMute() const
+{
+    return m_lpw->includesMute();
+}
+
+void
+LevelPanToolButton::setImageSize(int pixels)
+{
+    m_pixels = pixels;
+
+    QPixmap px(m_pixels, m_pixels);
+    px.fill(Qt::transparent);
+    setIcon(px);
+}
+
+void
+LevelPanToolButton::setBigImageSize(int pixels)
+{
+    m_pixelsBig = pixels;
+
+    m_lpw->setFixedWidth(m_pixelsBig);
+    m_lpw->setFixedHeight(m_pixelsBig);
+}
+
+void
+LevelPanToolButton::setLevel(float level)
+{
+    m_lpw->setLevel(level);
+    update();
+}
+
+void
+LevelPanToolButton::setPan(float pan)
+{
+    m_lpw->setPan(pan);
+    update();
+}
+
+void
+LevelPanToolButton::setIncludeMute(bool include)
+{
+    m_lpw->setIncludeMute(include);
+    update();
+}
+
+void
+LevelPanToolButton::setEnabled(bool enabled)
+{
+    m_lpw->setEnabled(enabled);
+    QToolButton::setEnabled(enabled);
+}
+
+void
+LevelPanToolButton::selfLevelChanged(float level)
+{
+    if (level > 0.f) {
+	m_muted = false;
+    } else {
+	m_muted = true;
+	m_savedLevel = 1.f;
+    }
+    update();
+}
+
+void
+LevelPanToolButton::selfClicked()
+{
+    cerr << "selfClicked" << endl;
+    
+    if (m_muted) {
+	m_muted = false;
+	m_lpw->setLevel(m_savedLevel);
+	emit levelChanged(m_savedLevel);
+    } else {
+	m_savedLevel = m_lpw->getLevel();
+	m_muted = true;
+	m_lpw->setLevel(0.f);
+	emit levelChanged(0.f);
+    }
+    update();
+}
+
+void
+LevelPanToolButton::paintEvent(QPaintEvent *)
+{
+    QStylePainter p(this);
+    QStyleOptionToolButton opt;
+    initStyleOption(&opt);
+    opt.features &= (~QStyleOptionToolButton::HasMenu);
+    p.drawComplexControl(QStyle::CC_ToolButton, opt);
+    
+    if (m_pixels >= height()) {
+        setImageSize(height()-1);
+    }
+    
+    double margin = (double(height()) - m_pixels) / 2.0;
+    m_lpw->renderTo(this, QRectF(margin, margin, m_pixels, m_pixels), false);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanToolButton.h	Mon Apr 20 09:18:55 2015 +0100
@@ -0,0 +1,73 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef LEVEL_PAN_TOOLBUTTON_H
+#define LEVEL_PAN_TOOLBUTTON_H
+
+#include <QToolButton>
+
+class LevelPanWidget;
+
+class LevelPanToolButton : public QToolButton
+{
+    Q_OBJECT
+
+public:
+    LevelPanToolButton(QWidget *parent = 0);
+    ~LevelPanToolButton();
+    
+    /// Return level as a gain value in the range [0,1]
+    float getLevel() const; 
+    
+    /// Return pan as a value in the range [-1,1]
+    float getPan() const;
+
+    /// Discover whether the level range includes muting or not
+    bool includesMute() const;
+
+    void setImageSize(int pixels);
+			
+    void setBigImageSize(int pixels);
+			
+public slots:
+    /// Set level in the range [0,1] -- will be rounded
+    void setLevel(float);
+
+    /// Set pan in the range [-1,1] -- will be rounded
+    void setPan(float);
+
+    /// Specify whether the level range should include muting or not
+    void setIncludeMute(bool);
+
+    void setEnabled(bool enabled);
+    
+signals:
+    void levelChanged(float);
+    void panChanged(float);
+
+private slots:
+    void selfLevelChanged(float);
+    void selfClicked();
+    
+protected:
+    void paintEvent(QPaintEvent *);
+    
+    LevelPanWidget *m_lpw;
+    int m_pixels;
+    int m_pixelsBig;
+    bool m_muted;
+    float m_savedLevel;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanWidget.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -0,0 +1,388 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "LevelPanWidget.h"
+
+#include <QPainter>
+#include <QMouseEvent>
+#include <QWheelEvent>
+
+#include "layer/ColourMapper.h"
+#include "base/AudioLevel.h"
+
+#include <iostream>
+#include <cmath>
+#include <cassert>
+
+using std::cerr;
+using std::endl;
+
+static const int maxLevel = 4; // min is 0, may be mute or not depending on m_includeMute
+static const int maxPan = 2; // range is -maxPan to maxPan
+
+LevelPanWidget::LevelPanWidget(QWidget *parent) :
+    QWidget(parent),
+    m_level(maxLevel),
+    m_pan(0),
+    m_editable(true),
+    m_includeMute(true)
+{
+}
+
+LevelPanWidget::~LevelPanWidget()
+{
+}
+
+QSize
+LevelPanWidget::sizeHint() const
+{
+    static double ratio = 0.0;
+    if (ratio == 0.0) {
+        double baseEm;
+#ifdef Q_OS_MAC
+        baseEm = 17.0;
+#else
+        baseEm = 15.0;
+#endif
+        double em = QFontMetrics(QFont()).height();
+        ratio = em / baseEm;
+    }
+
+    int pixels = 40;
+    int scaled = int(pixels * ratio + 0.5);
+    if (pixels != 0 && scaled == 0) scaled = 1;
+    return QSize(scaled, scaled);
+}
+
+static int
+db_to_level(double db)
+{
+    // Only if !m_includeMute, otherwise AudioLevel is used.
+    // Levels are: +6 0 -6 -12 -20
+    assert(maxLevel == 4);
+    if (db > 3.) return 4;
+    else if (db > -3.) return 3;
+    else if (db > -9.) return 2;
+    else if (db > -16.) return 1;
+    else return 0;
+}
+
+static double
+level_to_db(int level)
+{
+    // Only if !m_includeMute, otherwise AudioLevel is used.
+    // Levels are: +6 0 -6 -12 -20
+    assert(maxLevel == 4);
+    if (level >= 4) return 6.;
+    else if (level == 3) return 0.;
+    else if (level == 2) return -6.;
+    else if (level == 1) return -12.;
+    else return -20.;
+}
+
+void
+LevelPanWidget::setLevel(float flevel)
+{
+    int level;
+    if (m_includeMute) {
+        level = AudioLevel::multiplier_to_fader
+            (flevel, maxLevel, AudioLevel::ShortFader);
+    } else {
+        level = db_to_level(AudioLevel::multiplier_to_dB(flevel));
+    }
+    if (level < 0) level = 0;
+    if (level > maxLevel) level = maxLevel;
+    if (level != m_level) {
+	m_level = level;
+	float convertsTo = getLevel();
+	if (fabsf(convertsTo - flevel) > 1e-5) {
+	    emitLevelChanged();
+	}
+	update();
+    }
+}
+
+float
+LevelPanWidget::getLevel() const
+{
+    if (m_includeMute) {
+        return float(AudioLevel::fader_to_multiplier
+                     (m_level, maxLevel, AudioLevel::ShortFader));
+    } else {
+        return float(AudioLevel::dB_to_multiplier(level_to_db(m_level)));
+    }
+}
+
+void
+LevelPanWidget::setPan(float pan)
+{
+    m_pan = int(round(pan * maxPan));
+    if (m_pan < -maxPan) m_pan = -maxPan;
+    if (m_pan > maxPan) m_pan = maxPan;
+    update();
+}
+
+bool
+LevelPanWidget::isEditable() const
+{
+    return m_editable;
+}
+
+bool
+LevelPanWidget::includesMute() const
+{
+    return m_includeMute;
+}
+
+void
+LevelPanWidget::setEditable(bool editable)
+{
+    m_editable = editable;
+    update();
+}
+
+void
+LevelPanWidget::setIncludeMute(bool include)
+{
+    m_includeMute = include;
+    emitLevelChanged();
+    update();
+}
+
+float
+LevelPanWidget::getPan() const
+{
+    return float(m_pan) / float(maxPan);
+}
+
+void
+LevelPanWidget::emitLevelChanged()
+{
+    cerr << "emitting levelChanged(" << getLevel() << ")" << endl;
+    emit levelChanged(getLevel());
+}
+
+void
+LevelPanWidget::emitPanChanged()
+{
+    cerr << "emitting panChanged(" << getPan() << ")" << endl;
+    emit panChanged(getPan());
+}
+
+void
+LevelPanWidget::mousePressEvent(QMouseEvent *e)
+{
+    mouseMoveEvent(e);
+}
+
+void
+LevelPanWidget::mouseMoveEvent(QMouseEvent *e)
+{
+    if (!m_editable) return;
+    
+    int level, pan;
+    toCell(rect(), e->pos(), level, pan);
+    if (level == m_level && pan == m_pan) {
+	return;
+    }
+    if (level != m_level) {
+	m_level = level;
+	emitLevelChanged();
+    }
+    if (pan != m_pan) {
+	m_pan = pan;
+	emitPanChanged();
+    }
+    update();
+}
+
+void
+LevelPanWidget::mouseReleaseEvent(QMouseEvent *e)
+{
+    mouseMoveEvent(e);
+}
+
+void
+LevelPanWidget::wheelEvent(QWheelEvent *e)
+{
+    if (e->modifiers() & Qt::ControlModifier) {
+	if (e->delta() > 0) {
+	    if (m_pan < maxPan) {
+		++m_pan;
+		emitPanChanged();
+		update();
+	    }
+	} else {
+	    if (m_pan > -maxPan) {
+		--m_pan;
+		emitPanChanged();
+		update();
+	    }
+	}
+    } else {
+	if (e->delta() > 0) {
+	    if (m_level < maxLevel) {
+		++m_level;
+		emitLevelChanged();
+		update();
+	    }
+	} else {
+	    if (m_level > 0) {
+		--m_level;
+		emitLevelChanged();
+		update();
+	    }
+	}
+    }
+}
+
+void
+LevelPanWidget::toCell(QRectF rect, QPointF loc, int &level, int &pan) const
+{
+    double w = rect.width(), h = rect.height();
+
+    int npan = maxPan * 2 + 1;
+    int nlevel = maxLevel + 1;
+
+    double wcell = w / npan, hcell = h / nlevel;
+
+    level = int((h - (loc.y() - rect.y())) / hcell);
+    if (level < 0) level = 0;
+    if (level > maxLevel) level = maxLevel;
+
+    pan = int((loc.x() - rect.x()) / wcell) - maxPan;
+    if (pan < -maxPan) pan = -maxPan;
+    if (pan > maxPan) pan = maxPan;
+}
+
+QSizeF
+LevelPanWidget::cellSize(QRectF rect) const
+{
+    double w = rect.width(), h = rect.height();
+    int npan = maxPan * 2 + 1;
+    int nlevel = maxLevel + 1;
+    double wcell = w / npan, hcell = h / nlevel;
+    return QSizeF(wcell, hcell);
+}
+
+QPointF
+LevelPanWidget::cellCentre(QRectF rect, int level, int pan) const
+{
+    QSizeF cs = cellSize(rect);
+    return QPointF(rect.x() + cs.width() * (pan + maxPan) + cs.width() / 2.,
+		   rect.y() + rect.height() - cs.height() * (level + 1) + cs.height() / 2.);
+}
+
+QSizeF
+LevelPanWidget::cellLightSize(QRectF rect) const
+{
+    double extent = 3. / 4.;
+    QSizeF cs = cellSize(rect);
+    double m = std::min(cs.width(), cs.height());
+    return QSizeF(m * extent, m * extent);
+}
+
+QRectF
+LevelPanWidget::cellLightRect(QRectF rect, int level, int pan) const
+{
+    QSizeF cls = cellLightSize(rect);
+    QPointF cc = cellCentre(rect, level, pan);
+    return QRectF(cc.x() - cls.width() / 2., 
+		  cc.y() - cls.height() / 2.,
+		  cls.width(),
+		  cls.height());
+}
+
+double
+LevelPanWidget::thinLineWidth(QRectF rect) const
+{
+    double tw = ceil(rect.width() / (maxPan * 2. * 10.));
+    double th = ceil(rect.height() / (maxLevel * 10.));
+    return std::min(th, tw);
+}
+
+static QColor
+level_to_colour(int level)
+{
+    assert(maxLevel == 4);
+    if (level == 0) return Qt::black;
+    else if (level == 1) return QColor(80, 0, 0);
+    else if (level == 2) return QColor(160, 0, 0);
+    else if (level == 3) return QColor(255, 0, 0);
+    else return QColor(255, 255, 0);
+}
+
+void
+LevelPanWidget::renderTo(QPaintDevice *dev, QRectF rect, bool asIfEditable) const
+{
+    QPainter paint(dev);
+
+    paint.setRenderHint(QPainter::Antialiasing, true);
+
+    QPen pen;
+
+    double thin = thinLineWidth(rect);
+
+    pen.setColor(QColor(127, 127, 127, 127));
+    pen.setWidthF(cellLightSize(rect).width() + thin);
+    pen.setCapStyle(Qt::RoundCap);
+    paint.setPen(pen);
+
+    for (int pan = -maxPan; pan <= maxPan; ++pan) {
+	paint.drawLine(cellCentre(rect, 0, pan), cellCentre(rect, maxLevel, pan));
+    }
+
+    if (isEnabled()) {
+	pen.setColor(Qt::black);
+    } else {
+	pen.setColor(Qt::darkGray);
+    }
+
+    if (!asIfEditable && m_includeMute && m_level == 0) {
+        pen.setWidthF(thin * 2);
+        pen.setCapStyle(Qt::RoundCap);
+        paint.setPen(pen);
+        paint.drawLine(cellCentre(rect, 0, -maxPan),
+                       cellCentre(rect, maxLevel, maxPan));
+        paint.drawLine(cellCentre(rect, maxLevel, -maxPan),
+                       cellCentre(rect, 0, maxPan));
+        return;
+    }
+    
+    pen.setWidthF(thin);
+    pen.setCapStyle(Qt::FlatCap);
+    paint.setPen(pen);
+    
+    for (int level = 0; level <= m_level; ++level) {
+	if (isEnabled()) {
+	    paint.setBrush(level_to_colour(level));
+	}
+	QRectF clr = cellLightRect(rect, level, m_pan);
+	if (m_includeMute && m_level == 0) {
+	    paint.drawLine(clr.topLeft(), clr.bottomRight());
+	    paint.drawLine(clr.bottomLeft(), clr.topRight());
+	} else {
+	    paint.drawEllipse(clr);
+	}
+    }
+}
+
+void
+LevelPanWidget::paintEvent(QPaintEvent *)
+{
+    renderTo(this, rect(), m_editable);
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/widgets/LevelPanWidget.h	Mon Apr 20 09:18:55 2015 +0100
@@ -0,0 +1,89 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef LEVEL_PAN_WIDGET_H
+#define LEVEL_PAN_WIDGET_H
+
+#include <QWidget>
+
+/**
+ * A simple widget for coarse level and pan control.
+ */
+
+class LevelPanWidget : public QWidget
+{
+    Q_OBJECT
+
+public:
+    LevelPanWidget(QWidget *parent = 0);
+    ~LevelPanWidget();
+    
+    /// Return level as a gain value in the range [0,1]
+    float getLevel() const; 
+    
+    /// Return pan as a value in the range [-1,1]
+    float getPan() const;
+
+    /// Find out whether the widget is editable
+    bool isEditable() const;
+
+    /// Discover whether the level range includes muting or not
+    bool includesMute() const;
+
+    /// Draw a suitably sized copy of the widget's contents to the given device
+    void renderTo(QPaintDevice *, QRectF, bool asIfEditable) const;
+
+    QSize sizeHint() const;
+                                               
+public slots:
+    /// Set level in the range [0,1] -- will be rounded
+    void setLevel(float);
+
+    /// Set pan in the range [-1,1] -- will be rounded
+    void setPan(float);
+
+    /// Specify whether the widget is editable or read-only (default editable)
+    void setEditable(bool);
+
+    /// Specify whether the level range should include muting or not
+    void setIncludeMute(bool);
+    
+signals:
+    void levelChanged(float);
+    void panChanged(float);
+
+protected:
+    virtual void mousePressEvent(QMouseEvent *ev);
+    virtual void mouseMoveEvent(QMouseEvent *ev);
+    virtual void mouseReleaseEvent(QMouseEvent *ev);
+    virtual void wheelEvent(QWheelEvent *ev);
+    virtual void paintEvent(QPaintEvent *ev);
+
+    void emitLevelChanged();
+    void emitPanChanged();
+    
+    int m_level;
+    int m_pan;
+    bool m_editable;
+    bool m_includeMute;
+
+    QSizeF cellSize(QRectF) const;
+    QPointF cellCentre(QRectF, int level, int pan) const;
+    QSizeF cellLightSize(QRectF) const;
+    QRectF cellLightRect(QRectF, int level, int pan) const;
+    double thinLineWidth(QRectF) const;
+    void toCell(QRectF, QPointF loc, int &level, int &pan) const;
+};
+
+#endif
--- a/widgets/PropertyStack.cpp	Wed Mar 18 15:10:36 2015 +0000
+++ b/widgets/PropertyStack.cpp	Mon Apr 20 09:18:55 2015 +0100
@@ -30,7 +30,7 @@
 
 #include <iostream>
 
-#define DEBUG_PROPERTY_STACK 1
+//#define DEBUG_PROPERTY_STACK 1
 
 PropertyStack::PropertyStack(QWidget *parent, View *client) :
     QTabWidget(parent),