changeset 246:ddbde90773b0 spectrogram-cache-rejig

* Merge from trunk
author Chris Cannam
date Wed, 27 Feb 2008 11:59:42 +0000
parents 37847fc0b381
children 8492db8cf579
files icons/draw.png icons/erase.png icons/invert-vertical.png icons/normalise-columns.png icons/normalise.png icons/select.png icons/show-peaks.png icons/sv-splash.png icons/sv-splash.svg main/MainWindow.cpp main/MainWindow.h main/OSCHandler.cpp main/PreferencesDialog.cpp main/PreferencesDialog.h main/main.cpp sonic-visualiser.qrc sv.pro
diffstat 17 files changed, 1083 insertions(+), 723 deletions(-) [+]
line wrap: on
line diff
Binary file icons/draw.png has changed
Binary file icons/erase.png has changed
Binary file icons/invert-vertical.png has changed
Binary file icons/normalise-columns.png has changed
Binary file icons/normalise.png has changed
Binary file icons/select.png has changed
Binary file icons/show-peaks.png has changed
Binary file icons/sv-splash.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icons/sv-splash.svg	Wed Feb 27 11:59:42 2008 +0000
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="640px"
+   height="480px"
+   id="svg1952"
+   sodipodi:version="0.32"
+   inkscape:version="0.44.1"
+   sodipodi:docbase="/home/studio/code/sonic-visualiser/sv/icons"
+   sodipodi:docname="sv-splash.svg"
+   inkscape:export-filename="/home/studio/code/sonic-visualiser/sv/icons/sv-splash.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs1954" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="320"
+     inkscape:cy="240"
+     inkscape:current-layer="layer1"
+     inkscape:document-units="px"
+     showgrid="true"
+     inkscape:window-width="1101"
+     inkscape:window-height="969"
+     inkscape:window-x="169"
+     inkscape:window-y="26" />
+  <metadata
+     id="metadata1957">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <rect
+       style="fill:white;fill-opacity:1;stroke:black;stroke-width:5.70384979;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3849"
+       width="578.29614"
+       height="316.29614"
+       x="28.851925"
+       y="57.851925" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#d45500;stroke-width:7.58643961px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 226.71778,185.52035 C 407.22042,185.52035 407.22042,185.52035 407.22042,185.52035"
+       id="path4036" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:black;stroke-width:10.28721237;stroke-linecap:butt;stroke-linejoin:miter;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:1.39999998;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 228.89103,185.52402 C 255.18599,185.52402 258.59857,185.60302 258.59857,185.60302 C 270.76871,133.56701 271.36497,127.54591 274.66485,178.25451 C 279.70514,266.89367 284.18048,220.02667 290.61951,183.64501 C 302.75722,68.043355 303.99332,111.5005 306.56423,135.80647 C 316.45027,256.01216 316.0237,282.95104 321.30811,243.02856 C 325.23523,213.36022 336.92287,88.29917 339.01862,122.7736 C 353.59512,259.79486 354.02613,251.94928 358.50413,216.49781 C 373.27326,77.656444 368.76189,188.69998 377.97144,186.27485 C 404.04172,186.27485 403.59893,185.60302 403.59893,185.60302 L 407.00661,185.60302"
+       id="path3783"
+       sodipodi:nodetypes="cccccsccccc" />
+    <rect
+       style="fill:none;stroke:#d45500;stroke-width:15.17287922;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3775"
+       width="191.29092"
+       height="187.44695"
+       x="223.48573"
+       y="91.460907" />
+    <text
+       xml:space="preserve"
+       style="font-size:32px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Korataki"
+       x="59"
+       y="330"
+       id="text2049"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan2051"
+         x="59"
+         y="330">SONIC VISUALISER</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Korataki"
+       x="84.300003"
+       y="352"
+       id="text2053"
+       sodipodi:linespacing="100%"><tspan
+         sodipodi:role="line"
+         id="tspan2055"
+         x="84.300003"
+         y="352">QUEEN MARY, UNIVERSITY OF LONDON</tspan></text>
+  </g>
+</svg>
--- a/main/MainWindow.cpp	Tue Nov 13 13:51:07 2007 +0000
+++ b/main/MainWindow.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -16,7 +16,6 @@
 #include "../version.h"
 
 #include "MainWindow.h"
-#include "framework/Document.h"
 #include "PreferencesDialog.h"
 
 #include "view/Pane.h"
@@ -25,6 +24,8 @@
 #include "data/model/SparseOneDimensionalModel.h"
 #include "data/model/NoteModel.h"
 #include "data/model/Labeller.h"
+#include "data/osc/OSCQueue.h"
+#include "framework/Document.h"
 #include "view/ViewManager.h"
 #include "base/Preferences.h"
 #include "layer/WaveformLayer.h"
@@ -41,7 +42,7 @@
 #include "widgets/PropertyStack.h"
 #include "widgets/AudioDial.h"
 #include "widgets/IconLoader.h"
-#include "widgets/LayerTree.h"
+#include "widgets/LayerTreeDialog.h"
 #include "widgets/ListInputDialog.h"
 #include "widgets/SubdividingMenu.h"
 #include "widgets/NotifyingPushButton.h"
@@ -69,7 +70,6 @@
 #include "base/Clipboard.h"
 #include "base/UnitDatabase.h"
 #include "base/ColourDatabase.h"
-#include "data/osc/OSCQueue.h"
 
 // For version information
 #include "vamp/vamp.h"
@@ -133,8 +133,10 @@
     m_prevSolo(false),
     m_ffwdAction(0),
     m_rwdAction(0),
+    m_playControlsSpacer(0),
+    m_playControlsWidth(0),
     m_preferencesDialog(0),
-    m_layerTreeView(0),
+    m_layerTreeDialog(0),
     m_keyReference(new KeyReference())
 {
     setWindowTitle(tr("Sonic Visualiser"));
@@ -220,47 +222,33 @@
 
     IconLoader il;
 
-    m_playSharpen = new NotifyingPushButton(frame);
-    m_playSharpen->setToolTip(tr("Sharpen percussive transients"));
-    m_playSharpen->setFixedSize(20, 20);
-    m_playSharpen->setEnabled(false);
-    m_playSharpen->setCheckable(true);
-    m_playSharpen->setChecked(false);
-    m_playSharpen->setIcon(il.load("sharpen"));
-    connect(m_playSharpen, SIGNAL(clicked()), this, SLOT(playSharpenToggled()));
-    connect(m_playSharpen, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
-    connect(m_playSharpen, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
-
-    m_playMono = new NotifyingPushButton(frame);
-    m_playMono->setToolTip(tr("Run time stretcher in mono only"));
-    m_playMono->setFixedSize(20, 20);
-    m_playMono->setEnabled(false);
-    m_playMono->setCheckable(true);
-    m_playMono->setChecked(false);
-    m_playMono->setIcon(il.load("mono"));
-    connect(m_playMono, SIGNAL(clicked()), this, SLOT(playMonoToggled()));
-    connect(m_playMono, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
-    connect(m_playMono, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
-
     QSettings settings;
     settings.beginGroup("MainWindow");
-    m_playSharpen->setChecked(settings.value("playsharpen", true).toBool());
-    m_playMono->setChecked(settings.value("playmono", false).toBool());
     settings.endGroup();
 
+    m_playControlsSpacer = new QFrame;
+
     layout->setSpacing(4);
     layout->addWidget(scroll, 0, 0, 1, 5);
-    layout->addWidget(m_overview, 1, 0);
-    layout->addWidget(m_fader, 1, 1);
-    layout->addWidget(m_playSpeed, 1, 2);
-    layout->addWidget(m_playSharpen, 1, 3);
-    layout->addWidget(m_playMono, 1, 4);
-
-    m_paneStack->setPropertyStackMinWidth
-        (m_fader->width() + m_playSpeed->width() + m_playSharpen->width() +
-         m_playMono->width() + layout->spacing() * 4);
-
-    layout->setColumnStretch(0, 10);
+    layout->addWidget(m_overview, 1, 1);
+    layout->addWidget(m_playControlsSpacer, 1, 2);
+    layout->addWidget(m_playSpeed, 1, 3);
+    layout->addWidget(m_fader, 1, 4);
+
+    m_playControlsWidth = 
+        m_fader->width() + m_playSpeed->width() + layout->spacing() * 2;
+
+    layout->setColumnMinimumWidth(0, 14);
+    layout->setColumnStretch(0, 0);
+
+    m_paneStack->setPropertyStackMinWidth(m_playControlsWidth
+                                          + 2 + layout->spacing());
+    m_playControlsSpacer->setFixedSize(QSize(2, 2));
+
+    layout->setColumnStretch(1, 10);
+
+    connect(m_paneStack, SIGNAL(propertyStacksResized(int)),
+            this, SLOT(propertyStacksResized(int)));
 
     frame->setLayout(layout);
 
@@ -277,7 +265,7 @@
 {
     delete m_keyReference;
     delete m_preferencesDialog;
-    delete m_layerTreeView;
+    delete m_layerTreeDialog;
     Profiles::getInstance()->dump();
 }
 
@@ -593,6 +581,7 @@
     menu->addAction(action);
 
     QMenu *numberingMenu = menu->addMenu(tr("Number New Instants with"));
+    numberingMenu->setTearOffEnabled(true);
     QActionGroup *numberingGroup = new QActionGroup(this);
 
     Labeller::TypeNameMap types = m_labeller->getTypeNames();
@@ -614,7 +603,7 @@
             QMenu *cycleMenu = numberingMenu->addMenu(tr("Cycle size"));
             QActionGroup *cycleGroup = new QActionGroup(this);
 
-            int cycles[] = { 2, 3, 4, 5, 6, 7, 8, 10, 12, 16 };
+            int cycles[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16 };
             for (int i = 0; i < int(sizeof(cycles)/sizeof(cycles[0])); ++i) {
                 action = new QAction(QString("%1").arg(cycles[i]), this);
                 connect(action, SIGNAL(triggered()), this, SLOT(setInstantsCounterCycle()));
@@ -623,10 +612,6 @@
                 cycleGroup->addAction(action);
                 cycleMenu->addAction(action);
             }
-            
-            action = new QAction(tr("Reset Counters..."), this);
-            connect(action, SIGNAL(triggered()), this, SLOT(resetInstantsCounters()));
-            numberingMenu->addAction(action);
         }
 
         if (i->first == Labeller::ValueNone ||
@@ -636,8 +621,13 @@
         }
     }
 
-    action = new QAction(tr("Re-Number Selected Instants"), this);
-    action->setStatusTip(tr("Re-number the selected instants using the current labelling scheme"));
+    action = new QAction(tr("Set Numbering Counters..."), this);
+    action->setStatusTip(tr("Set the counters used for counter-based labelling"));
+    connect(action, SIGNAL(triggered()), this, SLOT(resetInstantsCounters()));
+    menu->addAction(action);
+            
+    action = new QAction(tr("Renumber Selected Instants"), this);
+    action->setStatusTip(tr("Renumber the selected instants using the current labelling scheme"));
     connect(action, SIGNAL(triggered()), this, SLOT(renumberInstants()));
     connect(this, SIGNAL(canRenumberInstants(bool)), action, SLOT(setEnabled(bool)));
 //    m_keyReference->registerShortcut(action);
@@ -810,8 +800,8 @@
 
     menu->addSeparator();
 
-    action = new QAction(tr("Show La&yer Hierarchy"), this);
-    action->setShortcut(tr("H"));
+    action = new QAction(tr("Show La&yer Summary"), this);
+    action->setShortcut(tr("Y"));
     action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session"));
     connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree()));
     m_keyReference->registerShortcut(action);
@@ -848,7 +838,7 @@
     action->setStatusTip(tr("Add a new pane containing only a time ruler"));
     connect(action, SIGNAL(triggered()), this, SLOT(addPane()));
     connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool)));
-    m_paneActions[action] = PaneConfiguration(LayerFactory::TimeRuler);
+    m_paneActions[action] = LayerConfiguration(LayerFactory::TimeRuler);
     m_keyReference->registerShortcut(action);
     menu->addAction(action);
 
@@ -884,7 +874,7 @@
 
 	connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
 	connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
-	m_layerActions[action] = type;
+	m_layerActions[action] = LayerConfiguration(type);
 	menu->addAction(action);
         m_rightButtonLayerMenu->addAction(action);
     }
@@ -901,7 +891,7 @@
     };
 
     std::vector<Model *> models;
-    if (m_document) models = m_document->getTransformerInputModels(); //!!! not well named for this!
+    if (m_document) models = m_document->getTransformInputModels();
     bool plural = (models.size() > 1);
     if (models.empty()) {
         models.push_back(getMainModel()); // probably 0
@@ -910,9 +900,11 @@
     for (unsigned int i = 0;
 	 i < sizeof(backgroundTypes)/sizeof(backgroundTypes[0]); ++i) {
 
-	for (int menuType = 0; menuType <= 1; ++menuType) { // pane, layer
-
-	    if (menuType == 0) menu = m_paneMenu;
+        const int paneMenuType = 0, layerMenuType = 1;
+
+	for (int menuType = paneMenuType; menuType <= layerMenuType; ++menuType) {
+
+	    if (menuType == paneMenuType) menu = m_paneMenu;
 	    else menu = m_layerMenu;
 
 	    QMenu *submenu = 0;
@@ -927,7 +919,7 @@
             case LayerFactory::Waveform:
                 icon = il.load("waveform");
                 mainText = tr("Add &Waveform");
-                if (menuType == 0) {
+                if (menuType == paneMenuType) {
                     shortcutText = tr("W");
                     tipText = tr("Add a new pane showing a waveform view");
                 } else {
@@ -939,7 +931,7 @@
             case LayerFactory::Spectrogram:
                 icon = il.load("spectrogram");
                 mainText = tr("Add Spectro&gram");
-                if (menuType == 0) {
+                if (menuType == paneMenuType) {
                     shortcutText = tr("G");
                     tipText = tr("Add a new pane showing a spectrogram");
                 } else {
@@ -950,7 +942,7 @@
             case LayerFactory::MelodicRangeSpectrogram:
                 icon = il.load("spectrogram");
                 mainText = tr("Add &Melodic Range Spectrogram");
-                if (menuType == 0) {
+                if (menuType == paneMenuType) {
                     shortcutText = tr("M");
                     tipText = tr("Add a new pane showing a spectrogram set up for an overview of note pitches");
                 } else {
@@ -961,7 +953,7 @@
             case LayerFactory::PeakFrequencySpectrogram:
                 icon = il.load("spectrogram");
                 mainText = tr("Add Pea&k Frequency Spectrogram");
-                if (menuType == 0) {
+                if (menuType == paneMenuType) {
                     shortcutText = tr("K");
                     tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies");
                 } else {
@@ -972,7 +964,7 @@
             case LayerFactory::Spectrum:
                 icon = il.load("spectrum");
                 mainText = tr("Add Spectr&um");
-                if (menuType == 0) {
+                if (menuType == paneMenuType) {
                     shortcutText = tr("U");
                     tipText = tr("Add a new pane showing a frequency spectrum");
                 } else {
@@ -984,11 +976,11 @@
             }
 
             std::vector<Model *> candidateModels;
-            if (menuType == 0) {
+//            if (menuType == paneMenuType) {
                 candidateModels = models;
-            } else {
-                candidateModels.push_back(0);
-            }
+//            } else {
+//                candidateModels.push_back(0);
+//            }
             
             for (std::vector<Model *>::iterator mi =
                      candidateModels.begin();
@@ -1013,33 +1005,33 @@
                     bool isDefault = (c == 0);
                     bool isOnly = (isDefault && (channels == 1));
 
-                    if (menuType == 1) {
-                        if (isDefault) isOnly = true;
-                        else continue;
-                    }
-
-                    if (isOnly && (!plural || menuType == 1)) {
-
-                        if (menuType == 1 && type != LayerFactory::Waveform) {
-                            action = new QAction(mainText, this);
-                        } else {
+//                    if (menuType == layerMenuType) {
+//                        if (isDefault) isOnly = true;
+//                        else continue;
+//                    }
+
+                    if (isOnly && (!plural /*|| menuType == layerMenuType*/)) {
+
+//                        if (menuType == layerMenuType && type != LayerFactory::Waveform) {
+//                            action = new QAction(mainText, this);
+//                        } else {
                             action = new QAction(icon, mainText, this);
-                        }                            
+//                        }                            
 
                         action->setShortcut(shortcutText);
                         action->setStatusTip(tipText);
-                        if (menuType == 0) {
+                        if (menuType == paneMenuType) {
                             connect(action, SIGNAL(triggered()),
                                     this, SLOT(addPane()));
                             connect(this, SIGNAL(canAddPane(bool)),
                                     action, SLOT(setEnabled(bool)));
-                            m_paneActions[action] = PaneConfiguration(type);
+                            m_paneActions[action] = LayerConfiguration(type);
                         } else {
                             connect(action, SIGNAL(triggered()),
                                     this, SLOT(addLayer()));
                             connect(this, SIGNAL(canAddLayer(bool)),
                                     action, SLOT(setEnabled(bool)));
-                            m_layerActions[action] = type;
+                            m_layerActions[action] = LayerConfiguration(type);
                         }
                         if (shortcutText != "") {
                             m_keyReference->registerShortcut(action);
@@ -1083,19 +1075,20 @@
 
                         action->setStatusTip(tipText);
 
-                        if (menuType == 0) {
+                        if (menuType == paneMenuType) {
                             connect(action, SIGNAL(triggered()),
                                     this, SLOT(addPane()));
                             connect(this, SIGNAL(canAddPane(bool)),
                                     action, SLOT(setEnabled(bool)));
                             m_paneActions[action] =
-                                PaneConfiguration(type, model, c - 1);
+                                LayerConfiguration(type, model, c - 1);
                         } else {
                             connect(action, SIGNAL(triggered()),
                                     this, SLOT(addLayer()));
                             connect(this, SIGNAL(canAddLayer(bool)),
                                     action, SLOT(setEnabled(bool)));
-                            m_layerActions[action] = type;
+                            m_layerActions[action] =
+                                LayerConfiguration(type, model, c - 1);
                         }
 
                         submenu->addAction(action);
@@ -1106,6 +1099,23 @@
     }
 
     menu = m_paneMenu;
+    menu->addSeparator();
+
+    action = new QAction(tr("Switch to Previous Pane"), this);
+    action->setShortcut(tr("["));
+    action->setStatusTip(tr("Make the next pane up in the pane stack current"));
+    connect(action, SIGNAL(triggered()), this, SLOT(previousPane()));
+    connect(this, SIGNAL(canSelectPreviousPane(bool)), action, SLOT(setEnabled(bool)));
+    m_keyReference->registerShortcut(action);
+    menu->addAction(action);
+
+    action = new QAction(tr("Switch to Next Pane"), this);
+    action->setShortcut(tr("]"));
+    action->setStatusTip(tr("Make the next pane down in the pane stack current"));
+    connect(action, SIGNAL(triggered()), this, SLOT(nextPane()));
+    connect(this, SIGNAL(canSelectNextPane(bool)), action, SLOT(setEnabled(bool)));
+    m_keyReference->registerShortcut(action);
+    menu->addAction(action);
 
     menu->addSeparator();
 
@@ -1123,7 +1133,7 @@
     action->setStatusTip(tr("Add a new layer showing a time ruler"));
     connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
     connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool)));
-    m_layerActions[action] = LayerFactory::TimeRuler;
+    m_layerActions[action] = LayerConfiguration(LayerFactory::TimeRuler);
     menu->addAction(action);
 
     menu->addSeparator();
@@ -1138,6 +1148,25 @@
 
     setupExistingLayersMenus();
 
+/*!!! These don't work correctly -- fix or omit
+    menu->addSeparator();
+
+    action = new QAction(tr("Switch to Previous Layer"), this);
+    action->setShortcut(tr("{"));
+    action->setStatusTip(tr("Make the previous layer in the pane current"));
+    connect(action, SIGNAL(triggered()), this, SLOT(previousLayer()));
+    connect(this, SIGNAL(canSelectPreviousLayer(bool)), action, SLOT(setEnabled(bool)));
+    m_keyReference->registerShortcut(action);
+    menu->addAction(action);
+
+    action = new QAction(tr("Switch to Next Layer"), this);
+    action->setShortcut(tr("}"));
+    action->setStatusTip(tr("Make the next layer in the pane current"));
+    connect(action, SIGNAL(triggered()), this, SLOT(nextLayer()));
+    connect(this, SIGNAL(canSelectNextLayer(bool)), action, SLOT(setEnabled(bool)));
+    m_keyReference->registerShortcut(action);
+    menu->addAction(action);
+*/
     m_rightButtonLayerMenu->addSeparator();
     menu->addSeparator();
 
@@ -1174,7 +1203,7 @@
    }
 
     TransformList transforms =
-	TransformFactory::getInstance()->getAllTransforms();
+	TransformFactory::getInstance()->getAllTransformDescriptions();
 
     vector<QString> types =
         TransformFactory::getInstance()->getAllTransformTypes();
@@ -1280,6 +1309,20 @@
         }
     }
 
+    // Names should only be duplicated here if they have the same
+    // plugin name, output name and maker but are in different library
+    // .so names -- that won't happen often I hope
+    std::map<QString, QString> idNameSonameMap;
+    std::set<QString> seenNames, duplicateNames;
+    for (unsigned int i = 0; i < transforms.size(); ++i) {
+        QString name = transforms[i].name;
+        if (seenNames.find(name) != seenNames.end()) {
+            duplicateNames.insert(name);
+        } else {
+            seenNames.insert(name);
+        }
+    }
+
     for (unsigned int i = 0; i < transforms.size(); ++i) {
 	
 	QString name = transforms[i].name;
@@ -1299,6 +1342,16 @@
         QString pluginName = name.section(": ", 0, 0);
         QString output = name.section(": ", 1);
 
+        if (duplicateNames.find(pluginName) != duplicateNames.end()) {
+            pluginName = QString("%1 <%2>")
+                .arg(pluginName)
+                .arg(transforms[i].identifier.section(':', 1, 1));
+            if (output == "") name = pluginName;
+            else name = QString("%1: %2")
+                .arg(pluginName)
+                .arg(output);
+        }
+
 	QAction *action = new QAction(tr("%1...").arg(name), this);
 	connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
 	m_transformActions[action] = transforms[i].identifier;
@@ -1439,6 +1492,8 @@
                 (tr("Repeat Transform"),
                  ti->second->shortcut(),
                  tr("Re-select the most recently run transform"));
+        } else {
+            ti->second->setShortcut(QString(""));
         }
 	m_recentTransformsMenu->addAction(ti->second);
     }
@@ -1737,10 +1792,21 @@
     m_keyReference->registerShortcut(action);
     m_toolActions[ViewManager::DrawMode] = action;
 
+    action = toolbar->addAction(il.load("erase"),
+				tr("Erase"));
+    action->setCheckable(true);
+    action->setShortcut(tr("5"));
+    action->setStatusTip(tr("Erase items from layer"));
+    connect(action, SIGNAL(triggered()), this, SLOT(toolEraseSelected()));
+    connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool)));
+    group->addAction(action);
+    m_keyReference->registerShortcut(action);
+    m_toolActions[ViewManager::EraseMode] = action;
+
     action = toolbar->addAction(il.load("measure"),
 				tr("Measure"));
     action->setCheckable(true);
-    action->setShortcut(tr("5"));
+    action->setShortcut(tr("6"));
     action->setStatusTip(tr("Make measurements in layer"));
     connect(action, SIGNAL(triggered()), this, SLOT(toolMeasureSelected()));
     connect(this, SIGNAL(canMeasureLayer(bool)), action, SLOT(setEnabled(bool)));
@@ -1748,14 +1814,6 @@
     m_keyReference->registerShortcut(action);
     m_toolActions[ViewManager::MeasureMode] = action;
 
-//    action = toolbar->addAction(il.load("text"),
-//				tr("Text"));
-//    action->setCheckable(true);
-//    action->setShortcut(tr("5"));
-//    connect(action, SIGNAL(triggered()), this, SLOT(toolTextSelected()));
-//    group->addAction(action);
-//    m_toolActions[ViewManager::TextMode] = action;
-
     toolNavigateSelected();
 
     Pane::registerShortcuts(*m_keyReference);
@@ -1898,17 +1956,17 @@
 }
 
 void
+MainWindow::toolEraseSelected()
+{
+    m_viewManager->setToolMode(ViewManager::EraseMode);
+}
+
+void
 MainWindow::toolMeasureSelected()
 {
     m_viewManager->setToolMode(ViewManager::MeasureMode);
 }
 
-//void
-//MainWindow::toolTextSelected()
-//{
-//    m_viewManager->setToolMode(ViewManager::TextMode);
-//}
-
 void
 MainWindow::importAudio()
 {
@@ -2508,11 +2566,6 @@
         m_preferencesDialog->applicationClosing(false);
     }
 
-    if (m_layerTreeView &&
-        m_layerTreeView->isVisible()) {
-        delete m_layerTreeView;
-    }
-
     closeSession();
 
     e->accept();
@@ -2659,6 +2712,20 @@
 }
 
 void
+MainWindow::propertyStacksResized(int width)
+{
+    std::cerr << "MainWindow::propertyStacksResized(" << width << ")" << std::endl;
+
+    if (!m_playControlsSpacer) return;
+
+    int spacerWidth = width - m_playControlsWidth - 4;
+    
+    std::cerr << "resizing spacer from " << m_playControlsSpacer->width() << " to " << spacerWidth << std::endl;
+
+    m_playControlsSpacer->setFixedSize(QSize(spacerWidth, 2));
+}
+
+void
 MainWindow::addPane()
 {
     QObject *s = sender();
@@ -2682,7 +2749,7 @@
 }
 
 void
-MainWindow::addPane(const PaneConfiguration &configuration, QString text)
+MainWindow::addPane(const LayerConfiguration &configuration, QString text)
 {
     CommandHistory::getInstance()->startCompoundOperation(text, true);
 
@@ -2719,7 +2786,7 @@
     if (suggestedModel) {
 
         // check its validity
-        std::vector<Model *> inputModels = m_document->getTransformerInputModels();
+        std::vector<Model *> inputModels = m_document->getTransformInputModels();
         for (size_t j = 0; j < inputModels.size(); ++j) {
             if (inputModels[j] == suggestedModel) {
                 model = suggestedModel;
@@ -2813,7 +2880,7 @@
 	    return;
 	}
 
-	LayerFactory::LayerType type = i->second;
+	LayerFactory::LayerType type = i->second.layer;
 	
 	LayerFactory::LayerTypeSet emptyTypes =
 	    LayerFactory::getInstance()->getValidEmptyLayerTypes();
@@ -2823,24 +2890,42 @@
 	if (emptyTypes.find(type) != emptyTypes.end()) {
 
 	    newLayer = m_document->createEmptyLayer(type);
-	    m_toolActions[ViewManager::DrawMode]->trigger();
+            if (newLayer) {
+                m_toolActions[ViewManager::DrawMode]->trigger();
+            }
 
 	} else {
 
-	    newLayer = m_document->createMainModelLayer(type);
-	}
-
-	m_document->addLayerToView(pane, newLayer);
-	m_paneStack->setCurrentLayer(pane, newLayer);
+            if (!i->second.sourceModel) {
+                // e.g. time ruler
+                newLayer = m_document->createMainModelLayer(type);
+            } else {
+                newLayer = m_document->createLayer(type);
+                if (m_document->isKnownModel(i->second.sourceModel)) {
+                    m_document->setChannel(newLayer, i->second.channel);
+                    m_document->setModel(newLayer, i->second.sourceModel);
+                } else {
+                    std::cerr << "WARNING: MainWindow::addLayer: unknown model "
+                              << i->second.sourceModel
+                              << " (\""
+                              << (i->second.sourceModel ? i->second.sourceModel->objectName().toStdString() : "")
+                              << "\") in layer action map"
+                              << std::endl;
+                }
+            }
+        }
+
+        if (newLayer) {
+            m_document->addLayerToView(pane, newLayer);
+            m_paneStack->setCurrentLayer(pane, newLayer);
+        }
 
 	return;
     }
 
-    TransformId transform = i->second;
-    ModelTransformerFactory *factory = ModelTransformerFactory::getInstance();
-
-    QString configurationXml;
-
+    //!!! want to do something like this, but it's not supported in
+    //ModelTransformerFactory yet
+    /*
     int channel = -1;
     // pick up the default channel from any existing layers on the same pane
     for (int j = 0; j < pane->getLayerCount(); ++j) {
@@ -2850,44 +2935,62 @@
 	    break;
 	}
     }
+    */
 
     // We always ask for configuration, even if the plugin isn't
     // supposed to be configurable, because we need to let the user
     // change the execution context (block size etc).
 
-    PluginTransformer::ExecutionContext context(channel);
+    QString transformId = i->second;
+    Transform transform = TransformFactory::getInstance()->
+        getDefaultTransformFor(transformId);
 
     std::vector<Model *> candidateInputModels =
-        m_document->getTransformerInputModels();
-
+        m_document->getTransformInputModels();
+
+    Model *defaultInputModel = 0;
+    for (int j = 0; j < pane->getLayerCount(); ++j) {
+        Layer *layer = pane->getLayer(j);
+        if (!layer) continue;
+        if (LayerFactory::getInstance()->getLayerType(layer) !=
+            LayerFactory::Waveform &&
+            !layer->isLayerOpaque()) continue;
+        Model *model = layer->getModel();
+        if (!model) continue;
+        for (size_t k = 0; k < candidateInputModels.size(); ++k) {
+            if (candidateInputModels[k] == model) {
+                defaultInputModel = model;
+                break;
+            }
+        }
+        if (defaultInputModel) break;
+    }
+    
     size_t startFrame = 0, duration = 0;
     size_t endFrame = 0;
     m_viewManager->getSelection().getExtents(startFrame, endFrame);
     if (endFrame > startFrame) duration = endFrame - startFrame;
     else startFrame = 0;
 
-    Model *inputModel = factory->getConfigurationForTransformer
+    ModelTransformer::Input input = ModelTransformerFactory::getInstance()->
+        getConfigurationForTransform
         (transform,
          candidateInputModels,
-         context,
-         configurationXml,
+         defaultInputModel,
          m_playSource,
          startFrame,
          duration);
 
-    if (!inputModel) return;
-
-//    std::cerr << "MainWindow::addLayer: Input model is " << inputModel << " \"" << inputModel->objectName().toStdString() << "\"" << std::endl;
-
-    Layer *newLayer = m_document->createDerivedLayer(transform,
-                                                     inputModel,
-                                                     context,
-                                                     configurationXml);
+    if (!input.getModel()) return;
+
+//    std::cerr << "MainWindow::addLayer: Input model is " << input.getModel() << " \"" << input.getModel()->objectName().toStdString() << "\"" << std::endl;
+
+    Layer *newLayer = m_document->createDerivedLayer(transform, input);
 
     if (newLayer) {
         m_document->addLayerToView(pane, newLayer);
-        m_document->setChannel(newLayer, context.channel);
-        m_recentTransforms.add(transform);
+        m_document->setChannel(newLayer, input.getChannel());
+        m_recentTransforms.add(transformId);
         m_paneStack->setCurrentLayer(pane, newLayer);
     }
 
@@ -2907,7 +3010,7 @@
 		 tr("New name for this layer:"),
 		 QLineEdit::Normal, layer->objectName(), &ok);
 	    if (ok) {
-		layer->setObjectName(newName);
+		layer->setPresentationName(newName);
 		setupExistingLayersMenus();
 	    }
 	}
@@ -2986,38 +3089,12 @@
                            .arg(pc));
     }
 
-    m_playSharpen->setEnabled(something);
-    m_playMono->setEnabled(something);
-    bool sharpen = (something && m_playSharpen->isChecked());
-    bool mono = (something && m_playMono->isChecked());
-    m_playSource->setTimeStretch(factor, sharpen, mono);
+    m_playSource->setTimeStretch(factor);
 
     updateMenuStates();
 }
 
 void
-MainWindow::playSharpenToggled()
-{
-    QSettings settings;
-    settings.beginGroup("MainWindow");
-    settings.setValue("playsharpen", m_playSharpen->isChecked());
-    settings.endGroup();
-
-    playSpeedChanged(m_playSpeed->value());
-}
-
-void
-MainWindow::playMonoToggled()
-{
-    QSettings settings;
-    settings.beginGroup("MainWindow");
-    settings.setValue("playmono", m_playMono->isChecked());
-    settings.endGroup();
-
-    playSpeedChanged(m_playSpeed->value());
-}    
-
-void
 MainWindow::speedUpPlayback()
 {
     int value = m_playSpeed->value();
@@ -3042,6 +3119,27 @@
 }
 
 void
+MainWindow::currentPaneChanged(Pane *pane)
+{
+    MainWindowBase::currentPaneChanged(pane);
+
+    if (!pane || !m_panLayer) return;
+    for (int i = pane->getLayerCount(); i > 0; ) {
+        --i;
+        Layer *layer = pane->getLayer(i);
+        if (LayerFactory::getInstance()->getLayerType(layer) ==
+            LayerFactory::Waveform) {
+            RangeSummarisableTimeValueModel *tvm = 
+                dynamic_cast<RangeSummarisableTimeValueModel *>(layer->getModel());
+            if (tvm) {
+                m_panLayer->setModel(tvm);
+                return;
+            }
+        }
+    }
+}
+
+void
 MainWindow::updateVisibleRangeDisplay(Pane *p) const
 {
     if (!getMainModel() || !p) {
@@ -3194,28 +3292,76 @@
 MainWindow::resetInstantsCounters()
 {
     LabelCounterInputDialog dialog(m_labeller, this);
+    dialog.setWindowTitle(tr("Reset Counters"));
     dialog.exec();
 }
 
 void
-MainWindow::modelGenerationFailed(QString transformName)
+MainWindow::modelGenerationFailed(QString transformName, QString message)
+{
+    if (message != "") {
+
+        QMessageBox::warning
+            (this,
+             tr("Failed to generate layer"),
+             tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2")
+             .arg(transformName).arg(message),
+             QMessageBox::Ok);
+    } else {
+        QMessageBox::warning
+            (this,
+             tr("Failed to generate layer"),
+             tr("<b>Layer generation failed</b><p>Failed to generate a derived layer.<p>The layer transform \"%1\" failed.<p>No error information is available.")
+             .arg(transformName),
+             QMessageBox::Ok);
+    }
+}
+
+void
+MainWindow::modelGenerationWarning(QString transformName, QString message)
+{
+    QMessageBox::warning
+        (this, tr("Warning"), message, QMessageBox::Ok);
+}
+
+void
+MainWindow::modelRegenerationFailed(QString layerName,
+                                    QString transformName, QString message)
+{
+    if (message != "") {
+
+        QMessageBox::warning
+            (this,
+             tr("Failed to regenerate layer"),
+             tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed:<p>%3")
+             .arg(layerName).arg(transformName).arg(message),
+             QMessageBox::Ok);
+    } else {
+        QMessageBox::warning
+            (this,
+             tr("Failed to regenerate layer"),
+             tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed.<p>No error information is available.")
+             .arg(layerName).arg(transformName),
+             QMessageBox::Ok);
+    }
+}
+
+void
+MainWindow::modelRegenerationWarning(QString layerName,
+                                     QString transformName, QString message)
+{
+    QMessageBox::warning
+        (this, tr("Warning"), tr("<b>Warning when regenerating layer</b><p>When regenerating the derived layer \"%1\" using new data model as input:<p>%2").arg(layerName).arg(message), QMessageBox::Ok);
+}
+
+void
+MainWindow::alignmentFailed(QString transformName, QString message)
 {
     QMessageBox::warning
         (this,
-         tr("Failed to generate layer"),
-         tr("<b>Layer generation failed</b><p>Failed to generate a derived layer.<p>The layer transform \"%1\" failed.<p>This may mean that a plugin failed to initialise, perhaps because it rejected the processing block size that was requested.")
-         .arg(transformName),
-         QMessageBox::Ok);
-}
-
-void
-MainWindow::modelRegenerationFailed(QString layerName, QString transformName)
-{
-    QMessageBox::warning
-        (this,
-         tr("Failed to regenerate layer"),
-         tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\".<p>The layer transform \"%2\" failed to run.<p>This may mean that the layer used a plugin that is not currently available.")
-         .arg(layerName).arg(transformName),
+         tr("Failed to calculate alignment"),
+         tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment using transform \"%1\":<p>%2")
+         .arg(transformName).arg(message),
          QMessageBox::Ok);
 }
 
@@ -3230,508 +3376,15 @@
 void
 MainWindow::showLayerTree()
 {
-    if (!m_layerTreeView.isNull()) {
-        m_layerTreeView->show();
-        m_layerTreeView->raise();
+    if (!m_layerTreeDialog.isNull()) {
+        m_layerTreeDialog->show();
+        m_layerTreeDialog->raise();
         return;
     }
 
-    //!!! should use an actual dialog class
-        
-    m_layerTreeView = new QTreeView();
-    LayerTreeModel *tree = new LayerTreeModel(m_paneStack);
-    m_layerTreeView->resize(500, 300); //!!!
-    m_layerTreeView->setModel(tree);
-    m_layerTreeView->expandAll();
-    m_layerTreeView->show();
-}
-
-void
-MainWindow::handleOSCMessage(const OSCMessage &message)
-{
-    std::cerr << "MainWindow::handleOSCMessage: thread id = " 
-              << QThread::currentThreadId() << std::endl;
-
-    // This large function should really be abstracted out.
-
-    if (message.getMethod() == "open") {
-
-        if (message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-            QString path = message.getArg(0).toString();
-            if (open(path, ReplaceMainModel) != FileOpenSucceeded) {
-                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
-                          << path.toStdString() << "\"" << std::endl;
-            }
-            //!!! we really need to spin here and not return until the
-            // file has been completely decoded...
-        }
-
-    } else if (message.getMethod() == "openadditional") {
-
-        if (message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-            QString path = message.getArg(0).toString();
-            if (open(path, CreateAdditionalModel) != FileOpenSucceeded) {
-                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
-                          << path.toStdString() << "\"" << std::endl;
-            }
-        }
-
-    } else if (message.getMethod() == "recent" ||
-               message.getMethod() == "last") {
-
-        int n = 0;
-        if (message.getMethod() == "recent" &&
-            message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::Int)) {
-            n = message.getArg(0).toInt() - 1;
-        }
-        std::vector<QString> recent = m_recentFiles.getRecent();
-        if (n >= 0 && n < int(recent.size())) {
-            if (open(recent[n], ReplaceMainModel) != FileOpenSucceeded) {
-                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
-                          << recent[n].toStdString() << "\"" << std::endl;
-            }
-        }
-
-    } else if (message.getMethod() == "save") {
-
-        QString path;
-        if (message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-            path = message.getArg(0).toString();
-            if (QFileInfo(path).exists()) {
-                std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in save" << std::endl;
-            } else {
-                saveSessionFile(path);
-            }
-        }
-
-    } else if (message.getMethod() == "export") {
-
-        QString path;
-        if (getMainModel()) {
-            if (message.getArgCount() == 1 &&
-                message.getArg(0).canConvert(QVariant::String)) {
-                path = message.getArg(0).toString();
-                if (QFileInfo(path).exists()) {
-                    std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in export" << std::endl;
-                } else {
-                    WavFileWriter writer(path,
-                                         getMainModel()->getSampleRate(),
-                                         getMainModel()->getChannelCount());
-                    MultiSelection ms = m_viewManager->getSelection();
-                    if (!ms.getSelections().empty()) {
-                        writer.writeModel(getMainModel(), &ms);
-                    } else {
-                        writer.writeModel(getMainModel());
-                    }
-                }
-            }
-        }
-
-    } else if (message.getMethod() == "jump" ||
-               message.getMethod() == "play") {
-
-        if (getMainModel()) {
-
-            unsigned long frame = m_viewManager->getPlaybackFrame();
-            bool selection = false;
-            bool play = (message.getMethod() == "play");
-
-            if (message.getArgCount() == 1) {
-
-                if (message.getArg(0).canConvert(QVariant::String) &&
-                    message.getArg(0).toString() == "selection") {
-
-                    selection = true;
-
-                } else if (message.getArg(0).canConvert(QVariant::String) &&
-                           message.getArg(0).toString() == "end") {
-
-                    frame = getMainModel()->getEndFrame();
-
-                } else if (message.getArg(0).canConvert(QVariant::Double)) {
-
-                    double time = message.getArg(0).toDouble();
-                    if (time < 0.0) time = 0.0;
-
-                    frame = lrint(time * getMainModel()->getSampleRate());
-                }
-            }
-
-            if (frame > getMainModel()->getEndFrame()) {
-                frame = getMainModel()->getEndFrame();
-            }
-
-            if (play) {
-                m_viewManager->setPlaySelectionMode(selection);
-            } 
-
-            if (selection) {
-                MultiSelection::SelectionList sl = m_viewManager->getSelections();
-                if (!sl.empty()) {
-                    frame = sl.begin()->getStartFrame();
-                }
-            }
-
-            m_viewManager->setPlaybackFrame(frame);
-
-            if (play && !m_playSource->isPlaying()) {
-                m_playSource->play(frame);
-            }
-        }
-
-    } else if (message.getMethod() == "stop") {
-            
-        if (m_playSource->isPlaying()) m_playSource->stop();
-
-    } else if (message.getMethod() == "loop") {
-
-        if (message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-
-            QString str = message.getArg(0).toString();
-            if (str == "on") {
-                m_viewManager->setPlayLoopMode(true);
-            } else if (str == "off") {
-                m_viewManager->setPlayLoopMode(false);
-            }
-        }
-
-    } else if (message.getMethod() == "solo") {
-
-        if (message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-
-            QString str = message.getArg(0).toString();
-            if (str == "on") {
-                m_viewManager->setPlaySoloMode(true);
-            } else if (str == "off") {
-                m_viewManager->setPlaySoloMode(false);
-            }
-        }
-
-    } else if (message.getMethod() == "select" ||
-               message.getMethod() == "addselect") {
-
-        if (getMainModel()) {
-
-            int f0 = getMainModel()->getStartFrame();
-            int f1 = getMainModel()->getEndFrame();
-
-            bool done = false;
-
-            if (message.getArgCount() == 2 &&
-                message.getArg(0).canConvert(QVariant::Double) &&
-                message.getArg(1).canConvert(QVariant::Double)) {
-                
-                double t0 = message.getArg(0).toDouble();
-                double t1 = message.getArg(1).toDouble();
-                if (t1 < t0) { double temp = t0; t0 = t1; t1 = temp; }
-                if (t0 < 0.0) t0 = 0.0;
-                if (t1 < 0.0) t1 = 0.0;
-
-                f0 = lrint(t0 * getMainModel()->getSampleRate());
-                f1 = lrint(t1 * getMainModel()->getSampleRate());
-                
-                Pane *pane = m_paneStack->getCurrentPane();
-                Layer *layer = 0;
-                if (pane) layer = pane->getSelectedLayer();
-                if (layer) {
-                    size_t resolution;
-                    layer->snapToFeatureFrame(pane, f0, resolution,
-                                              Layer::SnapLeft);
-                    layer->snapToFeatureFrame(pane, f1, resolution,
-                                              Layer::SnapRight);
-                }
-
-            } else if (message.getArgCount() == 1 &&
-                       message.getArg(0).canConvert(QVariant::String)) {
-
-                QString str = message.getArg(0).toString();
-                if (str == "none") {
-                    m_viewManager->clearSelections();
-                    done = true;
-                }
-            }
-
-            if (!done) {
-                if (message.getMethod() == "select") {
-                    m_viewManager->setSelection(Selection(f0, f1));
-                } else {
-                    m_viewManager->addSelection(Selection(f0, f1));
-                }
-            }
-        }
-
-    } else if (message.getMethod() == "add") {
-
-        if (getMainModel()) {
-
-            if (message.getArgCount() >= 1 &&
-                message.getArg(0).canConvert(QVariant::String)) {
-
-                int channel = -1;
-                if (message.getArgCount() == 2 &&
-                    message.getArg(0).canConvert(QVariant::Int)) {
-                    channel = message.getArg(0).toInt();
-                    if (channel < -1 ||
-                        channel > int(getMainModel()->getChannelCount())) {
-                        std::cerr << "WARNING: MainWindow::handleOSCMessage: channel "
-                                  << channel << " out of range" << std::endl;
-                        channel = -1;
-                    }
-                }
-
-                QString str = message.getArg(0).toString();
-                
-                LayerFactory::LayerType type =
-                    LayerFactory::getInstance()->getLayerTypeForName(str);
-
-                if (type == LayerFactory::UnknownLayer) {
-                    std::cerr << "WARNING: MainWindow::handleOSCMessage: unknown layer "
-                              << "type " << str.toStdString() << std::endl;
-                } else {
-
-                    PaneConfiguration configuration(type,
-                                                    getMainModel(),
-                                                    channel);
-                    
-                    addPane(configuration,
-                            tr("Add %1 Pane")
-                            .arg(LayerFactory::getInstance()->
-                                 getLayerPresentationName(type)));
-                }
-            }
-        }
-
-    } else if (message.getMethod() == "undo") {
-
-        CommandHistory::getInstance()->undo();
-
-    } else if (message.getMethod() == "redo") {
-
-        CommandHistory::getInstance()->redo();
-
-    } else if (message.getMethod() == "set") {
-
-        if (message.getArgCount() == 2 &&
-            message.getArg(0).canConvert(QVariant::String) &&
-            message.getArg(1).canConvert(QVariant::Double)) {
-
-            QString property = message.getArg(0).toString();
-            float value = (float)message.getArg(1).toDouble();
-
-            if (property == "gain") {
-                if (value < 0.0) value = 0.0;
-                m_fader->setValue(value);
-                if (m_playTarget) m_playTarget->setOutputGain(value);
-            } else if (property == "speedup") {
-                m_playSpeed->setMappedValue(value);
-            } else if (property == "overlays") {
-                if (value < 0.5) {
-                    m_viewManager->setOverlayMode(ViewManager::NoOverlays);
-                } else if (value < 1.5) {
-                    m_viewManager->setOverlayMode(ViewManager::MinimalOverlays);
-                } else if (value < 2.5) {
-                    m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
-                } else {
-                    m_viewManager->setOverlayMode(ViewManager::AllOverlays);
-                }                    
-            } else if (property == "zoomwheels") {
-                m_viewManager->setZoomWheelsEnabled(value > 0.5);
-            } else if (property == "propertyboxes") {
-                bool toggle = ((value < 0.5) !=
-                               (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks));
-                if (toggle) togglePropertyBoxes();
-            }
-                
-        } else {
-            PropertyContainer *container = 0;
-            Pane *pane = m_paneStack->getCurrentPane();
-            if (pane &&
-                message.getArgCount() == 3 &&
-                message.getArg(0).canConvert(QVariant::String) &&
-                message.getArg(1).canConvert(QVariant::String) &&
-                message.getArg(2).canConvert(QVariant::String)) {
-                if (message.getArg(0).toString() == "pane") {
-                    container = pane->getPropertyContainer(0);
-                } else if (message.getArg(0).toString() == "layer") {
-                    container = pane->getSelectedLayer();
-                }
-            }
-            if (container) {
-                QString nameString = message.getArg(1).toString();
-                QString valueString = message.getArg(2).toString();
-                container->setPropertyWithCommand(nameString, valueString);
-            }
-        }
-
-    } else if (message.getMethod() == "setcurrent") {
-
-        int paneIndex = -1, layerIndex = -1;
-        bool wantLayer = false;
-
-        if (message.getArgCount() >= 1 &&
-            message.getArg(0).canConvert(QVariant::Int)) {
-
-            paneIndex = message.getArg(0).toInt() - 1;
-
-            if (message.getArgCount() >= 2 &&
-                message.getArg(1).canConvert(QVariant::Int)) {
-                wantLayer = true;
-                layerIndex = message.getArg(1).toInt() - 1;
-            }
-        }
-
-        if (paneIndex >= 0 && paneIndex < m_paneStack->getPaneCount()) {
-            Pane *pane = m_paneStack->getPane(paneIndex);
-            m_paneStack->setCurrentPane(pane);
-            if (layerIndex >= 0 && layerIndex < pane->getLayerCount()) {
-                Layer *layer = pane->getLayer(layerIndex);
-                m_paneStack->setCurrentLayer(pane, layer);
-            } else if (wantLayer && layerIndex == -1) {
-                m_paneStack->setCurrentLayer(pane, 0);
-            }
-        }
-
-    } else if (message.getMethod() == "delete") {
-
-        if (message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-            
-            QString target = message.getArg(0).toString();
-
-            if (target == "pane") {
-
-                deleteCurrentPane();
-
-            } else if (target == "layer") {
-
-                deleteCurrentLayer();
-
-            } else {
-                
-                std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown delete target " << target.toStdString() << std::endl;
-            }
-        }
-
-    } else if (message.getMethod() == "zoom") {
-
-        if (message.getArgCount() == 1) {
-            if (message.getArg(0).canConvert(QVariant::String) &&
-                message.getArg(0).toString() == "in") {
-                zoomIn();
-            } else if (message.getArg(0).canConvert(QVariant::String) &&
-                       message.getArg(0).toString() == "out") {
-                zoomOut();
-            } else if (message.getArg(0).canConvert(QVariant::String) &&
-                       message.getArg(0).toString() == "default") {
-                zoomDefault();
-            } else if (message.getArg(0).canConvert(QVariant::Double)) {
-                double level = message.getArg(0).toDouble();
-                Pane *currentPane = m_paneStack->getCurrentPane();
-                if (level < 1.0) level = 1.0;
-                if (currentPane) currentPane->setZoomLevel(lrint(level));
-            }
-        }
-
-    } else if (message.getMethod() == "zoomvertical") {
-
-        Pane *pane = m_paneStack->getCurrentPane();
-        Layer *layer = 0;
-        if (pane && pane->getLayerCount() > 0) {
-            layer = pane->getLayer(pane->getLayerCount() - 1);
-        }
-        int defaultStep = 0;
-        int steps = 0;
-        if (layer) {
-            steps = layer->getVerticalZoomSteps(defaultStep);
-            if (message.getArgCount() == 1 && steps > 0) {
-                if (message.getArg(0).canConvert(QVariant::String) &&
-                    message.getArg(0).toString() == "in") {
-                    int step = layer->getCurrentVerticalZoomStep() + 1;
-                    if (step < steps) layer->setVerticalZoomStep(step);
-                } else if (message.getArg(0).canConvert(QVariant::String) &&
-                           message.getArg(0).toString() == "out") {
-                    int step = layer->getCurrentVerticalZoomStep() - 1;
-                    if (step >= 0) layer->setVerticalZoomStep(step);
-                } else if (message.getArg(0).canConvert(QVariant::String) &&
-                           message.getArg(0).toString() == "default") {
-                    layer->setVerticalZoomStep(defaultStep);
-                }
-            } else if (message.getArgCount() == 2) {
-                if (message.getArg(0).canConvert(QVariant::Double) &&
-                    message.getArg(1).canConvert(QVariant::Double)) {
-                    double min = message.getArg(0).toDouble();
-                    double max = message.getArg(1).toDouble();
-                    layer->setDisplayExtents(min, max);
-                }
-            }
-        }
-
-    } else if (message.getMethod() == "quit") {
-        
-        m_abandoning = true;
-        close();
-
-    } else if (message.getMethod() == "resize") {
-        
-        if (message.getArgCount() == 2) {
-
-            int width = 0, height = 0;
-
-            if (message.getArg(1).canConvert(QVariant::Int)) {
-
-                height = message.getArg(1).toInt();
-
-                if (message.getArg(0).canConvert(QVariant::String) &&
-                    message.getArg(0).toString() == "pane") {
-
-                    Pane *pane = m_paneStack->getCurrentPane();
-                    if (pane) pane->resize(pane->width(), height);
-
-                } else if (message.getArg(0).canConvert(QVariant::Int)) {
-
-                    width = message.getArg(0).toInt();
-                    resize(width, height);
-                }
-            }
-        }
-
-    } else if (message.getMethod() == "transform") {
-
-        Pane *pane = m_paneStack->getCurrentPane();
-
-        if (getMainModel() &&
-            pane &&
-            message.getArgCount() == 1 &&
-            message.getArg(0).canConvert(QVariant::String)) {
-
-            TransformId transform = message.getArg(0).toString();
-
-            Layer *newLayer = m_document->createDerivedLayer
-                (transform,
-                 getMainModel(),
-                 ModelTransformerFactory::getInstance()->getDefaultContextForTransformer
-                 (transform, getMainModel()),
-                 "");
-
-            if (newLayer) {
-                m_document->addLayerToView(pane, newLayer);
-                m_recentTransforms.add(transform);
-                m_paneStack->setCurrentLayer(pane, newLayer);
-            }
-        }
-
-    } else {
-        std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown or unsupported "
-                  << "method \"" << message.getMethod().toStdString()
-                  << "\"" << std::endl;
-    }
-            
+    m_layerTreeDialog = new LayerTreeDialog(m_paneStack);
+    m_layerTreeDialog->setAttribute(Qt::WA_DeleteOnClose); // see below
+    m_layerTreeDialog->show();
 }
 
 void
@@ -3766,10 +3419,6 @@
         contextHelpChanged(tr("Adjust the master playback level"));
     } else if (w == m_playSpeed) {
         contextHelpChanged(tr("Adjust the master playback speed"));
-    } else if (w == m_playSharpen && w->isEnabled()) {
-        contextHelpChanged(tr("Toggle transient sharpening for playback time scaling"));
-    } else if (w == m_playMono && w->isEnabled()) {
-        contextHelpChanged(tr("Toggle mono mode for playback time scaling"));
     }
 }
 
@@ -3788,7 +3437,7 @@
 void
 MainWindow::help()
 {
-    openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/1.0/en/"));
+    openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/1.2/en/"));
 }
 
 void
@@ -3815,7 +3464,7 @@
     QString aboutText;
 
     aboutText += tr("<h3>About Sonic Visualiser</h3>");
-    aboutText += tr("<p>Sonic Visualiser is a program for viewing and exploring audio data for<br>semantic music analysis and annotation.</p>");
+    aboutText += tr("<p>Sonic Visualiser is a program for viewing and exploring audio data for semantic music analysis and annotation.</p>");
     aboutText += tr("<p>%1 : %2 configuration</p>")
         .arg(version)
         .arg(debug ? tr("Debug") : tr("Release"));
@@ -3897,11 +3546,11 @@
 #endif
 
     aboutText += 
-        "<p>Sonic Visualiser Copyright &copy; 2005 - 2007 Chris Cannam and<br>"
+        "<p>Sonic Visualiser Copyright &copy; 2005 - 2008 Chris Cannam and "
         "Queen Mary, University of London.</p>"
-        "<p>This program is free software; you can redistribute it and/or<br>"
-        "modify it under the terms of the GNU General Public License as<br>"
-        "published by the Free Software Foundation; either version 2 of the<br>"
+        "<p>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.<br>See the file "
         "COPYING included with this distribution for more information.</p>";
     
--- a/main/MainWindow.h	Tue Nov 13 13:51:07 2007 +0000
+++ b/main/MainWindow.h	Wed Feb 27 11:59:42 2008 +0000
@@ -31,6 +31,7 @@
 #include "framework/SVFileReader.h"
 #include "data/fileio/FileFinder.h"
 #include "data/fileio/FileSource.h"
+#include "widgets/LayerTreeDialog.h"
 #include <map>
 
 class Document;
@@ -99,6 +100,7 @@
     virtual void toolSelectSelected();
     virtual void toolEditSelected();
     virtual void toolDrawSelected();
+    virtual void toolEraseSelected();
     virtual void toolMeasureSelected();
 
     virtual void documentModified();
@@ -111,11 +113,16 @@
     virtual void setInstantsCounterCycle();
     virtual void resetInstantsCounters();
 
-    virtual void modelGenerationFailed(QString);
-    virtual void modelRegenerationFailed(QString, QString);
+    virtual void modelGenerationFailed(QString, QString);
+    virtual void modelGenerationWarning(QString, QString);
+    virtual void modelRegenerationFailed(QString, QString, QString);
+    virtual void modelRegenerationWarning(QString, QString, QString);
+    virtual void alignmentFailed(QString, QString);
 
     virtual void rightButtonMenuRequested(Pane *, QPoint point);
 
+    virtual void propertyStacksResized(int);
+
     virtual void addPane();
     virtual void addLayer();
     virtual void renameCurrentLayer();
@@ -130,11 +137,11 @@
     virtual void setupRecentTransformsMenu();
 
     virtual void playSpeedChanged(int);
-    virtual void playSharpenToggled();
-    virtual void playMonoToggled();
     virtual void playSoloToggled();
     virtual void alignToggled();
 
+    virtual void currentPaneChanged(Pane *);
+
     virtual void speedUpPlayback();
     virtual void slowDownPlayback();
     virtual void restoreNormalPlayback();
@@ -163,8 +170,6 @@
     Overview                *m_overview;
     Fader                   *m_fader;
     AudioDial               *m_playSpeed;
-    QPushButton             *m_playSharpen;
-    QPushButton             *m_playMono;
     WaveformLayer           *m_panLayer;
 
     bool                     m_mainMenusCreated;
@@ -189,34 +194,37 @@
     bool                     m_soloModified;
     bool                     m_prevSolo;
 
+    QFrame                  *m_playControlsSpacer;
+    int                      m_playControlsWidth;
+
     QPointer<PreferencesDialog> m_preferencesDialog;
-    QPointer<QTreeView>      m_layerTreeView;
+    QPointer<LayerTreeDialog>   m_layerTreeDialog;
 
     KeyReference            *m_keyReference;
 
-    struct PaneConfiguration {
-	PaneConfiguration(LayerFactory::LayerType _layer
+    struct LayerConfiguration {
+	LayerConfiguration(LayerFactory::LayerType _layer
 			                       = LayerFactory::TimeRuler,
-                          Model *_source = 0,
-			  int _channel = -1) :
+                           Model *_source = 0,
+                           int _channel = -1) :
 	    layer(_layer), sourceModel(_source), channel(_channel) { }
 	LayerFactory::LayerType layer;
         Model *sourceModel;
 	int channel;
     };
 
-    typedef std::map<QAction *, PaneConfiguration> PaneActionMap;
+    typedef std::map<QAction *, LayerConfiguration> PaneActionMap;
     PaneActionMap m_paneActions;
 
+    typedef std::map<QAction *, LayerConfiguration> LayerActionMap;
+    LayerActionMap m_layerActions;
+
     typedef std::map<QAction *, TransformId> TransformActionMap;
     TransformActionMap m_transformActions;
 
     typedef std::map<TransformId, QAction *> TransformActionReverseMap;
     TransformActionReverseMap m_transformActionsReverse;
 
-    typedef std::map<QAction *, LayerFactory::LayerType> LayerActionMap;
-    LayerActionMap m_layerActions;
-
     typedef std::map<QAction *, Layer *> ExistingLayerActionMap;
     ExistingLayerActionMap m_existingLayerActions;
     ExistingLayerActionMap m_sliceActions;
@@ -237,7 +245,7 @@
     virtual void setupExistingLayersMenus();
     virtual void setupToolbars();
 
-    virtual void addPane(const PaneConfiguration &configuration, QString text);
+    virtual void addPane(const LayerConfiguration &configuration, QString text);
 
     virtual void closeEvent(QCloseEvent *e);
     virtual bool checkSaveModified();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/OSCHandler.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -0,0 +1,520 @@
+/* -*- 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-2007 Chris Cannam and QMUL.
+    
+    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 "MainWindow.h"
+#include "data/osc/OSCQueue.h"
+
+#include "layer/WaveformLayer.h"
+#include "view/ViewManager.h"
+#include "view/Pane.h"
+#include "view/PaneStack.h"
+#include "data/model/WaveFileModel.h"
+#include "base/CommandHistory.h"
+#include "audioio/AudioCallbackPlaySource.h"
+#include "audioio/AudioCallbackPlayTarget.h"
+#include "framework/Document.h"
+#include "data/fileio/WavFileWriter.h"
+#include "plugin/transform/TransformFactory.h"
+#include "widgets/Fader.h"
+#include "widgets/AudioDial.h"
+
+#include <QFileInfo>
+
+void
+MainWindow::handleOSCMessage(const OSCMessage &message)
+{
+    std::cerr << "MainWindow::handleOSCMessage: thread id = " 
+              << QThread::currentThreadId() << std::endl;
+
+    // This large function should really be abstracted out.
+
+    if (message.getMethod() == "open") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            QString path = message.getArg(0).toString();
+            if (open(path, ReplaceMainModel) != FileOpenSucceeded) {
+                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
+                          << path.toStdString() << "\"" << std::endl;
+            }
+            //!!! we really need to spin here and not return until the
+            // file has been completely decoded...
+        }
+
+    } else if (message.getMethod() == "openadditional") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            QString path = message.getArg(0).toString();
+            if (open(path, CreateAdditionalModel) != FileOpenSucceeded) {
+                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
+                          << path.toStdString() << "\"" << std::endl;
+            }
+        }
+
+    } else if (message.getMethod() == "recent" ||
+               message.getMethod() == "last") {
+
+        int n = 0;
+        if (message.getMethod() == "recent" &&
+            message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::Int)) {
+            n = message.getArg(0).toInt() - 1;
+        }
+        std::vector<QString> recent = m_recentFiles.getRecent();
+        if (n >= 0 && n < int(recent.size())) {
+            if (open(recent[n], ReplaceMainModel) != FileOpenSucceeded) {
+                std::cerr << "MainWindow::handleOSCMessage: File open failed for path \""
+                          << recent[n].toStdString() << "\"" << std::endl;
+            }
+        }
+
+    } else if (message.getMethod() == "save") {
+
+        QString path;
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            path = message.getArg(0).toString();
+            if (QFileInfo(path).exists()) {
+                std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in save" << std::endl;
+            } else {
+                saveSessionFile(path);
+            }
+        }
+
+    } else if (message.getMethod() == "export") {
+
+        QString path;
+        if (getMainModel()) {
+            if (message.getArgCount() == 1 &&
+                message.getArg(0).canConvert(QVariant::String)) {
+                path = message.getArg(0).toString();
+                if (QFileInfo(path).exists()) {
+                    std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in export" << std::endl;
+                } else {
+                    WavFileWriter writer(path,
+                                         getMainModel()->getSampleRate(),
+                                         getMainModel()->getChannelCount());
+                    MultiSelection ms = m_viewManager->getSelection();
+                    if (!ms.getSelections().empty()) {
+                        writer.writeModel(getMainModel(), &ms);
+                    } else {
+                        writer.writeModel(getMainModel());
+                    }
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "jump" ||
+               message.getMethod() == "play") {
+
+        if (getMainModel()) {
+
+            unsigned long frame = m_viewManager->getPlaybackFrame();
+            bool selection = false;
+            bool play = (message.getMethod() == "play");
+
+            if (message.getArgCount() == 1) {
+
+                if (message.getArg(0).canConvert(QVariant::String) &&
+                    message.getArg(0).toString() == "selection") {
+
+                    selection = true;
+
+                } else if (message.getArg(0).canConvert(QVariant::String) &&
+                           message.getArg(0).toString() == "end") {
+
+                    frame = getMainModel()->getEndFrame();
+
+                } else if (message.getArg(0).canConvert(QVariant::Double)) {
+
+                    double time = message.getArg(0).toDouble();
+                    if (time < 0.0) time = 0.0;
+
+                    frame = lrint(time * getMainModel()->getSampleRate());
+                }
+            }
+
+            if (frame > getMainModel()->getEndFrame()) {
+                frame = getMainModel()->getEndFrame();
+            }
+
+            if (play) {
+                m_viewManager->setPlaySelectionMode(selection);
+            } 
+
+            if (selection) {
+                MultiSelection::SelectionList sl = m_viewManager->getSelections();
+                if (!sl.empty()) {
+                    frame = sl.begin()->getStartFrame();
+                }
+            }
+
+            m_viewManager->setPlaybackFrame(frame);
+
+            if (play && !m_playSource->isPlaying()) {
+                m_playSource->play(frame);
+            }
+        }
+
+    } else if (message.getMethod() == "stop") {
+            
+        if (m_playSource->isPlaying()) m_playSource->stop();
+
+    } else if (message.getMethod() == "loop") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+
+            QString str = message.getArg(0).toString();
+            if (str == "on") {
+                m_viewManager->setPlayLoopMode(true);
+            } else if (str == "off") {
+                m_viewManager->setPlayLoopMode(false);
+            }
+        }
+
+    } else if (message.getMethod() == "solo") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+
+            QString str = message.getArg(0).toString();
+            if (str == "on") {
+                m_viewManager->setPlaySoloMode(true);
+            } else if (str == "off") {
+                m_viewManager->setPlaySoloMode(false);
+            }
+        }
+
+    } else if (message.getMethod() == "select" ||
+               message.getMethod() == "addselect") {
+
+        if (getMainModel()) {
+
+            int f0 = getMainModel()->getStartFrame();
+            int f1 = getMainModel()->getEndFrame();
+
+            bool done = false;
+
+            if (message.getArgCount() == 2 &&
+                message.getArg(0).canConvert(QVariant::Double) &&
+                message.getArg(1).canConvert(QVariant::Double)) {
+                
+                double t0 = message.getArg(0).toDouble();
+                double t1 = message.getArg(1).toDouble();
+                if (t1 < t0) { double temp = t0; t0 = t1; t1 = temp; }
+                if (t0 < 0.0) t0 = 0.0;
+                if (t1 < 0.0) t1 = 0.0;
+
+                f0 = lrint(t0 * getMainModel()->getSampleRate());
+                f1 = lrint(t1 * getMainModel()->getSampleRate());
+                
+                Pane *pane = m_paneStack->getCurrentPane();
+                Layer *layer = 0;
+                if (pane) layer = pane->getSelectedLayer();
+                if (layer) {
+                    size_t resolution;
+                    layer->snapToFeatureFrame(pane, f0, resolution,
+                                              Layer::SnapLeft);
+                    layer->snapToFeatureFrame(pane, f1, resolution,
+                                              Layer::SnapRight);
+                }
+
+            } else if (message.getArgCount() == 1 &&
+                       message.getArg(0).canConvert(QVariant::String)) {
+
+                QString str = message.getArg(0).toString();
+                if (str == "none") {
+                    m_viewManager->clearSelections();
+                    done = true;
+                }
+            }
+
+            if (!done) {
+                if (message.getMethod() == "select") {
+                    m_viewManager->setSelection(Selection(f0, f1));
+                } else {
+                    m_viewManager->addSelection(Selection(f0, f1));
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "add") {
+
+        if (getMainModel()) {
+
+            if (message.getArgCount() >= 1 &&
+                message.getArg(0).canConvert(QVariant::String)) {
+
+                int channel = -1;
+                if (message.getArgCount() == 2 &&
+                    message.getArg(0).canConvert(QVariant::Int)) {
+                    channel = message.getArg(0).toInt();
+                    if (channel < -1 ||
+                        channel > int(getMainModel()->getChannelCount())) {
+                        std::cerr << "WARNING: MainWindow::handleOSCMessage: channel "
+                                  << channel << " out of range" << std::endl;
+                        channel = -1;
+                    }
+                }
+
+                QString str = message.getArg(0).toString();
+                
+                LayerFactory::LayerType type =
+                    LayerFactory::getInstance()->getLayerTypeForName(str);
+
+                if (type == LayerFactory::UnknownLayer) {
+                    std::cerr << "WARNING: MainWindow::handleOSCMessage: unknown layer "
+                              << "type " << str.toStdString() << std::endl;
+                } else {
+
+                    LayerConfiguration configuration(type,
+                                                     getMainModel(),
+                                                     channel);
+                    
+                    addPane(configuration,
+                            tr("Add %1 Pane")
+                            .arg(LayerFactory::getInstance()->
+                                 getLayerPresentationName(type)));
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "undo") {
+
+        CommandHistory::getInstance()->undo();
+
+    } else if (message.getMethod() == "redo") {
+
+        CommandHistory::getInstance()->redo();
+
+    } else if (message.getMethod() == "set") {
+
+        if (message.getArgCount() == 2 &&
+            message.getArg(0).canConvert(QVariant::String) &&
+            message.getArg(1).canConvert(QVariant::Double)) {
+
+            QString property = message.getArg(0).toString();
+            float value = (float)message.getArg(1).toDouble();
+
+            if (property == "gain") {
+                if (value < 0.0) value = 0.0;
+                m_fader->setValue(value);
+                if (m_playTarget) m_playTarget->setOutputGain(value);
+            } else if (property == "speedup") {
+                m_playSpeed->setMappedValue(value);
+            } else if (property == "overlays") {
+                if (value < 0.5) {
+                    m_viewManager->setOverlayMode(ViewManager::NoOverlays);
+                } else if (value < 1.5) {
+                    m_viewManager->setOverlayMode(ViewManager::MinimalOverlays);
+                } else if (value < 2.5) {
+                    m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
+                } else {
+                    m_viewManager->setOverlayMode(ViewManager::AllOverlays);
+                }                    
+            } else if (property == "zoomwheels") {
+                m_viewManager->setZoomWheelsEnabled(value > 0.5);
+            } else if (property == "propertyboxes") {
+                bool toggle = ((value < 0.5) !=
+                               (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks));
+                if (toggle) togglePropertyBoxes();
+            }
+                
+        } else {
+            PropertyContainer *container = 0;
+            Pane *pane = m_paneStack->getCurrentPane();
+            if (pane &&
+                message.getArgCount() == 3 &&
+                message.getArg(0).canConvert(QVariant::String) &&
+                message.getArg(1).canConvert(QVariant::String) &&
+                message.getArg(2).canConvert(QVariant::String)) {
+                if (message.getArg(0).toString() == "pane") {
+                    container = pane->getPropertyContainer(0);
+                } else if (message.getArg(0).toString() == "layer") {
+                    container = pane->getSelectedLayer();
+                }
+            }
+            if (container) {
+                QString nameString = message.getArg(1).toString();
+                QString valueString = message.getArg(2).toString();
+                container->setPropertyWithCommand(nameString, valueString);
+            }
+        }
+
+    } else if (message.getMethod() == "setcurrent") {
+
+        int paneIndex = -1, layerIndex = -1;
+        bool wantLayer = false;
+
+        if (message.getArgCount() >= 1 &&
+            message.getArg(0).canConvert(QVariant::Int)) {
+
+            paneIndex = message.getArg(0).toInt() - 1;
+
+            if (message.getArgCount() >= 2 &&
+                message.getArg(1).canConvert(QVariant::Int)) {
+                wantLayer = true;
+                layerIndex = message.getArg(1).toInt() - 1;
+            }
+        }
+
+        if (paneIndex >= 0 && paneIndex < m_paneStack->getPaneCount()) {
+            Pane *pane = m_paneStack->getPane(paneIndex);
+            m_paneStack->setCurrentPane(pane);
+            if (layerIndex >= 0 && layerIndex < pane->getLayerCount()) {
+                Layer *layer = pane->getLayer(layerIndex);
+                m_paneStack->setCurrentLayer(pane, layer);
+            } else if (wantLayer && layerIndex == -1) {
+                m_paneStack->setCurrentLayer(pane, 0);
+            }
+        }
+
+    } else if (message.getMethod() == "delete") {
+
+        if (message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+            
+            QString target = message.getArg(0).toString();
+
+            if (target == "pane") {
+
+                deleteCurrentPane();
+
+            } else if (target == "layer") {
+
+                deleteCurrentLayer();
+
+            } else {
+                
+                std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown delete target " << target.toStdString() << std::endl;
+            }
+        }
+
+    } else if (message.getMethod() == "zoom") {
+
+        if (message.getArgCount() == 1) {
+            if (message.getArg(0).canConvert(QVariant::String) &&
+                message.getArg(0).toString() == "in") {
+                zoomIn();
+            } else if (message.getArg(0).canConvert(QVariant::String) &&
+                       message.getArg(0).toString() == "out") {
+                zoomOut();
+            } else if (message.getArg(0).canConvert(QVariant::String) &&
+                       message.getArg(0).toString() == "default") {
+                zoomDefault();
+            } else if (message.getArg(0).canConvert(QVariant::Double)) {
+                double level = message.getArg(0).toDouble();
+                Pane *currentPane = m_paneStack->getCurrentPane();
+                if (level < 1.0) level = 1.0;
+                if (currentPane) currentPane->setZoomLevel(lrint(level));
+            }
+        }
+
+    } else if (message.getMethod() == "zoomvertical") {
+
+        Pane *pane = m_paneStack->getCurrentPane();
+        Layer *layer = 0;
+        if (pane && pane->getLayerCount() > 0) {
+            layer = pane->getLayer(pane->getLayerCount() - 1);
+        }
+        int defaultStep = 0;
+        int steps = 0;
+        if (layer) {
+            steps = layer->getVerticalZoomSteps(defaultStep);
+            if (message.getArgCount() == 1 && steps > 0) {
+                if (message.getArg(0).canConvert(QVariant::String) &&
+                    message.getArg(0).toString() == "in") {
+                    int step = layer->getCurrentVerticalZoomStep() + 1;
+                    if (step < steps) layer->setVerticalZoomStep(step);
+                } else if (message.getArg(0).canConvert(QVariant::String) &&
+                           message.getArg(0).toString() == "out") {
+                    int step = layer->getCurrentVerticalZoomStep() - 1;
+                    if (step >= 0) layer->setVerticalZoomStep(step);
+                } else if (message.getArg(0).canConvert(QVariant::String) &&
+                           message.getArg(0).toString() == "default") {
+                    layer->setVerticalZoomStep(defaultStep);
+                }
+            } else if (message.getArgCount() == 2) {
+                if (message.getArg(0).canConvert(QVariant::Double) &&
+                    message.getArg(1).canConvert(QVariant::Double)) {
+                    double min = message.getArg(0).toDouble();
+                    double max = message.getArg(1).toDouble();
+                    layer->setDisplayExtents(min, max);
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "quit") {
+        
+        m_abandoning = true;
+        close();
+
+    } else if (message.getMethod() == "resize") {
+        
+        if (message.getArgCount() == 2) {
+
+            int width = 0, height = 0;
+
+            if (message.getArg(1).canConvert(QVariant::Int)) {
+
+                height = message.getArg(1).toInt();
+
+                if (message.getArg(0).canConvert(QVariant::String) &&
+                    message.getArg(0).toString() == "pane") {
+
+                    Pane *pane = m_paneStack->getCurrentPane();
+                    if (pane) pane->resize(pane->width(), height);
+
+                } else if (message.getArg(0).canConvert(QVariant::Int)) {
+
+                    width = message.getArg(0).toInt();
+                    resize(width, height);
+                }
+            }
+        }
+
+    } else if (message.getMethod() == "transform") {
+
+        Pane *pane = m_paneStack->getCurrentPane();
+
+        if (getMainModel() &&
+            pane &&
+            message.getArgCount() == 1 &&
+            message.getArg(0).canConvert(QVariant::String)) {
+
+            TransformId transformId = message.getArg(0).toString();
+
+	    Transform transform = TransformFactory::getInstance()->
+                getDefaultTransformFor(transformId);
+	    
+            Layer *newLayer = m_document->createDerivedLayer
+                (transform, getMainModel());
+
+            if (newLayer) {
+                m_document->addLayerToView(pane, newLayer);
+                m_recentTransforms.add(transformId);
+                m_paneStack->setCurrentLayer(pane, newLayer);
+            }
+        }
+
+    } else {
+        std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown or unsupported "
+                  << "method \"" << message.getMethod().toStdString()
+                  << "\"" << std::endl;
+    }
+            
+}
--- a/main/PreferencesDialog.cpp	Tue Nov 13 13:51:07 2007 +0000
+++ b/main/PreferencesDialog.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -30,6 +30,7 @@
 #include <QLineEdit>
 #include <QFileDialog>
 #include <QMessageBox>
+#include <QSpinBox>
 
 #include "widgets/WindowTypeSelector.h"
 #include "widgets/IconLoader.h"
@@ -144,6 +145,13 @@
             this, SLOT(tempDirButtonClicked()));
     tempDirButton->setFixedSize(QSize(24, 24));
 
+    QCheckBox *showSplash = new QCheckBox;
+    m_showSplash = prefs->getShowSplash();
+    showSplash->setCheckState(m_showSplash ? Qt::Checked : Qt::Unchecked);
+    connect(showSplash, SIGNAL(stateChanged(int)),
+            this, SLOT(showSplashChanged(int)));
+
+#ifndef Q_WS_MAC
     QComboBox *bgMode = new QComboBox;
     int bg = prefs->getPropertyRangeAndValue("Background Mode", &min, &max,
                                              &deflt);
@@ -155,6 +163,20 @@
 
     connect(bgMode, SIGNAL(currentIndexChanged(int)),
             this, SLOT(backgroundModeChanged(int)));
+#endif
+
+    QSpinBox *fontSize = new QSpinBox;
+    int fs = prefs->getPropertyRangeAndValue("View Font Size", &min, &max,
+                                             &deflt);
+    m_viewFontSize = fs;
+    fontSize->setMinimum(min);
+    fontSize->setMaximum(max);
+    fontSize->setSuffix(" pt");
+    fontSize->setSingleStep(1);
+    fontSize->setValue(fs);
+
+    connect(fontSize, SIGNAL(valueChanged(int)),
+            this, SLOT(viewFontSizeChanged(int)));
 
     // General tab
 
@@ -170,10 +192,29 @@
                        row, 0);
     subgrid->addWidget(propertyLayout, row++, 1, 1, 2);
 
+#ifndef Q_WS_MAC
     subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
                                                 ("Background Mode"))),
                        row, 0);
     subgrid->addWidget(bgMode, row++, 1, 1, 2);
+#endif
+
+    subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
+                                                ("View Font Size"))),
+                       row, 0);
+    subgrid->addWidget(fontSize, row++, 1, 1, 2);
+
+    subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
+                                                ("Show Splash Screen"))),
+                       row, 0);
+    subgrid->addWidget(showSplash, row++, 1, 1, 1);
+
+    subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
+                                                ("Temporary Directory Root"))),
+                       row, 0);
+    subgrid->addWidget(m_tempDirRootEdit, row, 1, 1, 1);
+    subgrid->addWidget(tempDirButton, row, 2, 1, 1);
+    row++;
 
     subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
                                                 ("Resample On Load"))),
@@ -185,13 +226,6 @@
                        row, 0);
     subgrid->addWidget(resampleQuality, row++, 1, 1, 2);
 
-    subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
-                                                ("Temporary Directory Root"))),
-                       row, 0);
-    subgrid->addWidget(m_tempDirRootEdit, row, 1, 1, 1);
-    subgrid->addWidget(tempDirButton, row, 2, 1, 1);
-    row++;
-
     subgrid->setRowStretch(row, 10);
     
     tab->addTab(frame, tr("&General"));
@@ -288,6 +322,14 @@
 }
 
 void
+PreferencesDialog::showSplashChanged(int state)
+{
+    m_showSplash = (state == Qt::Checked);
+    m_applyButton->setEnabled(true);
+    m_changesOnRestart = true;
+}
+
+void
 PreferencesDialog::tempDirRootChanged(QString r)
 {
     m_tempDirRoot = r;
@@ -315,6 +357,13 @@
 }
 
 void
+PreferencesDialog::viewFontSizeChanged(int sz)
+{
+    m_viewFontSize = sz;
+    m_applyButton->setEnabled(true);
+}
+
+void
 PreferencesDialog::okClicked()
 {
     applyClicked();
@@ -333,8 +382,10 @@
     prefs->setTuningFrequency(m_tuningFrequency);
     prefs->setResampleQuality(m_resampleQuality);
     prefs->setResampleOnLoad(m_resampleOnLoad);
+    prefs->setShowSplash(m_showSplash);
     prefs->setTemporaryDirectoryRoot(m_tempDirRoot);
     prefs->setBackgroundMode(Preferences::BackgroundMode(m_backgroundMode));
+    prefs->setViewFontSize(m_viewFontSize);
 
     m_applyButton->setEnabled(false);
 
--- a/main/PreferencesDialog.h	Tue Nov 13 13:51:07 2007 +0000
+++ b/main/PreferencesDialog.h	Wed Feb 27 11:59:42 2008 +0000
@@ -44,6 +44,8 @@
     void resampleOnLoadChanged(int state);
     void tempDirRootChanged(QString root);
     void backgroundModeChanged(int mode);
+    void viewFontSizeChanged(int sz);
+    void showSplashChanged(int state);
 
     void tempDirButtonClicked();
 
@@ -65,6 +67,8 @@
     bool m_resampleOnLoad;
     QString m_tempDirRoot;
     int m_backgroundMode;
+    int m_viewFontSize;
+    bool m_showSplash;
 
     bool m_changesOnRestart;
 };
--- a/main/main.cpp	Tue Nov 13 13:51:07 2007 +0000
+++ b/main/main.cpp	Wed Feb 27 11:59:42 2008 +0000
@@ -32,6 +32,7 @@
 #include <QIcon>
 #include <QSessionManager>
 #include <QDir>
+#include <QSplashScreen>
 
 #include <iostream>
 #include <signal.h>
@@ -53,7 +54,11 @@
  Document, SVFileReader
 
  - Turning one model (e.g. audio) into another (e.g. more audio, or a
- curve extracted from it): Transform and subclasses
+ curve extracted from it): Transform, encapsulating the data that need
+ to be stored to be able to reproduce a given transformation;
+ TransformFactory, for discovering the available types of transform;
+ ModelTransformerFactory, ModelTransformer and subclasses, providing
+ the mechanisms for applying transforms to data models
 
  - Creating the plugins used by transforms: RealTimePluginFactory,
  FeatureExtractionPluginFactory.  See also the API documentation for
@@ -229,6 +234,18 @@
     QApplication::setOrganizationDomain("sonicvisualiser.org");
     QApplication::setApplicationName(QApplication::tr("Sonic Visualiser"));
 
+    QPixmap pixmap(":/icons/sv-splash.png");
+    QSplashScreen splash(pixmap);
+
+    QSettings settings;
+
+    settings.beginGroup("Preferences");
+    if (settings.value("show-splash", true).toBool()) {
+        splash.show();
+        application.processEvents();
+    }
+    settings.endGroup();
+
     QIcon icon;
     int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
     for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
@@ -266,8 +283,8 @@
     qRegisterMetaType<size_t>("size_t");
     qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
 
-    MainWindow gui(audioOutput, oscSupport);
-    application.setMainWindow(&gui);
+    MainWindow *gui = new MainWindow(audioOutput, oscSupport);
+    application.setMainWindow(gui);
 
     QDesktopWidget *desktop = QApplication::desktop();
     QRect available = desktop->availableGeometry();
@@ -277,21 +294,20 @@
     if (height < 450) height = available.height() * 2 / 3;
     if (width > height * 2) width = height * 2;
 
-    QSettings settings;
     settings.beginGroup("MainWindow");
     QSize size = settings.value("size", QSize(width, height)).toSize();
-    gui.resize(size);
+    gui->resize(size);
     if (settings.contains("position")) {
-        gui.move(settings.value("position").toPoint());
+        gui->move(settings.value("position").toPoint());
     }
     settings.endGroup();
     
-    gui.show();
+    gui->show();
 
     // The MainWindow class seems to have trouble dealing with this if
     // it tries to adapt to this preference before the constructor is
     // complete.  As a lazy hack, apply it explicitly from here
-    gui.preferenceChanged("Property Box Layout");
+    gui->preferenceChanged("Property Box Layout");
 
     bool haveSession = false;
     bool haveMainModel = false;
@@ -308,7 +324,7 @@
 
         if (path.endsWith("sv")) {
             if (!haveSession) {
-                status = gui.openSessionFile(path);
+                status = gui->openSessionFile(path);
                 if (status == MainWindow::FileOpenSucceeded) {
                     haveSession = true;
                     haveMainModel = true;
@@ -320,24 +336,24 @@
         }
         if (status != MainWindow::FileOpenSucceeded) {
             if (!haveMainModel) {
-                status = gui.open(path, MainWindow::ReplaceMainModel);
+                status = gui->open(path, MainWindow::ReplaceMainModel);
                 if (status == MainWindow::FileOpenSucceeded) {
                     haveMainModel = true;
                 }
             } else {
                 if (haveSession && !havePriorCommandLineModel) {
-                    status = gui.open(path, MainWindow::AskUser);
+                    status = gui->open(path, MainWindow::AskUser);
                     if (status == MainWindow::FileOpenSucceeded) {
                         havePriorCommandLineModel = true;
                     }
                 } else {
-                    status = gui.open(path, MainWindow::CreateAdditionalModel);
+                    status = gui->open(path, MainWindow::CreateAdditionalModel);
                 }
             }
         }
         if (status == MainWindow::FileOpenFailed) {
 	    QMessageBox::critical
-                (&gui, QMessageBox::tr("Failed to open file"),
+                (gui, QMessageBox::tr("Failed to open file"),
                  QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
         }
     }
@@ -351,6 +367,7 @@
     settings.endGroup();
 #endif
 
+    splash.finish(gui);
 
 /*
     TipDialog tipDialog;
@@ -359,10 +376,11 @@
     }
 */
     int rv = application.exec();
-//    std::cerr << "application.exec() returned " << rv << std::endl;
+    std::cerr << "application.exec() returned " << rv << std::endl;
 
     cleanupMutex.lock();
     TempDirectory::getInstance()->cleanup();
+
     application.releaseMainWindow();
 
 #ifdef HAVE_FFTW3F
@@ -375,5 +393,7 @@
     }
 #endif
 
+    delete gui;
+
     return rv;
 }
--- a/sonic-visualiser.qrc	Tue Nov 13 13:51:07 2007 +0000
+++ b/sonic-visualiser.qrc	Wed Feb 27 11:59:42 2008 +0000
@@ -28,6 +28,7 @@
     <file>icons/text.png</file>
     <file>icons/draw.png</file>
     <file>icons/draw-curve.png</file>
+    <file>icons/erase.png</file>
     <file>icons/measure.png</file>
     <file>icons/measure1cursor.xbm</file>
     <file>icons/measure1mask.xbm</file>
@@ -66,6 +67,10 @@
     <file>icons/help.png</file>
     <file>icons/emptypage.png</file>
     <file>icons/cross.png</file>
+    <file>icons/normalise.png</file>
+    <file>icons/normalise-columns.png</file>
+    <file>icons/invert-vertical.png</file>
+    <file>icons/show-peaks.png</file>
     <file>icons/sv-16x16.png</file>
     <file>icons/sv-22x22.png</file>
     <file>icons/sv-24x24.png</file>
@@ -73,6 +78,7 @@
     <file>icons/sv-48x48.png</file>
     <file>icons/sv-64x64.png</file>
     <file>icons/sv-128x128.png</file>
+    <file>icons/sv-splash.png</file>
     <file>samples/bass.wav</file>
     <file>samples/beep.wav</file>
     <file>samples/bounce.wav</file>
--- a/sv.pro	Tue Nov 13 13:51:07 2007 +0000
+++ b/sv.pro	Wed Feb 27 11:59:42 2008 +0000
@@ -1,7 +1,7 @@
 
 TEMPLATE = app
 
-SV_UNIT_PACKAGES = vamp vamp-hostsdk fftw3f samplerate jack portaudio mad id3tag oggz fishsound lrdf raptor sndfile liblo
+SV_UNIT_PACKAGES = vamp vamp-hostsdk rubberband fftw3 fftw3f samplerate jack portaudio mad id3tag oggz fishsound lrdf raptor sndfile liblo
 load(../sv.prf)
 
 CONFIG += sv qt thread warn_on stl rtti exceptions
@@ -36,6 +36,7 @@
 HEADERS += main/MainWindow.h \
            main/PreferencesDialog.h
 SOURCES += main/main.cpp \
+           main/OSCHandler.cpp \
            main/MainWindow.cpp \
            main/PreferencesDialog.cpp
 RESOURCES += sonic-visualiser.qrc