changeset 743:a352fb986e7b tony_integration

Merge from branch tonioni
author Chris Cannam
date Tue, 11 Mar 2014 17:33:19 +0000
parents e5f4385615ac (current diff) 56ba2b03508e (diff)
children b6dc57688c72
files
diffstat 21 files changed, 321 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/layer/Colour3DPlotLayer.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -51,6 +51,7 @@
     m_binScale(LinearBinScale),
     m_normalizeColumns(false),
     m_normalizeVisibleArea(false),
+    m_normalizeHybrid(false),
     m_invertVertical(false),
     m_opaque(false),
     m_smooth(false),
@@ -430,6 +431,21 @@
 }
 
 void
+Colour3DPlotLayer::setNormalizeHybrid(bool n)
+{
+    if (m_normalizeHybrid == n) return;
+    m_normalizeHybrid = n;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+bool
+Colour3DPlotLayer::getNormalizeHybrid() const
+{
+    return m_normalizeHybrid;
+}
+
+void
 Colour3DPlotLayer::setNormalizeVisibleArea(bool n)
 {
     if (m_normalizeVisibleArea == n) return;
@@ -882,7 +898,7 @@
 {
     DenseThreeDimensionalModel::Column values = m_model->getColumn(col);
     while (values.size() < m_model->getHeight()) values.push_back(0.f);
-    if (!m_normalizeColumns) return values;
+    if (!m_normalizeColumns && !m_normalizeHybrid) return values;
 
     float colMax = 0.f, colMin = 0.f;
     float min = 0.f, max = 0.f;
@@ -905,6 +921,13 @@
         if (value != newvalue) values[y] = newvalue;
     }
 
+    if (m_normalizeHybrid && (colMax > 0.0)) {
+        float logmax = log10(colMax);
+        for (size_t y = 0; y < values.size(); ++y) {
+            values[y] *= logmax;
+        }
+    }
+
     return values;
 }
     
--- a/layer/Colour3DPlotLayer.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/layer/Colour3DPlotLayer.h	Tue Mar 11 17:33:19 2014 +0000
@@ -116,12 +116,26 @@
     void setBinScale(BinScale);
     BinScale getBinScale() const;
 
+    /**
+     * Normalize each column to its maximum value, independent of its
+     * neighbours.
+     */
     void setNormalizeColumns(bool n);
     bool getNormalizeColumns() const;
 
+    /**
+     * Normalize each value against the maximum in the visible region.
+     */
     void setNormalizeVisibleArea(bool n);
     bool getNormalizeVisibleArea() const;
 
+    /**
+     * Normalize each column to its maximum value, and then scale by
+     * the log of the (absolute) maximum value.
+     */
+    void setNormalizeHybrid(bool n);
+    bool getNormalizeHybrid() const;
+
     void setInvertVertical(bool i);
     bool getInvertVertical() const;
 
@@ -168,6 +182,7 @@
     BinScale    m_binScale;
     bool        m_normalizeColumns;
     bool        m_normalizeVisibleArea;
+    bool        m_normalizeHybrid;
     bool        m_invertVertical;
     bool        m_opaque;
     bool        m_smooth;
--- a/layer/Layer.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/layer/Layer.h	Tue Mar 11 17:33:19 2014 +0000
@@ -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 "";
     }
 
@@ -231,7 +231,7 @@
 
     virtual void splitStart(View *, QMouseEvent *) { }
     virtual void splitEnd(View *, QMouseEvent *) { }
-    virtual void addNote(View *v, QMouseEvent *e) { };
+    virtual void addNote(View *, QMouseEvent *) { };
 
     // Measurement rectangle (or equivalent).  Unlike draw and edit,
     // the base Layer class can provide working implementations of
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ShowLayerCommand.h	Tue Mar 11 17:33:19 2014 +0000
@@ -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	Tue Jan 28 15:02:09 2014 +0000
+++ b/layer/SpectrogramLayer.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -75,6 +75,7 @@
     m_binDisplay(AllBins),
     m_normalizeColumns(false),
     m_normalizeVisibleArea(false),
+    m_normalizeHybrid(false),
     m_lastEmittedZoomStep(-1),
     m_synchronous(false),
     m_haveDetailedScale(false),
@@ -951,6 +952,24 @@
 }
 
 void
+SpectrogramLayer::setNormalizeHybrid(bool n)
+{
+    if (m_normalizeHybrid == n) return;
+
+    invalidateImageCaches();
+    invalidateMagnitudes();
+    m_normalizeHybrid = n;
+
+    emit layerParametersChanged();
+}
+
+bool
+SpectrogramLayer::getNormalizeHybrid() const
+{
+    return m_normalizeHybrid;
+}
+
+void
 SpectrogramLayer::setNormalizeVisibleArea(bool n)
 {
     SVDEBUG << "SpectrogramLayer::setNormalizeVisibleArea(" << n
@@ -1245,50 +1264,6 @@
 }
 
 float
-SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const
-{
-    //!!! unused
-
-    int value = uc;
-    float input;
-
-    //!!! incorrect for normalizing visible area (and also out of date)
-    
-    switch (m_colourScale) {
-	
-    default:
-    case LinearColourScale:
-	input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50);
-	break;
-    
-    case MeterColourScale:
-	input = AudioLevel::preview_to_multiplier(value - 1, 255)
-	    / (m_normalizeColumns ? 1.0 : 50.0);
-	break;
-
-    case dBSquaredColourScale:
-	input = float(value - 1) / 255.0;
-	input = (input * 80.0) - 80.0;
-	input = powf(10.0, input) / 20.0;
-	value = int(input);
-	break;
-
-    case dBColourScale:
-	input = float(value - 1) / 255.0;
-	input = (input * 80.0) - 80.0;
-	input = powf(10.0, input) / 20.0;
-	value = int(input);
-	break;
-
-    case PhaseColourScale:
-	input = float(value - 128) * M_PI / 127.0;
-	break;
-    }
-
-    return input;
-}
-
-float
 SpectrogramLayer::getEffectiveMinFrequency() const
 {
     int sr = m_model->getSampleRate();
@@ -2556,6 +2531,14 @@
                     fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1);
                 } else if (m_normalizeColumns) {
                     fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
+                } else if (m_normalizeHybrid) {
+                    fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
+                    float max = fft->getMaximumMagnitudeAt(sx);
+                    if (max > 0.f) {
+                        for (int i = minbin; i <= maxbin; ++i) {
+                            values[i - minbin] *= log10(max);
+                        }
+                    }
                 } else {
                     fft->getMagnitudesAt(sx, values, minbin, maxbin - minbin + 1);
                 }
@@ -2574,7 +2557,7 @@
                 float value = values[bin - minbin];
 
                 if (m_colourScale != PhaseColourScale) {
-                    if (!m_normalizeColumns) {
+                    if (!m_normalizeColumns && !m_normalizeHybrid) {
                         value /= (m_fftSize/2.f);
                     }
                     mag.sample(value);
@@ -2715,6 +2698,14 @@
                         fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1);
                     } else if (m_normalizeColumns) {
                         fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
+                    } else if (m_normalizeHybrid) {
+                        fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
+                        float max = fft->getMaximumMagnitudeAt(sx);
+                        for (int i = minbin; i <= maxbin; ++i) {
+                            if (max > 0.f) {
+                                autoarray[i - minbin] *= log10(max);
+                            }
+                        }
                     } else {
                         fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1);
                     }
@@ -2723,7 +2714,7 @@
                     SVDEBUG << "Retrieving column " << sx << " from peaks cache" << endl;
 #endif
                     c = sourceModel->getColumn(sx);
-                    if (m_normalizeColumns) {
+                    if (m_normalizeColumns || m_normalizeHybrid) {
                         for (int y = 0; y < h; ++y) {
                             if (c[y] > columnMax) columnMax = c[y];
                         }
@@ -2823,9 +2814,12 @@
             float peak = peaks[y];
             
             if (m_colourScale != PhaseColourScale &&
-                m_normalizeColumns && 
+                (m_normalizeColumns || m_normalizeHybrid) && 
                 columnMax > 0.f) {
                 peak /= columnMax;
+                if (m_normalizeHybrid) {
+                    peak *= log10(columnMax);
+                }
             }
             
             unsigned char peakpix = getDisplayValue(v, peak);
--- a/layer/SpectrogramLayer.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/layer/SpectrogramLayer.h	Tue Mar 11 17:33:19 2014 +0000
@@ -170,13 +170,27 @@
      */
     void setBinDisplay(BinDisplay);
     BinDisplay getBinDisplay() const;
-
+ 
+    /**
+     * Normalize each column to its maximum value, independent of its
+     * neighbours.
+     */
     void setNormalizeColumns(bool n);
     bool getNormalizeColumns() const;
 
+    /**
+     * Normalize each value against the maximum in the visible region.
+     */
     void setNormalizeVisibleArea(bool n);
     bool getNormalizeVisibleArea() const;
 
+    /**
+     * Normalize each column to its maximum value, and then scale by
+     * the log of the (absolute) maximum value.
+     */
+    void setNormalizeHybrid(bool n);
+    bool getNormalizeHybrid() const;
+    
     void setColourMap(int map);
     int getColourMap() const;
 
@@ -260,6 +274,7 @@
     BinDisplay          m_binDisplay;
     bool                m_normalizeColumns;
     bool                m_normalizeVisibleArea;
+    bool                m_normalizeHybrid;
     int                 m_lastEmittedZoomStep;
     bool                m_synchronous;
 
@@ -320,7 +335,6 @@
     void rotatePalette(int distance);
 
     unsigned char getDisplayValue(View *v, float input) const;
-    float getInputForDisplayValue(unsigned char uc) const;
 
     int getColourScaleWidth(QPainter &) const;
 
--- a/layer/TimeValueLayer.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/layer/TimeValueLayer.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -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,31 +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 != "") {
-            bool 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;
     }
--- a/svgui.pro	Tue Jan 28 15:02:09 2014 +0000
+++ b/svgui.pro	Tue Mar 11 17:33:19 2014 +0000
@@ -5,6 +5,10 @@
     include(config.pri)
 }
 !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
--- a/view/Pane.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/Pane.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -90,8 +90,10 @@
     
     updateHeadsUpDisplay();
 
-
-//    SVDEBUG << "Pane::Pane(" << this << ") returning" << endl;
+    connect(this, SIGNAL(regionOutlined(QRect)), 
+            this, SLOT(zoomToRegion(QRect)));
+
+    cerr << "Pane::Pane(" << this << ") returning" << endl;
 }
 
 void
@@ -357,10 +359,10 @@
                      bool &closeToRight) const
 {
     if (m_identifyFeatures &&
-    m_manager &&
-    m_manager->getToolModeFor(this) == ViewManager::EditMode &&
-    !m_manager->getSelections().empty() &&
-    !selectionIsBeingEdited()) {
+        m_manager &&
+        m_manager->getToolModeFor(this) == ViewManager::EditMode &&
+        !m_manager->getSelections().empty() &&
+        !selectionIsBeingEdited()) {
 
     Selection s(getSelectionAt(m_identifyPoint.x(),
                    closeToLeft, closeToRight));
@@ -1440,7 +1442,7 @@
             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);
+            emit regionOutlined(QRect(x0, y0, x1 - x0, y1 - y0));
         }
 
     } else if (mode == ViewManager::SelectMode) {
@@ -1452,6 +1454,7 @@
 
         if (m_manager && m_manager->haveInProgressSelection()) {
 
+            //cerr << "JTEST: release with selection" << endl;
             bool exclusive;
             Selection selection = m_manager->getInProgressSelection(exclusive);
         
@@ -1467,6 +1470,16 @@
                 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();
 
@@ -1792,8 +1805,13 @@
 }
 
 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);
--- a/view/Pane.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/Pane.h	Tue Mar 11 17:33:19 2014 +0000
@@ -68,6 +68,7 @@
     void dropAccepted(QStringList uriList);
     void dropAccepted(QString text);
     void doubleClickSelectInvoked(size_t frame);
+    void regionOutlined(QRect rect);
 
 public slots:
     virtual void toolModeChanged();
@@ -85,6 +86,8 @@
 
     virtual void propertyContainerSelected(View *, PropertyContainer *pc);
 
+    void zoomToRegion(QRect r);
+
     void mouseEnteredWidget();
     void mouseLeftWidget();
 
@@ -130,7 +133,6 @@
 
     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);
 
--- a/view/PaneStack.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/PaneStack.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -247,11 +247,16 @@
 void
 PaneStack::deletePane(Pane *pane)
 {
+    cerr << "PaneStack::deletePane(" << pane << ")" << endl;
+
     std::vector<PaneRec>::iterator i;
     bool found = false;
 
+    QWidget *stack = 0;
+
     for (i = m_panes.begin(); i != m_panes.end(); ++i) {
 	if (i->pane == pane) {
+            stack = i->propertyStack;
 	    m_panes.erase(i);
 	    found = true;
 	    break;
@@ -262,6 +267,7 @@
 
 	for (i = m_hiddenPanes.begin(); i != m_hiddenPanes.end(); ++i) {
 	    if (i->pane == pane) {
+                stack = i->propertyStack;
 		m_hiddenPanes.erase(i);
 		found = true;
 		break;
@@ -276,6 +282,18 @@
 
     emit paneAboutToBeDeleted(pane);
 
+    cerr << "PaneStack::deletePane: about to delete parent " << pane->parent() << " of pane " << pane << endl;
+
+    // The property stack associated with the parent was initially
+    // created with the same parent as it, so it would be deleted when
+    // we delete the pane's parent in a moment -- but it may have been
+    // reparented depending on the layout. We'd better delete it
+    // separately first. (This fixes a crash on opening a new layer
+    // with a new unit type in it, when a long-defunct property box
+    // could be signalled from the unit database to tell it that a new
+    // unit had appeared.)
+    delete stack;
+
     delete pane->parent();
 
     if (m_currentPane == pane) {
--- a/view/View.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/View.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -64,12 +64,12 @@
     m_manager(0),
     m_propertyContainer(new ViewPropertyContainer(this))
 {
-    SVDEBUG << "View::View(" << this << ")" << endl;
+//    cerr << "View::View(" << this << ")" << endl;
 }
 
 View::~View()
 {
-//    SVDEBUG << "View::~View(" << this << ")" << endl;
+//    cerr << "View::~View(" << this << ")" << endl;
 
     m_deleting = true;
     delete m_propertyContainer;
@@ -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;
     }
@@ -2407,7 +2415,11 @@
 ViewPropertyContainer::ViewPropertyContainer(View *v) :
     m_v(v)
 {
+//    cerr << "ViewPropertyContainer: " << this << " is owned by View " << v << endl;
     connect(m_v, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
 	    this, SIGNAL(propertyChanged(PropertyContainer::PropertyName)));
 }
 
+ViewPropertyContainer::~ViewPropertyContainer()
+{
+}
--- a/view/View.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/View.h	Tue Mar 11 17:33:19 2014 +0000
@@ -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;
     }
@@ -401,6 +404,8 @@
 
 public:
     ViewPropertyContainer(View *v);
+    virtual ~ViewPropertyContainer();
+
     PropertyList getProperties() const { return m_v->getProperties(); }
     QString getPropertyLabel(const PropertyName &n) const {
         return m_v->getPropertyLabel(n);
--- a/view/ViewManager.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/ViewManager.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -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 =
@@ -279,6 +279,7 @@
     if (m_selections.getSelections() == ms.getSelections()) return;
     SetSelectionCommand *command = new SetSelectionCommand(this, ms);
     CommandHistory::getInstance()->addCommand(command);
+    emit selectionChangedByUser();
 }
 
 size_t
@@ -669,11 +670,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	Tue Jan 28 15:02:09 2014 +0000
+++ b/view/ViewManager.h	Tue Mar 11 17:33:19 2014 +0000
@@ -155,7 +155,8 @@
 
     enum OverlayMode {
         NoOverlays,
-        MinimalOverlays,
+        GlobalOverlays,
+        StandardOverlays,
         AllOverlays
     };
     void setOverlayMode(OverlayMode mode);
@@ -177,7 +178,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;
@@ -191,6 +192,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; }
@@ -214,9 +218,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();
 
--- a/widgets/AudioDial.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/widgets/AudioDial.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -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/KeyReference.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/widgets/KeyReference.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -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);
         }
     }
--- a/widgets/KeyReference.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/widgets/KeyReference.h	Tue Mar 11 17:33:19 2014 +0000
@@ -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	Tue Jan 28 15:02:09 2014 +0000
+++ b/widgets/PropertyBox.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -117,7 +117,7 @@
 PropertyBox::populateViewPlayFrame()
 {
 #ifdef DEBUG_PROPERTY_BOX
-    cerr << "PropertyBox(" << m_container << ")::populateViewPlayFrame" << endl;
+    cerr << "PropertyBox[" << this << ":" << m_container << "]::populateViewPlayFrame" << endl;
 #endif
 
     if (m_viewPlayFrame) {
@@ -563,7 +563,9 @@
 void
 PropertyBox::unitDatabaseChanged()
 {
+#ifdef DEBUG_PROPERTY_BOX
     cerr << "PropertyBox[" << this << "]: unitDatabaseChanged" << endl;
+#endif
     blockSignals(true);
 
 //    cerr << "my container is " << m_container << endl;
--- a/widgets/PropertyStack.cpp	Tue Jan 28 15:02:09 2014 +0000
+++ b/widgets/PropertyStack.cpp	Tue Mar 11 17:33:19 2014 +0000
@@ -23,6 +23,7 @@
 #include "widgets/IconLoader.h"
 #include "base/Command.h"
 #include "widgets/CommandHistory.h"
+#include "layer/ShowLayerCommand.h"
 
 #include <QIcon>
 #include <QTabWidget>
@@ -74,6 +75,10 @@
 	    m_client, SLOT(propertyContainerSelected(View *, PropertyContainer *)));
 }
 
+PropertyStack::~PropertyStack()
+{
+}
+
 void
 PropertyStack::repopulate()
 {
@@ -153,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;
@@ -207,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/PropertyStack.h	Tue Jan 28 15:02:09 2014 +0000
+++ b/widgets/PropertyStack.h	Tue Mar 11 17:33:19 2014 +0000
@@ -31,6 +31,7 @@
 
 public:
     PropertyStack(QWidget *parent, View *client);
+    virtual ~PropertyStack();
 
     View *getClient() { return m_client; }
     bool containsContainer(PropertyContainer *container) const;