changeset 200:1871581e4da9

* Split MainWindow out into MainWindowBase (pane stack management, basic file I/O etc) and MainWindow (widget structure, menus and actions etc)
author Chris Cannam
date Mon, 22 Oct 2007 14:24:31 +0000
parents 6e1d4d500092
children de783e8ee5f0
files main/MainWindow.cpp main/MainWindow.h main/MainWindowBase.cpp main/MainWindowBase.h main/main.cpp sv.pro
diffstat 6 files changed, 2515 insertions(+), 2187 deletions(-) [+]
line wrap: on
line diff
--- a/main/MainWindow.cpp	Mon Oct 22 09:45:35 2007 +0000
+++ b/main/MainWindow.cpp	Mon Oct 22 14:24:31 2007 +0000
@@ -4,7 +4,7 @@
     Sonic Visualiser
     An audio file viewer and annotation editor.
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
+    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
@@ -70,9 +70,6 @@
 #include "base/ColourDatabase.h"
 #include "osc/OSCQueue.h"
 
-//!!!
-#include "data/model/AggregateWaveModel.h"
-
 // For version information
 #include "vamp/vamp.h"
 #include "vamp-sdk/PluginBase.h"
@@ -115,17 +112,8 @@
 
 
 MainWindow::MainWindow(bool withAudioOutput, bool withOSCSupport) :
-    m_document(0),
-    m_paneStack(0),
-    m_viewManager(0),
+    MainWindowBase(withAudioOutput, withOSCSupport),
     m_overview(0),
-    m_timeRulerLayer(0),
-    m_audioOutput(withAudioOutput),
-    m_playSource(0),
-    m_playTarget(0),
-    m_oscQueue(withOSCSupport ? new OSCQueue() : 0),
-    m_recentFiles("RecentFiles", 20),
-    m_recentTransforms("RecentTransforms", 20),
     m_mainMenusCreated(false),
     m_paneMenu(0),
     m_layerMenu(0),
@@ -141,10 +129,6 @@
     m_rightButtonPlaybackMenu(0),
     m_ffwdAction(0),
     m_rwdAction(0),
-    m_documentModified(false),
-    m_openingAudioFile(false),
-    m_abandoning(false),
-    m_labeller(0),
     m_preferencesDialog(0),
     m_layerTreeView(0),
     m_keyReference(new KeyReference())
@@ -170,29 +154,10 @@
     cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true);
     cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true);
 
-    connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()),
-	    this, SLOT(documentModified()));
-    connect(CommandHistory::getInstance(), SIGNAL(documentRestored()),
-	    this, SLOT(documentRestored()));
-
     QFrame *frame = new QFrame;
     setCentralWidget(frame);
 
     QGridLayout *layout = new QGridLayout;
-    
-    m_viewManager = new ViewManager();
-    connect(m_viewManager, SIGNAL(selectionChanged()),
-	    this, SLOT(updateMenuStates()));
-    connect(m_viewManager, SIGNAL(inProgressSelectionChanged()),
-	    this, SLOT(inProgressSelectionChanged()));
-
-    Preferences::BackgroundMode mode =
-        Preferences::getInstance()->getBackgroundMode();
-    m_initialDarkBackground = m_viewManager->getGlobalDarkBackground();
-    if (mode != Preferences::BackgroundFromTheme) {
-        m_viewManager->setGlobalDarkBackground
-            (mode == Preferences::DarkBackground);
-    }
 
     m_descriptionLabel = new QLabel;
 
@@ -201,22 +166,6 @@
     scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
     scroll->setFrameShape(QFrame::NoFrame);
 
-    m_paneStack = new PaneStack(scroll, m_viewManager);
-    connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)),
-	    this, SLOT(currentPaneChanged(Pane *)));
-    connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)),
-	    this, SLOT(currentLayerChanged(Pane *, Layer *)));
-    connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)),
-            this, SLOT(rightButtonMenuRequested(Pane *, QPoint)));
-    connect(m_paneStack, SIGNAL(propertyStacksResized()),
-            this, SLOT(propertyStacksResized()));
-    connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)),
-            this, SLOT(contextHelpChanged(const QString &)));
-    connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QStringList)),
-            this, SLOT(paneDropAccepted(Pane *, QStringList)));
-    connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QString)),
-            this, SLOT(paneDropAccepted(Pane *, QString)));
-
     scroll->setWidget(m_paneStack);
 
     m_overview = new Overview(frame);
@@ -242,14 +191,7 @@
     } else {
         m_panLayer->setBaseColour
             (ColourDatabase::getInstance()->getColourIndex(tr("Green")));
-    }        
-
-    m_playSource = new AudioCallbackPlaySource(m_viewManager);
-
-    connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)),
-	    this,           SLOT(sampleRateMismatch(size_t, size_t, bool)));
-    connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()),
-            this,           SLOT(audioOverloadPluginDisabled()));
+    }
 
     m_fader = new Fader(frame, false);
     connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
@@ -318,45 +260,6 @@
 
     frame->setLayout(layout);
 
-    connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)),
-	    this, SLOT(outputLevelsChanged(float, float)));
-
-    connect(m_viewManager, SIGNAL(playbackFrameChanged(unsigned long)),
-            this, SLOT(playbackFrameChanged(unsigned long)));
-
-    connect(m_viewManager, SIGNAL(globalCentreFrameChanged(unsigned long)),
-            this, SLOT(globalCentreFrameChanged(unsigned long)));
-
-    connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)),
-            this, SLOT(viewCentreFrameChanged(View *, unsigned long)));
-
-    connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)),
-            this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool)));
-
-    connect(Preferences::getInstance(),
-            SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
-            this,
-            SLOT(preferenceChanged(PropertyContainer::PropertyName)));
-
-//    preferenceChanged("Property Box Layout");
-
-    if (m_oscQueue && m_oscQueue->isOK()) {
-        connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC()));
-        QTimer *oscTimer = new QTimer(this);
-        connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC()));
-        oscTimer->start(1000);
-    }
-
-    Labeller::ValueType labellerType = Labeller::ValueFromTwoLevelCounter;
-    settings.beginGroup("MainWindow");
-    labellerType = (Labeller::ValueType)
-        settings.value("labellertype", (int)labellerType).toInt();
-    int cycle = settings.value("labellercycle", 4).toInt();
-    settings.endGroup();
-
-    m_labeller = new Labeller(labellerType);
-    m_labeller->setCounterCycleSize(cycle);
-
     setupMenus();
     setupToolbars();
     setupHelpMenu();
@@ -368,81 +271,12 @@
 
 MainWindow::~MainWindow()
 {
-//    std::cerr << "MainWindow::~MainWindow()" << std::endl;
-
-    if (!m_abandoning) {
-        closeSession();
-    }
-    delete m_playTarget;
-    delete m_playSource;
-    delete m_viewManager;
-    delete m_oscQueue;
     delete m_keyReference;
     delete m_preferencesDialog;
     delete m_layerTreeView;
     Profiles::getInstance()->dump();
 }
 
-QString
-MainWindow::getOpenFileName(FileFinder::FileType type)
-{
-    FileFinder *ff = FileFinder::getInstance();
-    switch (type) {
-    case FileFinder::SessionFile:
-        return ff->getOpenFileName(type, m_sessionFile);
-    case FileFinder::AudioFile:
-        return ff->getOpenFileName(type, m_audioFile);
-    case FileFinder::LayerFile:
-        return ff->getOpenFileName(type, m_sessionFile);
-    case FileFinder::LayerFileNoMidi:
-        return ff->getOpenFileName(type, m_sessionFile);
-    case FileFinder::SessionOrAudioFile:
-        return ff->getOpenFileName(type, m_sessionFile);
-    case FileFinder::ImageFile:
-        return ff->getOpenFileName(type, m_sessionFile);
-    case FileFinder::AnyFile:
-        if (getMainModel() != 0 &&
-            m_paneStack != 0 &&
-            m_paneStack->getCurrentPane() != 0) { // can import a layer
-            return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile);
-        } else {
-            return ff->getOpenFileName(FileFinder::SessionOrAudioFile,
-                                       m_sessionFile);
-        }
-    }
-    return "";
-}
-
-QString
-MainWindow::getSaveFileName(FileFinder::FileType type)
-{
-    FileFinder *ff = FileFinder::getInstance();
-    switch (type) {
-    case FileFinder::SessionFile:
-        return ff->getSaveFileName(type, m_sessionFile);
-    case FileFinder::AudioFile:
-        return ff->getSaveFileName(type, m_audioFile);
-    case FileFinder::LayerFile:
-        return ff->getSaveFileName(type, m_sessionFile);
-    case FileFinder::LayerFileNoMidi:
-        return ff->getSaveFileName(type, m_sessionFile);
-    case FileFinder::SessionOrAudioFile:
-        return ff->getSaveFileName(type, m_sessionFile);
-    case FileFinder::ImageFile:
-        return ff->getSaveFileName(type, m_sessionFile);
-    case FileFinder::AnyFile:
-        return ff->getSaveFileName(type, m_sessionFile);
-    }
-    return "";
-}
-
-void
-MainWindow::registerLastOpenedFilePath(FileFinder::FileType type, QString path)
-{
-    FileFinder *ff = FileFinder::getInstance();
-    ff->registerLastOpenedFilePath(type, path);
-}
-
 void
 MainWindow::setupMenus()
 {
@@ -777,7 +611,7 @@
             QActionGroup *cycleGroup = new QActionGroup(this);
 
             int cycles[] = { 2, 3, 4, 5, 6, 7, 8, 10, 12, 16 };
-            for (int i = 0; i < sizeof(cycles)/sizeof(cycles[0]); ++i) {
+            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()));
                 action->setCheckable(true);
@@ -1909,6 +1743,8 @@
 void
 MainWindow::updateMenuStates()
 {
+    MainWindowBase::updateMenuStates();
+
     Pane *currentPane = 0;
     Layer *currentLayer = 0;
 
@@ -1920,10 +1756,6 @@
     bool haveCurrentLayer =
         (haveCurrentPane &&
          (currentLayer != 0));
-    bool haveMainModel =
-	(getMainModel() != 0);
-    bool havePlayTarget =
-	(m_playTarget != 0);
     bool haveSelection = 
 	(m_viewManager &&
 	 !m_viewManager->getSelections().empty());
@@ -1936,40 +1768,11 @@
     bool haveCurrentTimeValueLayer = 
 	(haveCurrentLayer &&
 	 dynamic_cast<TimeValueLayer *>(currentLayer));
-    bool haveCurrentColour3DPlot =
-        (haveCurrentLayer &&
-         dynamic_cast<Colour3DPlotLayer *>(currentLayer));
-    bool haveClipboardContents =
-        (m_viewManager &&
-         !m_viewManager->getClipboard().empty());
-
-    emit canAddPane(haveMainModel);
-    emit canDeleteCurrentPane(haveCurrentPane);
-    emit canZoom(haveMainModel && haveCurrentPane);
-    emit canScroll(haveMainModel && haveCurrentPane);
-    emit canAddLayer(haveMainModel && haveCurrentPane);
-    emit canImportMoreAudio(haveMainModel);
-    emit canImportLayer(haveMainModel && haveCurrentPane);
-    emit canExportAudio(haveMainModel);
-    emit canExportLayer(haveMainModel &&
-                        (haveCurrentEditableLayer || haveCurrentColour3DPlot));
-    emit canExportImage(haveMainModel && haveCurrentPane);
-    emit canDeleteCurrentLayer(haveCurrentLayer);
-    emit canRenameLayer(haveCurrentLayer);
-    emit canEditLayer(haveCurrentEditableLayer);
-    emit canMeasureLayer(haveCurrentLayer);
-    emit canSelect(haveMainModel && haveCurrentPane);
-    emit canPlay(havePlayTarget);
-    emit canFfwd(true);
-    emit canRewind(true);
-    emit canPaste(haveCurrentEditableLayer && haveClipboardContents);
-    emit canInsertInstant(haveCurrentPane);
-    emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection);
-    emit canRenumberInstants(haveCurrentTimeInstantsLayer && haveSelection);
-    emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection);
-    emit canClearSelection(haveSelection);
-    emit canEditSelection(haveSelection && haveCurrentEditableLayer);
-    emit canSave(m_sessionFile != "" && m_documentModified);
+
+    emit canChangePlaybackSpeed(true);
+    int v = m_playSpeed->value();
+    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
+    emit canSlowDownPlayback(v > m_playSpeed->minimum());
 
     if (m_viewManager && 
         (m_viewManager->getToolMode() == ViewManager::MeasureMode)) {
@@ -1982,11 +1785,6 @@
         m_deleteSelectedAction->setStatusTip(tr("Delete items in current selection from the current layer"));
     }
 
-    emit canChangePlaybackSpeed(true);
-    int v = m_playSpeed->value();
-    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
-    emit canSlowDownPlayback(v > m_playSpeed->minimum());
-
     if (m_ffwdAction && m_rwdAction) {
         if (haveCurrentTimeInstantsLayer) {
             m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
@@ -2038,124 +1836,15 @@
 void
 MainWindow::documentModified()
 {
-//    std::cerr << "MainWindow::documentModified" << std::endl;
-
-    if (!m_documentModified) {
-	setWindowTitle(tr("%1 (modified)").arg(windowTitle()));
-    }
-
-    m_documentModified = true;
-    updateMenuStates();
+    //!!!
+    MainWindowBase::documentModified();
 }
 
 void
 MainWindow::documentRestored()
 {
-//    std::cerr << "MainWindow::documentRestored" << std::endl;
-
-    if (m_documentModified) {
-	QString wt(windowTitle());
-	wt.replace(tr(" (modified)"), "");
-	setWindowTitle(wt);
-    }
-
-    m_documentModified = false;
-    updateMenuStates();
-}
-
-void
-MainWindow::playLoopToggled()
-{
-    QAction *action = dynamic_cast<QAction *>(sender());
-    
-    if (action) {
-	m_viewManager->setPlayLoopMode(action->isChecked());
-    } else {
-	m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode());
-    }
-}
-
-void
-MainWindow::playSelectionToggled()
-{
-    QAction *action = dynamic_cast<QAction *>(sender());
-    
-    if (action) {
-	m_viewManager->setPlaySelectionMode(action->isChecked());
-    } else {
-	m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode());
-    }
-}
-
-void
-MainWindow::playSoloToggled()
-{
-    QAction *action = dynamic_cast<QAction *>(sender());
-    
-    if (action) {
-	m_viewManager->setPlaySoloMode(action->isChecked());
-    } else {
-	m_viewManager->setPlaySoloMode(!m_viewManager->getPlaySoloMode());
-    }
-
-    if (m_viewManager->getPlaySoloMode()) {
-        currentPaneChanged(m_paneStack->getCurrentPane());
-    } else {
-        m_viewManager->setPlaybackModel(0);
-        if (m_playSource) {
-            m_playSource->clearSoloModelSet();
-        }
-    }
-}
-
-void
-MainWindow::currentPaneChanged(Pane *p)
-{
-    updateMenuStates();
-    updateVisibleRangeDisplay(p);
-
-    if (!p) return;
-
-    if (!(m_viewManager &&
-          m_playSource &&
-          m_viewManager->getPlaySoloMode())) {
-        if (m_viewManager) m_viewManager->setPlaybackModel(0);
-        return;
-    }
-
-    Model *prevPlaybackModel = m_viewManager->getPlaybackModel();
-
-    View::ModelSet soloModels = p->getModels();
-    
-    for (View::ModelSet::iterator mi = soloModels.begin();
-         mi != soloModels.end(); ++mi) {
-        if (dynamic_cast<RangeSummarisableTimeValueModel *>(*mi)) {
-            m_viewManager->setPlaybackModel(*mi);
-        }
-    }
-    
-    RangeSummarisableTimeValueModel *a = 
-        dynamic_cast<RangeSummarisableTimeValueModel *>(prevPlaybackModel);
-    RangeSummarisableTimeValueModel *b = 
-        dynamic_cast<RangeSummarisableTimeValueModel *>(m_viewManager->
-                                                        getPlaybackModel());
-
-    m_playSource->setSoloModelSet(soloModels);
-
-    if (a && b && (a != b)) {
-        int frame = m_playSource->getCurrentPlayingFrame();
-        //!!! I don't really believe that these functions are the right way around
-        int rframe = a->alignFromReference(frame);
-        int bframe = b->alignToReference(rframe);
-        if (m_playSource->isPlaying()) m_playSource->play(bframe);
-    }
-}
-
-void
-MainWindow::currentLayerChanged(Pane *p, Layer *)
-{
-    updateMenuStates();
-    updateVisibleRangeDisplay(p);
+    //!!!
+    MainWindowBase::documentRestored();
 }
 
 void
@@ -2195,331 +1884,6 @@
 //}
 
 void
-MainWindow::selectAll()
-{
-    if (!getMainModel()) return;
-    m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(),
-					  getMainModel()->getEndFrame()));
-}
-
-void
-MainWindow::selectToStart()
-{
-    if (!getMainModel()) return;
-    m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(),
-					  m_viewManager->getGlobalCentreFrame()));
-}
-
-void
-MainWindow::selectToEnd()
-{
-    if (!getMainModel()) return;
-    m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(),
-					  getMainModel()->getEndFrame()));
-}
-
-void
-MainWindow::selectVisible()
-{
-    Model *model = getMainModel();
-    if (!model) return;
-
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (!currentPane) return;
-
-    size_t startFrame, endFrame;
-
-    if (currentPane->getStartFrame() < 0) startFrame = 0;
-    else startFrame = currentPane->getStartFrame();
-
-    if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame();
-    else endFrame = currentPane->getEndFrame();
-
-    m_viewManager->setSelection(Selection(startFrame, endFrame));
-}
-
-void
-MainWindow::clearSelection()
-{
-    m_viewManager->clearSelections();
-}
-
-void
-MainWindow::cut()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (!currentPane) return;
-
-    Layer *layer = currentPane->getSelectedLayer();
-    if (!layer) return;
-
-    Clipboard &clipboard = m_viewManager->getClipboard();
-    clipboard.clear();
-
-    MultiSelection::SelectionList selections = m_viewManager->getSelections();
-
-    CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true);
-
-    for (MultiSelection::SelectionList::iterator i = selections.begin();
-         i != selections.end(); ++i) {
-        layer->copy(*i, clipboard);
-        layer->deleteSelection(*i);
-    }
-
-    CommandHistory::getInstance()->endCompoundOperation();
-}
-
-void
-MainWindow::copy()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (!currentPane) return;
-
-    Layer *layer = currentPane->getSelectedLayer();
-    if (!layer) return;
-
-    Clipboard &clipboard = m_viewManager->getClipboard();
-    clipboard.clear();
-
-    MultiSelection::SelectionList selections = m_viewManager->getSelections();
-
-    for (MultiSelection::SelectionList::iterator i = selections.begin();
-         i != selections.end(); ++i) {
-        layer->copy(*i, clipboard);
-    }
-}
-
-void
-MainWindow::paste()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (!currentPane) return;
-
-    //!!! if we have no current layer, we should create one of the most
-    // appropriate type
-
-    Layer *layer = currentPane->getSelectedLayer();
-    if (!layer) return;
-
-    Clipboard &clipboard = m_viewManager->getClipboard();
-    Clipboard::PointList contents = clipboard.getPoints();
-/*
-    long minFrame = 0;
-    bool have = false;
-    for (int i = 0; i < contents.size(); ++i) {
-        if (!contents[i].haveFrame()) continue;
-        if (!have || contents[i].getFrame() < minFrame) {
-            minFrame = contents[i].getFrame();
-            have = true;
-        }
-    }
-
-    long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame;
-
-    layer->paste(clipboard, frameOffset);
-*/
-    layer->paste(clipboard, 0, true);
-}
-
-void
-MainWindow::deleteSelected()
-{
-    if (m_paneStack->getCurrentPane() &&
-	m_paneStack->getCurrentPane()->getSelectedLayer()) {
-        
-        Layer *layer = m_paneStack->getCurrentPane()->getSelectedLayer();
-
-        if (m_viewManager && 
-            (m_viewManager->getToolMode() == ViewManager::MeasureMode)) {
-
-            layer->deleteCurrentMeasureRect();
-
-        } else {
-
-            MultiSelection::SelectionList selections =
-                m_viewManager->getSelections();
-            
-            for (MultiSelection::SelectionList::iterator i = selections.begin();
-                 i != selections.end(); ++i) {
-                layer->deleteSelection(*i);
-            }
-	}
-    }
-}
-
-void
-MainWindow::insertInstant()
-{
-    int frame = m_viewManager->getPlaybackFrame();
-    insertInstantAt(frame);
-}
-
-void
-MainWindow::insertInstantsAtBoundaries()
-{
-    MultiSelection::SelectionList selections = m_viewManager->getSelections();
-    for (MultiSelection::SelectionList::iterator i = selections.begin();
-         i != selections.end(); ++i) {
-        size_t start = i->getStartFrame();
-        size_t end = i->getEndFrame();
-        if (start != end) {
-            insertInstantAt(i->getStartFrame());
-            insertInstantAt(i->getEndFrame());
-        }
-    }
-}
-
-void
-MainWindow::insertInstantAt(size_t frame)
-{
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (!pane) {
-        return;
-    }
-
-    Layer *layer = dynamic_cast<TimeInstantLayer *>
-        (pane->getSelectedLayer());
-
-    if (!layer) {
-        for (int i = pane->getLayerCount(); i > 0; --i) {
-            layer = dynamic_cast<TimeInstantLayer *>(pane->getLayer(i - 1));
-            if (layer) break;
-        }
-
-        if (!layer) {
-            CommandHistory::getInstance()->startCompoundOperation
-                (tr("Add Point"), true);
-            layer = m_document->createEmptyLayer(LayerFactory::TimeInstants);
-            if (layer) {
-                m_document->addLayerToView(pane, layer);
-                m_paneStack->setCurrentLayer(pane, layer);
-            }
-            CommandHistory::getInstance()->endCompoundOperation();
-        }
-    }
-
-    if (layer) {
-    
-        Model *model = layer->getModel();
-        SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
-            (model);
-
-        if (sodm) {
-            SparseOneDimensionalModel::Point point(frame, "");
-
-            SparseOneDimensionalModel::Point prevPoint(0);
-            bool havePrevPoint = false;
-
-            SparseOneDimensionalModel::EditCommand *command =
-                new SparseOneDimensionalModel::EditCommand(sodm, tr("Add Point"));
-
-            if (m_labeller->actingOnPrevPoint()) {
-
-                SparseOneDimensionalModel::PointList prevPoints =
-                    sodm->getPreviousPoints(frame);
-
-                if (!prevPoints.empty()) {
-                    prevPoint = *prevPoints.begin();
-                    havePrevPoint = true;
-                }
-            }
-
-            if (m_labeller) {
-
-                m_labeller->setSampleRate(sodm->getSampleRate());
-
-                if (havePrevPoint) {
-                    command->deletePoint(prevPoint);
-                }
-
-                m_labeller->label<SparseOneDimensionalModel::Point>
-                    (point, havePrevPoint ? &prevPoint : 0);
-
-                if (havePrevPoint) {
-                    command->addPoint(prevPoint);
-                }
-            }
-            
-            command->addPoint(point);
-
-            command->setName(tr("Add Point at %1 s")
-                             .arg(RealTime::frame2RealTime
-                                  (frame,
-                                   sodm->getSampleRate())
-                                  .toText(false).c_str()));
-
-            command->finish();
-        }
-    }
-}
-
-void
-MainWindow::setInstantsNumbering()
-{
-    QAction *a = dynamic_cast<QAction *>(sender());
-    if (!a) return;
-
-    int type = m_numberingActions[a];
-    
-    if (m_labeller) m_labeller->setType(Labeller::ValueType(type));
-
-    QSettings settings;
-    settings.beginGroup("MainWindow");
-    settings.setValue("labellertype", type);
-    settings.endGroup();
-}
-
-void
-MainWindow::setInstantsCounterCycle()
-{
-    QAction *a = dynamic_cast<QAction *>(sender());
-    if (!a) return;
-    
-    int cycle = a->text().toInt();
-    if (cycle == 0) return;
-
-    if (m_labeller) m_labeller->setCounterCycleSize(cycle);
-    
-    QSettings settings;
-    settings.beginGroup("MainWindow");
-    settings.setValue("labellercycle", cycle);
-    settings.endGroup();
-}
-
-void
-MainWindow::resetInstantsCounters()
-{
-    LabelCounterInputDialog dialog(m_labeller, this);
-    dialog.exec();
-}
-
-void
-MainWindow::renumberInstants()
-{
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (!pane) return;
-
-    Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
-    if (!layer) return;
-
-    MultiSelection ms(m_viewManager->getSelection());
-    
-    Model *model = layer->getModel();
-    SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
-        (model);
-    if (!sodm) return;
-
-    if (!m_labeller) return;
-
-    Labeller labeller(*m_labeller);
-    labeller.setSampleRate(sodm->getSampleRate());
-
-    // This uses a command
-
-    labeller.labelAll<SparseOneDimensionalModel::Point>(*sodm, &ms);
-}
-
-void
 MainWindow::importAudio()
 {
     QString path = getOpenFileName(FileFinder::AudioFile);
@@ -2857,498 +2221,6 @@
     delete image;
 }
 
-MainWindow::FileOpenStatus
-MainWindow::open(QString fileOrUrl, AudioFileOpenMode mode)
-{
-    return open(FileSource(fileOrUrl), mode);
-}
-
-MainWindow::FileOpenStatus
-MainWindow::open(FileSource source, AudioFileOpenMode mode)
-{
-    FileOpenStatus status;
-
-    if (!source.isAvailable()) return FileOpenFailed;
-    source.waitForData();
-
-    bool canImportLayer = (getMainModel() != 0 &&
-                           m_paneStack != 0 &&
-                           m_paneStack->getCurrentPane() != 0);
-
-    if ((status = openAudio(source, mode)) != FileOpenFailed) {
-        return status;
-    } else if ((status = openSession(source)) != FileOpenFailed) {
-	return status;
-    } else if ((status = openPlaylist(source, mode)) != FileOpenFailed) {
-        return status;
-    } else if (!canImportLayer) {
-        return FileOpenWrongMode;
-    } else if ((status = openImage(source)) != FileOpenFailed) {
-        return status;
-    } else if ((status = openLayer(source)) != FileOpenFailed) {
-        return status;
-    } else {
-	return FileOpenFailed;
-    }
-}
-
-MainWindow::FileOpenStatus
-MainWindow::openAudio(FileSource source, AudioFileOpenMode mode)
-{
-    std::cerr << "MainWindow::openAudio(" << source.getLocation().toStdString() << ")" << std::endl;
-
-    if (!source.isAvailable()) return FileOpenFailed;
-    source.waitForData();
-
-    m_openingAudioFile = true;
-
-    size_t rate = 0;
-
-    if (Preferences::getInstance()->getResampleOnLoad()) {
-        rate = m_playSource->getSourceSampleRate();
-    }
-
-    WaveFileModel *newModel = new WaveFileModel(source, rate);
-
-    if (!newModel->isOK()) {
-	delete newModel;
-        m_openingAudioFile = false;
-	return FileOpenFailed;
-    }
-
-    std::cerr << "mode = " << mode << std::endl;
-
-    if (mode == AskUser) {
-        if (getMainModel()) {
-
-            static bool prevSetAsMain = true;
-            bool setAsMain = true;
-            
-            QStringList items;
-            items << tr("Replace the existing main waveform")
-                  << tr("Load this file into a new waveform pane");
-
-            bool ok = false;
-            QString item = ListInputDialog::getItem
-                (this, tr("Select target for import"),
-                 tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"),
-                 items, prevSetAsMain ? 0 : 1, &ok);
-            
-            if (!ok || item.isEmpty()) {
-                delete newModel;
-                m_openingAudioFile = false;
-                return FileOpenCancelled;
-            }
-            
-            setAsMain = (item == items[0]);
-            prevSetAsMain = setAsMain;
-
-            if (setAsMain) mode = ReplaceMainModel;
-            else mode = CreateAdditionalModel;
-
-        } else {
-            mode = ReplaceMainModel;
-        }
-    }
-
-    if (mode == ReplaceCurrentPane) {
-
-        Pane *pane = m_paneStack->getCurrentPane();
-        if (pane) {
-            if (getMainModel()) {
-                View::ModelSet models(pane->getModels());
-                if (models.find(getMainModel()) != models.end()) {
-                    mode = ReplaceMainModel;
-                }
-            } else {
-                mode = ReplaceMainModel;
-            }
-        } else {
-            mode = CreateAdditionalModel;
-        }
-    }
-
-    if (mode == CreateAdditionalModel && !getMainModel()) {
-        mode = ReplaceMainModel;
-    }
-
-    if (mode == ReplaceMainModel) {
-
-        Model *prevMain = getMainModel();
-        if (prevMain) {
-            m_playSource->removeModel(prevMain);
-            PlayParameterRepository::getInstance()->removeModel(prevMain);
-        }
-        PlayParameterRepository::getInstance()->addModel(newModel);
-
-	m_document->setMainModel(newModel);
-	setupMenus();
-
-	if (m_sessionFile == "") {
-	    setWindowTitle(tr("Sonic Visualiser: %1")
-                           .arg(source.getLocation()));
-	    CommandHistory::getInstance()->clear();
-	    CommandHistory::getInstance()->documentSaved();
-	    m_documentModified = false;
-	} else {
-	    setWindowTitle(tr("Sonic Visualiser: %1 [%2]")
-			   .arg(QFileInfo(m_sessionFile).fileName())
-			   .arg(source.getLocation()));
-	    if (m_documentModified) {
-		m_documentModified = false;
-		documentModified(); // so as to restore "(modified)" window title
-	    }
-	}
-
-        if (!source.isRemote()) m_audioFile = source.getLocalFilename();
-
-    } else if (mode == CreateAdditionalModel) {
-
-	CommandHistory::getInstance()->startCompoundOperation
-	    (tr("Import \"%1\"").arg(source.getLocation()), true);
-
-	m_document->addImportedModel(newModel);
-
-	AddPaneCommand *command = new AddPaneCommand(this);
-	CommandHistory::getInstance()->addCommand(command);
-
-	Pane *pane = command->getPane();
-
-	if (!m_timeRulerLayer) {
-	    m_timeRulerLayer = m_document->createMainModelLayer
-		(LayerFactory::TimeRuler);
-	}
-
-	m_document->addLayerToView(pane, m_timeRulerLayer);
-
-	Layer *newLayer = m_document->createImportedLayer(newModel);
-
-	if (newLayer) {
-	    m_document->addLayerToView(pane, newLayer);
-	}
-	
-	CommandHistory::getInstance()->endCompoundOperation();
-
-    } else if (mode == ReplaceCurrentPane) {
-
-        // We know there is a current pane, otherwise we would have
-        // reset the mode to CreateAdditionalModel above; and we know
-        // the current pane does not contain the main model, otherwise
-        // we would have reset it to ReplaceMainModel.  But we don't
-        // know whether the pane contains a waveform model at all.
-        
-        Pane *pane = m_paneStack->getCurrentPane();
-        Layer *replace = 0;
-
-        for (int i = 0; i < pane->getLayerCount(); ++i) {
-            Layer *layer = pane->getLayer(i);
-            if (dynamic_cast<WaveformLayer *>(layer)) {
-                replace = layer;
-                break;
-            }
-        }
-
-	CommandHistory::getInstance()->startCompoundOperation
-	    (tr("Import \"%1\"").arg(source.getLocation()), true);
-
-	m_document->addImportedModel(newModel);
-
-        if (replace) {
-            m_document->removeLayerFromView(pane, replace);
-        }
-
-	Layer *newLayer = m_document->createImportedLayer(newModel);
-
-	if (newLayer) {
-	    m_document->addLayerToView(pane, newLayer);
-	}
-	
-	CommandHistory::getInstance()->endCompoundOperation();
-    }
-
-    updateMenuStates();
-    m_recentFiles.addFile(source.getLocation());
-    if (!source.isRemote()) {
-        // for file dialog
-        registerLastOpenedFilePath(FileFinder::AudioFile,
-                                   source.getLocalFilename());
-    }
-    m_openingAudioFile = false;
-
-    currentPaneChanged(m_paneStack->getCurrentPane());
-
-    return FileOpenSucceeded;
-}
-
-MainWindow::FileOpenStatus
-MainWindow::openPlaylist(FileSource source, AudioFileOpenMode mode)
-{
-    std::set<QString> extensions;
-    PlaylistFileReader::getSupportedExtensions(extensions);
-    QString extension = source.getExtension();
-    if (extensions.find(extension) == extensions.end()) return FileOpenFailed;
-
-    if (!source.isAvailable()) return FileOpenFailed;
-    source.waitForData();
-
-    PlaylistFileReader reader(source.getLocalFilename());
-    if (!reader.isOK()) return FileOpenFailed;
-
-    PlaylistFileReader::Playlist playlist = reader.load();
-
-    bool someSuccess = false;
-
-    for (PlaylistFileReader::Playlist::const_iterator i = playlist.begin();
-         i != playlist.end(); ++i) {
-
-        FileOpenStatus status = openAudio(*i, mode);
-
-        if (status == FileOpenCancelled) {
-            return FileOpenCancelled;
-        }
-
-        if (status == FileOpenSucceeded) {
-            someSuccess = true;
-            mode = CreateAdditionalModel;
-        }
-    }
-
-    if (someSuccess) return FileOpenSucceeded;
-    else return FileOpenFailed;
-}
-
-MainWindow::FileOpenStatus
-MainWindow::openLayer(FileSource source)
-{
-    Pane *pane = m_paneStack->getCurrentPane();
-    
-    if (!pane) {
-	// shouldn't happen, as the menu action should have been disabled
-	std::cerr << "WARNING: MainWindow::openLayer: no current pane" << std::endl;
-	return FileOpenWrongMode;
-    }
-
-    if (!getMainModel()) {
-	// shouldn't happen, as the menu action should have been disabled
-	std::cerr << "WARNING: MainWindow::openLayer: No main model -- hence no default sample rate available" << std::endl;
-	return FileOpenWrongMode;
-    }
-
-    if (!source.isAvailable()) return FileOpenFailed;
-    source.waitForData();
-
-    QString path = source.getLocalFilename();
-
-    if (source.getExtension() == "svl" || source.getExtension() == "xml") {
-
-        PaneCallback callback(this);
-        QFile file(path);
-        
-        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
-            std::cerr << "ERROR: MainWindow::openLayer("
-                      << source.getLocation().toStdString()
-                      << "): Failed to open file for reading" << std::endl;
-            return FileOpenFailed;
-        }
-        
-        SVFileReader reader(m_document, callback, source.getLocation());
-        reader.setCurrentPane(pane);
-        
-        QXmlInputSource inputSource(&file);
-        reader.parse(inputSource);
-        
-        if (!reader.isOK()) {
-            std::cerr << "ERROR: MainWindow::openLayer("
-                      << source.getLocation().toStdString()
-                      << "): Failed to read XML file: "
-                      << reader.getErrorString().toStdString() << std::endl;
-            return FileOpenFailed;
-        }
-
-        m_recentFiles.addFile(source.getLocation());
-
-        if (!source.isRemote()) {
-            registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog
-        }
-
-    } else {
-        
-        try {
-
-            Model *model = DataFileReaderFactory::load
-                (path, getMainModel()->getSampleRate());
-        
-            if (model) {
-
-                std::cerr << "MainWindow::openLayer: Have model" << std::endl;
-
-                Layer *newLayer = m_document->createImportedLayer(model);
-
-                if (newLayer) {
-
-                    m_document->addLayerToView(pane, newLayer);
-                    m_recentFiles.addFile(source.getLocation());
-                    
-                    if (!source.isRemote()) {
-                        registerLastOpenedFilePath
-                            (FileFinder::LayerFile,
-                             path); // for file dialog
-                    }
-                    
-                    return FileOpenSucceeded;
-                }
-            }
-        } catch (DataFileReaderFactory::Exception e) {
-            if (e == DataFileReaderFactory::ImportCancelled) {
-                return FileOpenCancelled;
-            }
-        }
-    }
-    
-    source.setLeaveLocalFile(true);
-    return FileOpenFailed;
-}
-
-MainWindow::FileOpenStatus
-MainWindow::openImage(FileSource source)
-{
-    Pane *pane = m_paneStack->getCurrentPane();
-    
-    if (!pane) {
-	// shouldn't happen, as the menu action should have been disabled
-	std::cerr << "WARNING: MainWindow::openImage: no current pane" << std::endl;
-	return FileOpenWrongMode;
-    }
-
-    if (!m_document->getMainModel()) {
-        return FileOpenWrongMode;
-    }
-
-    bool newLayer = false;
-
-    ImageLayer *il = dynamic_cast<ImageLayer *>(pane->getSelectedLayer());
-    if (!il) {
-        for (int i = pane->getLayerCount()-1; i >= 0; --i) {
-            il = dynamic_cast<ImageLayer *>(pane->getLayer(i));
-            if (il) break;
-        }
-    }
-    if (!il) {
-        il = dynamic_cast<ImageLayer *>
-            (m_document->createEmptyLayer(LayerFactory::Image));
-        if (!il) return FileOpenFailed;
-        newLayer = true;
-    }
-
-    // We don't put the image file in Recent Files
-
-    std::cerr << "openImage: trying location \"" << source.getLocation().toStdString() << "\" in image layer" << std::endl;
-
-    if (!il->addImage(m_viewManager->getGlobalCentreFrame(), source.getLocation())) {
-        if (newLayer) {
-            m_document->setModel(il, 0); // releasing its model
-            delete il;
-        }
-        return FileOpenFailed;
-    } else {
-        if (newLayer) {
-            m_document->addLayerToView(pane, il);
-        }
-        m_paneStack->setCurrentLayer(pane, il);
-    }
-
-    return FileOpenSucceeded;
-}
-
-MainWindow::FileOpenStatus
-MainWindow::openSession(FileSource source)
-{
-    if (!source.isAvailable()) return FileOpenFailed;
-    if (source.getExtension() != "sv") return FileOpenFailed;
-    source.waitForData();
-
-    BZipFileDevice bzFile(source.getLocalFilename());
-    if (!bzFile.open(QIODevice::ReadOnly)) return FileOpenFailed;
-
-    if (!checkSaveModified()) return FileOpenCancelled;
-
-    QString error;
-    closeSession();
-    createDocument();
-
-    PaneCallback callback(this);
-    m_viewManager->clearSelections();
-
-    SVFileReader reader(m_document, callback, source.getLocation());
-    QXmlInputSource inputSource(&bzFile);
-    reader.parse(inputSource);
-    
-    if (!reader.isOK()) {
-        error = tr("SV XML file read error:\n%1").arg(reader.getErrorString());
-    }
-    
-    bzFile.close();
-
-    bool ok = (error == "");
-
-    if (ok) {
-
-	setWindowTitle(tr("Sonic Visualiser: %1")
-		       .arg(source.getLocation()));
-
-	if (!source.isRemote()) m_sessionFile = source.getLocalFilename();
-
-	setupMenus();
-	CommandHistory::getInstance()->clear();
-	CommandHistory::getInstance()->documentSaved();
-	m_documentModified = false;
-	updateMenuStates();
-
-        m_recentFiles.addFile(source.getLocation());
-
-        if (!source.isRemote()) {
-            // for file dialog
-            registerLastOpenedFilePath(FileFinder::SessionFile,
-                                        source.getLocalFilename());
-        }
-
-    } else {
-	setWindowTitle(tr("Sonic Visualiser"));
-    }
-
-    return ok ? FileOpenSucceeded : FileOpenFailed;
-}
-
-void
-MainWindow::createPlayTarget()
-{
-    if (m_playTarget) return;
-
-    m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource);
-    if (!m_playTarget) {
-	QMessageBox::warning
-	    (this, tr("Couldn't open audio device"),
-	     tr("<b>No audio available</b><p>Could not open an audio device for playback.<p>Audio playback will not be available during this session."),
-	     QMessageBox::Ok);
-    }
-    connect(m_fader, SIGNAL(valueChanged(float)),
-	    m_playTarget, SLOT(setOutputGain(float)));
-}
-
-WaveFileModel *
-MainWindow::getMainModel()
-{
-    if (!m_document) return 0;
-    return m_document->getMainModel();
-}
-
-const WaveFileModel *
-MainWindow::getMainModel() const
-{
-    if (!m_document) return 0;
-    return m_document->getMainModel();
-}
-
 void
 MainWindow::newSession()
 {
@@ -3381,33 +2253,6 @@
 }
 
 void
-MainWindow::createDocument()
-{
-    m_document = new Document;
-
-    connect(m_document, SIGNAL(layerAdded(Layer *)),
-	    this, SLOT(layerAdded(Layer *)));
-    connect(m_document, SIGNAL(layerRemoved(Layer *)),
-	    this, SLOT(layerRemoved(Layer *)));
-    connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)),
-	    this, SLOT(layerAboutToBeDeleted(Layer *)));
-    connect(m_document, SIGNAL(layerInAView(Layer *, bool)),
-	    this, SLOT(layerInAView(Layer *, bool)));
-
-    connect(m_document, SIGNAL(modelAdded(Model *)),
-	    this, SLOT(modelAdded(Model *)));
-    connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)),
-	    this, SLOT(mainModelChanged(WaveFileModel *)));
-    connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)),
-	    this, SLOT(modelAboutToBeDeleted(Model *)));
-
-    connect(m_document, SIGNAL(modelGenerationFailed(QString)),
-            this, SLOT(modelGenerationFailed(QString)));
-    connect(m_document, SIGNAL(modelRegenerationFailed(QString, QString)),
-            this, SLOT(modelRegenerationFailed(QString, QString)));
-}
-
-void
 MainWindow::closeSession()
 {
     if (!checkSaveModified()) return;
@@ -3465,7 +2310,7 @@
 
     if (path.isEmpty()) return;
 
-    if (openSession(path) == FileOpenFailed) {
+    if (openSessionFile(path) == FileOpenFailed) {
 	QMessageBox::critical(this, tr("Failed to open file"),
 			      tr("<b>File open failed</b><p>Session file \"%1\" could not be opened").arg(path));
     }
@@ -3550,6 +2395,24 @@
 }
 
 void
+MainWindow::paneAdded(Pane *pane)
+{
+    if (m_overview) m_overview->registerView(pane);
+}    
+
+void
+MainWindow::paneHidden(Pane *pane)
+{
+    if (m_overview) m_overview->unregisterView(pane); 
+}    
+
+void
+MainWindow::paneAboutToBeDeleted(Pane *pane)
+{
+    if (m_overview) m_overview->unregisterView(pane); 
+}    
+
+void
 MainWindow::paneDropAccepted(Pane *pane, QStringList uriList)
 {
     if (pane) m_paneStack->setCurrentPane(pane);
@@ -3624,6 +2487,8 @@
         delete m_layerTreeView;
     }
 
+    closeSession();
+
     e->accept();
     return;
 }
@@ -3751,239 +2616,12 @@
     }
 }
 
-bool
-MainWindow::saveSessionFile(QString path)
-{
-    BZipFileDevice bzFile(path);
-    if (!bzFile.open(QIODevice::WriteOnly)) {
-        std::cerr << "Failed to open session file \"" << path.toStdString()
-                  << "\" for writing: "
-                  << bzFile.errorString().toStdString() << std::endl;
-        return false;
-    }
-
-    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
-
-    QTextStream out(&bzFile);
-    toXml(out);
-    out.flush();
-
-    QApplication::restoreOverrideCursor();
-
-    if (!bzFile.isOK()) {
-	QMessageBox::critical(this, tr("Failed to write file"),
-			      tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
-			      .arg(path).arg(bzFile.errorString()));
-        bzFile.close();
-	return false;
-    }
-
-    bzFile.close();
-    return true;
-}
-
-void
-MainWindow::toXml(QTextStream &out)
-{
-    QString indent("  ");
-
-    out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
-    out << "<!DOCTYPE sonic-visualiser>\n";
-    out << "<sv>\n";
-
-    m_document->toXml(out, "", "");
-
-    out << "<display>\n";
-
-    out << QString("  <window width=\"%1\" height=\"%2\"/>\n")
-	.arg(width()).arg(height());
-
-    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
-
-	Pane *pane = m_paneStack->getPane(i);
-
-	if (pane) {
-            pane->toXml(out, indent);
-	}
-    }
-
-    out << "</display>\n";
-
-    m_viewManager->getSelection().toXml(out);
-
-    out << "</sv>\n";
-}
-
-Pane *
-MainWindow::addPaneToStack()
-{
-    AddPaneCommand *command = new AddPaneCommand(this);
-    CommandHistory::getInstance()->addCommand(command);
-    return command->getPane();
-}
-
-void
-MainWindow::zoomIn()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->zoom(true);
-}
-
-void
-MainWindow::zoomOut()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->zoom(false);
-}
-
-void
-MainWindow::zoomToFit()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (!currentPane) return;
-
-    Model *model = getMainModel();
-    if (!model) return;
-    
-    size_t start = model->getStartFrame();
-    size_t end = model->getEndFrame();
-    size_t pixels = currentPane->width();
-
-    size_t sw = currentPane->getVerticalScaleWidth();
-    if (pixels > sw * 2) pixels -= sw * 2;
-    else pixels = 1;
-    if (pixels > 4) pixels -= 4;
-
-    size_t zoomLevel = (end - start) / pixels;
-
-    currentPane->setZoomLevel(zoomLevel);
-    currentPane->setCentreFrame((start + end) / 2);
-}
-
-void
-MainWindow::zoomDefault()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->setZoomLevel(1024);
-}
-
-void
-MainWindow::scrollLeft()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->scroll(false, false);
-}
-
-void
-MainWindow::jumpLeft()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->scroll(false, true);
-}
-
-void
-MainWindow::scrollRight()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->scroll(true, false);
-}
-
-void
-MainWindow::jumpRight()
-{
-    Pane *currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) currentPane->scroll(true, true);
-}
-
-void
-MainWindow::showNoOverlays()
-{
-    m_viewManager->setOverlayMode(ViewManager::NoOverlays);
-}
-
-void
-MainWindow::showMinimalOverlays()
-{
-    m_viewManager->setOverlayMode(ViewManager::MinimalOverlays);
-}
-
-void
-MainWindow::showStandardOverlays()
-{
-    m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
-}
-
-void
-MainWindow::showAllOverlays()
-{
-    m_viewManager->setOverlayMode(ViewManager::AllOverlays);
-}
-
-void
-MainWindow::toggleZoomWheels()
-{
-    if (m_viewManager->getZoomWheelsEnabled()) {
-        m_viewManager->setZoomWheelsEnabled(false);
-    } else {
-        m_viewManager->setZoomWheelsEnabled(true);
-    }
-}
-
-void
-MainWindow::togglePropertyBoxes()
-{
-    if (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks) {
-        if (Preferences::getInstance()->getPropertyBoxLayout() ==
-            Preferences::VerticallyStacked) {
-            m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
-        } else {
-            m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
-        }
-    } else {
-        m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks);
-    }
-}
-
-void
-MainWindow::toggleStatusBar()
-{
-    QSettings settings;
-    settings.beginGroup("MainWindow");
-    bool sb = settings.value("showstatusbar", true).toBool();
-
-    if (sb) {
-        statusBar()->hide();
-    } else {
-        statusBar()->show();
-    }
-
-    settings.setValue("showstatusbar", !sb);
-
-    settings.endGroup();
-}
-
 void
 MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
 {
-    if (name == "Property Box Layout") {
-        if (m_paneStack->getLayoutStyle() != PaneStack::NoPropertyStacks) {
-            if (Preferences::getInstance()->getPropertyBoxLayout() ==
-                Preferences::VerticallyStacked) {
-                m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
-            } else {
-                m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
-            }
-        }
-    } else if (name == "Background Mode" && m_viewManager) {
-        Preferences::BackgroundMode mode =
-            Preferences::getInstance()->getBackgroundMode();
-        if (mode == Preferences::BackgroundFromTheme) {
-            m_viewManager->setGlobalDarkBackground(m_initialDarkBackground);
-        } else if (mode == Preferences::DarkBackground) {
-            m_viewManager->setGlobalDarkBackground(true);
-        } else {
-            m_viewManager->setGlobalDarkBackground(false);
-        }
+    MainWindowBase::preferenceChanged(name);
+
+    if (name == "Background Mode" && m_viewManager) {
         if (m_viewManager->getGlobalDarkBackground()) {
             m_panLayer->setBaseColour
                 (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green")));
@@ -3991,171 +2629,7 @@
             m_panLayer->setBaseColour
                 (ColourDatabase::getInstance()->getColourIndex(tr("Green")));
         }      
-    }            
-}
-
-void
-MainWindow::play()
-{
-    if (m_playSource->isPlaying()) {
-        stop();
-    } else {
-        playbackFrameChanged(m_viewManager->getPlaybackFrame());
-	m_playSource->play(m_viewManager->getPlaybackFrame());
-    }
-}
-
-void
-MainWindow::ffwd()
-{
-    if (!getMainModel()) return;
-
-    int frame = m_viewManager->getPlaybackFrame();
-    ++frame;
-
-    Layer *layer = getSnapLayer();
-    size_t sr = getMainModel()->getSampleRate();
-
-    if (!layer) {
-
-        frame = RealTime::realTime2Frame
-            (RealTime::frame2RealTime(frame, sr) + RealTime(2, 0), sr);
-        if (frame > int(getMainModel()->getEndFrame())) {
-            frame = getMainModel()->getEndFrame();
-        }
-
-    } else {
-
-        size_t resolution = 0;
-        if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(),
-                                       frame, resolution, Layer::SnapRight)) {
-            frame = getMainModel()->getEndFrame();
-        }
-    }
-        
-    if (frame < 0) frame = 0;
-
-    if (m_viewManager->getPlaySelectionMode()) {
-        frame = m_viewManager->constrainFrameToSelection(size_t(frame));
-    }
-    
-    m_viewManager->setPlaybackFrame(frame);
-}
-
-void
-MainWindow::ffwdEnd()
-{
-    if (!getMainModel()) return;
-
-    size_t frame = getMainModel()->getEndFrame();
-
-    if (m_viewManager->getPlaySelectionMode()) {
-        frame = m_viewManager->constrainFrameToSelection(frame);
-    }
-
-    m_viewManager->setPlaybackFrame(frame);
-}
-
-void
-MainWindow::rewind()
-{
-    if (!getMainModel()) return;
-
-    int frame = m_viewManager->getPlaybackFrame();
-    if (frame > 0) --frame;
-
-    Layer *layer = getSnapLayer();
-    size_t sr = getMainModel()->getSampleRate();
-    
-    // when rewinding during playback, we want to allow a period
-    // following a rewind target point at which the rewind will go to
-    // the prior point instead of the immediately neighbouring one
-    if (m_playSource && m_playSource->isPlaying()) {
-        RealTime ct = RealTime::frame2RealTime(frame, sr);
-        ct = ct - RealTime::fromSeconds(0.25);
-        if (ct < RealTime::zeroTime) ct = RealTime::zeroTime;
-//        std::cerr << "rewind: frame " << frame << " -> ";
-        frame = RealTime::realTime2Frame(ct, sr);
-//        std::cerr << frame << std::endl;
-    }
-
-    if (!layer) {
-        
-        frame = RealTime::realTime2Frame
-            (RealTime::frame2RealTime(frame, sr) - RealTime(2, 0), sr);
-        if (frame < int(getMainModel()->getStartFrame())) {
-            frame = getMainModel()->getStartFrame();
-        }
-
-    } else {
-
-        size_t resolution = 0;
-        if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(),
-                                       frame, resolution, Layer::SnapLeft)) {
-            frame = getMainModel()->getStartFrame();
-        }
-    }
-
-    if (frame < 0) frame = 0;
-
-    if (m_viewManager->getPlaySelectionMode()) {
-        frame = m_viewManager->constrainFrameToSelection(size_t(frame));
-    }
-
-    m_viewManager->setPlaybackFrame(frame);
-}
-
-void
-MainWindow::rewindStart()
-{
-    if (!getMainModel()) return;
-
-    size_t frame = getMainModel()->getStartFrame();
-
-    if (m_viewManager->getPlaySelectionMode()) {
-        frame = m_viewManager->constrainFrameToSelection(frame);
-    }
-
-    m_viewManager->setPlaybackFrame(frame);
-}
-
-Layer *
-MainWindow::getSnapLayer() const
-{
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (!pane) return 0;
-
-    Layer *layer = pane->getSelectedLayer();
-
-    if (!dynamic_cast<TimeInstantLayer *>(layer) &&
-        !dynamic_cast<TimeValueLayer *>(layer) &&
-        !dynamic_cast<TimeRulerLayer *>(layer)) {
-
-        layer = 0;
-
-        for (int i = pane->getLayerCount(); i > 0; --i) {
-            Layer *l = pane->getLayer(i-1);
-            if (dynamic_cast<TimeRulerLayer *>(l)) {
-                layer = l;
-                break;
-            }
-        }
-    }
-
-    return layer;
-}
-
-void
-MainWindow::stop()
-{
-    m_playSource->stop();
-
-    if (m_paneStack && m_paneStack->getCurrentPane()) {
-        updateVisibleRangeDisplay(m_paneStack->getCurrentPane());
-    } else {
-        m_myStatusMessage = "";
-        statusBar()->showMessage("");
-    }
+    }     
 }
 
 void
@@ -4253,92 +2727,6 @@
     updateMenuStates();
 }
 
-MainWindow::AddPaneCommand::AddPaneCommand(MainWindow *mw) :
-    m_mw(mw),
-    m_pane(0),
-    m_prevCurrentPane(0),
-    m_added(false)
-{
-}
-
-MainWindow::AddPaneCommand::~AddPaneCommand()
-{
-    if (m_pane && !m_added) {
-	m_mw->m_paneStack->deletePane(m_pane);
-    }
-}
-
-QString
-MainWindow::AddPaneCommand::getName() const
-{
-    return tr("Add Pane");
-}
-
-void
-MainWindow::AddPaneCommand::execute()
-{
-    if (!m_pane) {
-	m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
-	m_pane = m_mw->m_paneStack->addPane();
-
-        connect(m_pane, SIGNAL(contextHelpChanged(const QString &)),
-                m_mw, SLOT(contextHelpChanged(const QString &)));
-    } else {
-	m_mw->m_paneStack->showPane(m_pane);
-    }
-
-    m_mw->m_paneStack->setCurrentPane(m_pane);
-    m_mw->m_overview->registerView(m_pane);
-    m_added = true;
-}
-
-void
-MainWindow::AddPaneCommand::unexecute()
-{
-    m_mw->m_paneStack->hidePane(m_pane);
-    m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
-    m_mw->m_overview->unregisterView(m_pane); 
-    m_added = false;
-}
-
-MainWindow::RemovePaneCommand::RemovePaneCommand(MainWindow *mw, Pane *pane) :
-    m_mw(mw),
-    m_pane(pane),
-    m_added(true)
-{
-}
-
-MainWindow::RemovePaneCommand::~RemovePaneCommand()
-{
-    if (m_pane && !m_added) {
-	m_mw->m_paneStack->deletePane(m_pane);
-    }
-}
-
-QString
-MainWindow::RemovePaneCommand::getName() const
-{
-    return tr("Remove Pane");
-}
-
-void
-MainWindow::RemovePaneCommand::execute()
-{
-    m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
-    m_mw->m_paneStack->hidePane(m_pane);
-    m_mw->m_overview->unregisterView(m_pane);
-    m_added = false;
-}
-
-void
-MainWindow::RemovePaneCommand::unexecute()
-{
-    m_mw->m_paneStack->showPane(m_pane);
-    m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
-    m_mw->m_overview->registerView(m_pane);
-    m_added = true;
-}
-
 void
 MainWindow::addLayer()
 {
@@ -4478,45 +2866,6 @@
 }
 
 void
-MainWindow::deleteCurrentPane()
-{
-    CommandHistory::getInstance()->startCompoundOperation
-	(tr("Delete Pane"), true);
-
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (pane) {
-	while (pane->getLayerCount() > 0) {
-	    Layer *layer = pane->getLayer(0);
-	    if (layer) {
-		m_document->removeLayerFromView(pane, layer);
-	    } else {
-		break;
-	    }
-	}
-
-	RemovePaneCommand *command = new RemovePaneCommand(this, pane);
-	CommandHistory::getInstance()->addCommand(command);
-    }
-
-    CommandHistory::getInstance()->endCompoundOperation();
-
-    updateMenuStates();
-}
-
-void
-MainWindow::deleteCurrentLayer()
-{
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (pane) {
-	Layer *layer = pane->getSelectedLayer();
-	if (layer) {
-	    m_document->removeLayerFromView(pane, layer);
-	}
-    }
-    updateMenuStates();
-}
-
-void
 MainWindow::renameCurrentLayer()
 {
     Pane *pane = m_paneStack->getCurrentPane();
@@ -4614,68 +2963,6 @@
 }
 
 void
-MainWindow::playbackFrameChanged(unsigned long frame)
-{
-    if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
-
-    RealTime now = RealTime::frame2RealTime
-        (frame, getMainModel()->getSampleRate());
-
-    if (now.sec == m_lastPlayStatusSec) return;
-
-    RealTime then = RealTime::frame2RealTime
-        (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate());
-
-    QString nowStr;
-    QString thenStr;
-    QString remainingStr;
-
-    if (then.sec > 10) {
-        nowStr = now.toSecText().c_str();
-        thenStr = then.toSecText().c_str();
-        remainingStr = (then - now).toSecText().c_str();
-        m_lastPlayStatusSec = now.sec;
-    } else {
-        nowStr = now.toText(true).c_str();
-        thenStr = then.toText(true).c_str();
-        remainingStr = (then - now).toText(true).c_str();
-    }        
-
-    m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)")
-        .arg(nowStr).arg(thenStr).arg(remainingStr);
-
-    statusBar()->showMessage(m_myStatusMessage);
-}
-
-void
-MainWindow::globalCentreFrameChanged(unsigned long )
-{
-    if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
-    Pane *p = 0;
-    if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
-    if (!p->getFollowGlobalPan()) return;
-    updateVisibleRangeDisplay(p);
-}
-
-void
-MainWindow::viewCentreFrameChanged(View *v, unsigned long )
-{
-    if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
-    Pane *p = 0;
-    if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
-    if (v == p) updateVisibleRangeDisplay(p);
-}
-
-void
-MainWindow::viewZoomLevelChanged(View *v, unsigned long , bool )
-{
-    if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
-    Pane *p = 0;
-    if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
-    if (v == p) updateVisibleRangeDisplay(p);
-}
-
-void
 MainWindow::updateVisibleRangeDisplay(Pane *p) const
 {
     if (!getMainModel() || !p) {
@@ -4756,68 +3043,23 @@
 }
 
 void
-MainWindow::layerAdded(Layer *)
+MainWindow::layerRemoved(Layer *layer)
 {
-//    std::cerr << "MainWindow::layerAdded(" << layer << ")" << std::endl;
-//    setupExistingLayersMenu();
-    updateMenuStates();
-}
-
-void
-MainWindow::layerRemoved(Layer *)
-{
-//    std::cerr << "MainWindow::layerRemoved(" << layer << ")" << std::endl;
     setupExistingLayersMenus();
-    updateMenuStates();
-}
-
-void
-MainWindow::layerAboutToBeDeleted(Layer *layer)
-{
-//    std::cerr << "MainWindow::layerAboutToBeDeleted(" << layer << ")" << std::endl;
-    if (layer == m_timeRulerLayer) {
-//	std::cerr << "(this is the time ruler layer)" << std::endl;
-	m_timeRulerLayer = 0;
-    }
+    MainWindowBase::layerRemoved(layer);
 }
 
 void
 MainWindow::layerInAView(Layer *layer, bool inAView)
 {
-//    std::cerr << "MainWindow::layerInAView(" << layer << "," << inAView << ")" << std::endl;
-
-    // Check whether we need to add or remove model from play source
-    Model *model = layer->getModel();
-    if (model) {
-        if (inAView) {
-            m_playSource->addModel(model);
-        } else {
-            bool found = false;
-            for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
-                Pane *pane = m_paneStack->getPane(i);
-                if (!pane) continue;
-                for (int j = 0; j < pane->getLayerCount(); ++j) {
-                    Layer *pl = pane->getLayer(j);
-                    if (pl && pl->getModel() == model) {
-                        found = true;
-                        break;
-                    }
-                }
-                if (found) break;
-            }
-            if (!found) m_playSource->removeModel(model);
-        }
-    }
-
     setupExistingLayersMenus();
-    updateMenuStates();
+    MainWindowBase::layerInAView(layer, inAView);
 }
 
 void
 MainWindow::modelAdded(Model *model)
 {
-//    std::cerr << "MainWindow::modelAdded(" << model << ")" << std::endl;
-    m_playSource->addModel(model);
+    MainWindowBase::modelAdded(model);
     if (dynamic_cast<DenseTimeValueModel *>(model)) {
         setupPaneAndLayerMenus();
     }
@@ -4826,22 +3068,54 @@
 void
 MainWindow::mainModelChanged(WaveFileModel *model)
 {
-//    std::cerr << "MainWindow::mainModelChanged(" << model << ")" << std::endl;
-    updateDescriptionLabel();
     m_panLayer->setModel(model);
-    if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
-    if (model && !m_playTarget && m_audioOutput) createPlayTarget();
+
+    MainWindowBase::mainModelChanged(model);
+
+    if (m_playTarget) {
+        connect(m_fader, SIGNAL(valueChanged(float)),
+                m_playTarget, SLOT(setOutputGain(float)));
+    }
 }
 
 void
-MainWindow::modelAboutToBeDeleted(Model *model)
+MainWindow::setInstantsNumbering()
 {
-//    std::cerr << "MainWindow::modelAboutToBeDeleted(" << model << ")" << std::endl;
-    if (model == m_viewManager->getPlaybackModel()) {
-        m_viewManager->setPlaybackModel(0);
-    }
-    m_playSource->removeModel(model);
-    FFTDataServer::modelAboutToBeDeleted(model);
+    QAction *a = dynamic_cast<QAction *>(sender());
+    if (!a) return;
+
+    int type = m_numberingActions[a];
+    
+    if (m_labeller) m_labeller->setType(Labeller::ValueType(type));
+
+    QSettings settings;
+    settings.beginGroup("MainWindow");
+    settings.setValue("labellertype", type);
+    settings.endGroup();
+}
+
+void
+MainWindow::setInstantsCounterCycle()
+{
+    QAction *a = dynamic_cast<QAction *>(sender());
+    if (!a) return;
+    
+    int cycle = a->text().toInt();
+    if (cycle == 0) return;
+
+    if (m_labeller) m_labeller->setCounterCycleSize(cycle);
+    
+    QSettings settings;
+    settings.beginGroup("MainWindow");
+    settings.setValue("labellercycle", cycle);
+    settings.endGroup();
+}
+
+void
+MainWindow::resetInstantsCounters()
+{
+    LabelCounterInputDialog dialog(m_labeller, this);
+    dialog.exec();
 }
 
 void
@@ -4875,19 +3149,6 @@
 }
 
 void
-MainWindow::propertyStacksResized()
-{
-/*
-    std::cerr << "MainWindow::propertyStacksResized" << std::endl;
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (pane && m_overview) {
-        std::cerr << "Fixed width: "  << pane->width() << std::endl;
-        m_overview->setFixedWidth(pane->width());
-    }
-*/
-}
-
-void
 MainWindow::showLayerTree()
 {
     if (!m_layerTreeView.isNull()) {
@@ -4907,23 +3168,6 @@
 }
 
 void
-MainWindow::pollOSC()
-{
-    if (!m_oscQueue || m_oscQueue->isEmpty()) return;
-    std::cerr << "MainWindow::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << std::endl;
-
-    if (m_openingAudioFile) return;
-
-    OSCMessage message = m_oscQueue->readMessage();
-
-    if (message.getTarget() != 0) {
-        return; //!!! for now -- this class is target 0, others not handled yet
-    }
-
-    handleOSCMessage(message);
-}
-
-void
 MainWindow::handleOSCMessage(const OSCMessage &message)
 {
     std::cerr << "MainWindow::handleOSCMessage: thread id = " 
@@ -5457,24 +3701,6 @@
 }
 
 void
-MainWindow::inProgressSelectionChanged()
-{
-    Pane *currentPane = 0;
-    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
-    if (currentPane) updateVisibleRangeDisplay(currentPane);
-}
-
-void
-MainWindow::contextHelpChanged(const QString &s)
-{
-    if (s == "" && m_myStatusMessage != "") {
-        statusBar()->showMessage(m_myStatusMessage);
-        return;
-    }
-    statusBar()->showMessage(s);
-}
-
-void
 MainWindow::website()
 {
     openHelpUrl(tr("http://www.sonicvisualiser.org/"));
@@ -5487,46 +3713,6 @@
 }
 
 void
-MainWindow::openHelpUrl(QString url)
-{
-    // This method mostly lifted from Qt Assistant source code
-
-    QProcess *process = new QProcess(this);
-    connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
-
-    QStringList args;
-
-#ifdef Q_OS_MAC
-    args.append(url);
-    process->start("open", args);
-#else
-#ifdef Q_OS_WIN32
-
-	QString pf(getenv("ProgramFiles"));
-	QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE");
-
-	args.append(url);
-	process->start(command, args);
-
-#else
-#ifdef Q_WS_X11
-    if (!qgetenv("KDE_FULL_SESSION").isEmpty()) {
-        args.append("exec");
-        args.append(url);
-        process->start("kfmclient", args);
-    } else if (!qgetenv("BROWSER").isEmpty()) {
-        args.append(url);
-        process->start(qgetenv("BROWSER"), args);
-    } else {
-        args.append(url);
-        process->start("firefox", args);
-    }
-#endif
-#endif
-#endif
-}
-
-void
 MainWindow::about()
 {
     bool debug = false;
--- a/main/MainWindow.h	Mon Oct 22 09:45:35 2007 +0000
+++ b/main/MainWindow.h	Mon Oct 22 14:24:31 2007 +0000
@@ -4,7 +4,7 @@
     Sonic Visualiser
     An audio file viewer and annotation editor.
     Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
+    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
@@ -19,9 +19,10 @@
 #include <QFrame>
 #include <QString>
 #include <QUrl>
-#include <QMainWindow>
 #include <QPointer>
 
+#include "MainWindowBase.h"
+
 #include "base/Command.h"
 #include "view/ViewManager.h"
 #include "base/PropertyContainer.h"
@@ -58,7 +59,7 @@
 class Labeller;
 
 
-class MainWindow : public QMainWindow
+class MainWindow : public MainWindowBase
 {
     Q_OBJECT
 
@@ -66,228 +67,102 @@
     MainWindow(bool withAudioOutput = true,
                bool withOSCSupport = true);
     virtual ~MainWindow();
-    
-    enum AudioFileOpenMode {
-        ReplaceMainModel,
-        CreateAdditionalModel,
-        ReplaceCurrentPane,
-        AskUser
-    };
-
-    enum FileOpenStatus {
-        FileOpenSucceeded,
-        FileOpenFailed,
-        FileOpenCancelled,
-        FileOpenWrongMode // attempted to open layer when no main model present
-    };
-
-    FileOpenStatus open(QString fileOrUrl, AudioFileOpenMode = AskUser);
-    FileOpenStatus open(FileSource source, AudioFileOpenMode = AskUser);
-
-    FileOpenStatus openAudio(FileSource source, AudioFileOpenMode = AskUser);
-    FileOpenStatus openPlaylist(FileSource source, AudioFileOpenMode = AskUser);
-    FileOpenStatus openLayer(FileSource source);
-    FileOpenStatus openImage(FileSource source);
-    FileOpenStatus openSession(FileSource source);
-
-    bool saveSessionFile(QString path);
-    bool commitData(bool mayAskUser); // on session shutdown
 
 signals:
-    // Used to toggle the availability of menu actions
-    void canAddPane(bool);
-    void canDeleteCurrentPane(bool);
-    void canAddLayer(bool);
-    void canImportMoreAudio(bool);
-    void canImportLayer(bool);
-    void canExportAudio(bool);
-    void canExportLayer(bool);
-    void canExportImage(bool);
-    void canRenameLayer(bool);
-    void canEditLayer(bool);
-    void canMeasureLayer(bool);
-    void canSelect(bool);
-    void canClearSelection(bool);
-    void canEditSelection(bool);
-    void canDeleteSelection(bool);
-    void canPaste(bool);
-    void canInsertInstant(bool);
-    void canInsertInstantsAtBoundaries(bool);
-    void canRenumberInstants(bool);
-    void canDeleteCurrentLayer(bool);
-    void canZoom(bool);
-    void canScroll(bool);
-    void canPlay(bool);
-    void canFfwd(bool);
-    void canRewind(bool);
-    void canPlaySelection(bool);
-    void canSpeedUpPlayback(bool);
-    void canSlowDownPlayback(bool);
-    void canChangePlaybackSpeed(bool);
-    void canSave(bool);
 
 public slots:
-    void preferenceChanged(PropertyContainer::PropertyName);
+    virtual void preferenceChanged(PropertyContainer::PropertyName);
+    virtual bool commitData(bool mayAskUser);
 
 protected slots:
-    void openSession();
-    void importAudio();
-    void importMoreAudio();
-    void openSomething();
-    void openLocation();
-    void openRecentFile();
-    void exportAudio();
-    void importLayer();
-    void exportLayer();
-    void exportImage();
-    void saveSession();
-    void saveSessionAs();
-    void newSession();
-    void closeSession();
-    void preferences();
+    virtual void openSession();
+    virtual void importAudio();
+    virtual void importMoreAudio();
+    virtual void openSomething();
+    virtual void openLocation();
+    virtual void openRecentFile();
+    virtual void exportAudio();
+    virtual void importLayer();
+    virtual void exportLayer();
+    virtual void exportImage();
+    virtual void saveSession();
+    virtual void saveSessionAs();
+    virtual void newSession();
+    virtual void closeSession();
+    virtual void preferences();
 
-    void zoomIn();
-    void zoomOut();
-    void zoomToFit();
-    void zoomDefault();
-    void scrollLeft();
-    void scrollRight();
-    void jumpLeft();
-    void jumpRight();
+    virtual void sampleRateMismatch(size_t, size_t, bool);
+    virtual void audioOverloadPluginDisabled();
 
-    void showNoOverlays();
-    void showMinimalOverlays();
-    void showStandardOverlays();
-    void showAllOverlays();
+    virtual void toolNavigateSelected();
+    virtual void toolSelectSelected();
+    virtual void toolEditSelected();
+    virtual void toolDrawSelected();
+    virtual void toolMeasureSelected();
 
-    void toggleZoomWheels();
-    void togglePropertyBoxes();
-    void toggleStatusBar();
+    virtual void documentModified();
+    virtual void documentRestored();
 
-    void play();
-    void ffwd();
-    void ffwdEnd();
-    void rewind();
-    void rewindStart();
-    void stop();
+    virtual void updateMenuStates();
+    virtual void updateDescriptionLabel();
 
-    void addPane();
-    void addLayer();
-    void deleteCurrentPane();
-    void renameCurrentLayer();
-    void deleteCurrentLayer();
+    virtual void setInstantsNumbering();
+    virtual void setInstantsCounterCycle();
+    virtual void resetInstantsCounters();
 
-    void playLoopToggled();
-    void playSelectionToggled();
-    void playSoloToggled();
-    void playSpeedChanged(int);
-    void playSharpenToggled();
-    void playMonoToggled();
-    void speedUpPlayback();
-    void slowDownPlayback();
-    void restoreNormalPlayback();
-    void sampleRateMismatch(size_t, size_t, bool);
-    void audioOverloadPluginDisabled();
+    virtual void modelGenerationFailed(QString);
+    virtual void modelRegenerationFailed(QString, QString);
 
-    void playbackFrameChanged(unsigned long);
-    void globalCentreFrameChanged(unsigned long);
-    void viewCentreFrameChanged(View *, unsigned long);
-    void viewZoomLevelChanged(View *, unsigned long, bool);
-    void outputLevelsChanged(float, float);
+    virtual void rightButtonMenuRequested(Pane *, QPoint point);
 
-    void currentPaneChanged(Pane *);
-    void currentLayerChanged(Pane *, Layer *);
+    virtual void addPane();
+    virtual void addLayer();
+    virtual void renameCurrentLayer();
 
-    void toolNavigateSelected();
-    void toolSelectSelected();
-    void toolEditSelected();
-    void toolDrawSelected();
-    void toolMeasureSelected();
+    virtual void paneAdded(Pane *);
+    virtual void paneHidden(Pane *);
+    virtual void paneAboutToBeDeleted(Pane *);
+    virtual void paneDropAccepted(Pane *, QStringList);
+    virtual void paneDropAccepted(Pane *, QString);
 
-    void selectAll();
-    void selectToStart();
-    void selectToEnd();
-    void selectVisible();
-    void clearSelection();
-    void cut();
-    void copy();
-    void paste();
-    void deleteSelected();
-    void insertInstant();
-    void insertInstantAt(size_t);
-    void insertInstantsAtBoundaries();
-    void setInstantsNumbering();
-    void setInstantsCounterCycle();
-    void resetInstantsCounters();
-    void renumberInstants();
+    virtual void setupRecentFilesMenu();
+    virtual void setupRecentTransformsMenu();
 
-    void documentModified();
-    void documentRestored();
+    virtual void playSpeedChanged(int);
+    virtual void playSharpenToggled();
+    virtual void playMonoToggled();
 
-    void updateMenuStates();
-    void updateDescriptionLabel();
+    virtual void speedUpPlayback();
+    virtual void slowDownPlayback();
+    virtual void restoreNormalPlayback();
 
-    void layerAdded(Layer *);
-    void layerRemoved(Layer *);
-    void layerAboutToBeDeleted(Layer *);
-    void layerInAView(Layer *, bool);
+    virtual void outputLevelsChanged(float, float);
 
-    void mainModelChanged(WaveFileModel *);
-    void modelAdded(Model *);
-    void modelAboutToBeDeleted(Model *);
+    virtual void layerRemoved(Layer *);
+    virtual void layerInAView(Layer *, bool);
 
-    void modelGenerationFailed(QString);
-    void modelRegenerationFailed(QString, QString);
+    virtual void mainModelChanged(WaveFileModel *);
+    virtual void modelAdded(Model *);
 
-    void rightButtonMenuRequested(Pane *, QPoint point);
+    virtual void showLayerTree();
 
-    void propertyStacksResized();
+    virtual void mouseEnteredWidget();
+    virtual void mouseLeftWidget();
 
-    void paneDropAccepted(Pane *, QStringList);
-    void paneDropAccepted(Pane *, QString);
+    virtual void handleOSCMessage(const OSCMessage &);
 
-    void setupRecentFilesMenu();
-    void setupRecentTransformsMenu();
-
-    void showLayerTree();
-
-    void pollOSC();
-    void handleOSCMessage(const OSCMessage &);
-
-    void mouseEnteredWidget();
-    void mouseLeftWidget();
-    void contextHelpChanged(const QString &);
-    void inProgressSelectionChanged();
-
-    void website();
-    void help();
-    void about();
-    void keyReference();
+    virtual void website();
+    virtual void help();
+    virtual void about();
+    virtual void keyReference();
 
 protected:
-    QString                  m_sessionFile;
-    QString                  m_audioFile;
-    Document                *m_document;
-
-    QLabel                  *m_descriptionLabel;
-    PaneStack               *m_paneStack;
-    ViewManager             *m_viewManager;
     Overview                *m_overview;
     Fader                   *m_fader;
     AudioDial               *m_playSpeed;
     QPushButton             *m_playSharpen;
     QPushButton             *m_playMono;
     WaveformLayer           *m_panLayer;
-    Layer                   *m_timeRulerLayer;
-
-    bool                     m_audioOutput;
-    AudioCallbackPlaySource *m_playSource;
-    AudioCallbackPlayTarget *m_playTarget;
-
-    OSCQueue                *m_oscQueue;
-
-    RecentFiles              m_recentFiles;
-    RecentFiles              m_recentTransforms;
 
     bool                     m_mainMenusCreated;
     QMenu                   *m_paneMenu;
@@ -307,26 +182,11 @@
     QAction                 *m_ffwdAction;
     QAction                 *m_rwdAction;
 
-    bool                     m_documentModified;
-    bool                     m_openingAudioFile;
-    bool                     m_abandoning;
-
-    Labeller                *m_labeller;
-
-    int                      m_lastPlayStatusSec;
-    mutable QString          m_myStatusMessage;
-
     QPointer<PreferencesDialog> m_preferencesDialog;
     QPointer<QTreeView>      m_layerTreeView;
 
-    bool                     m_initialDarkBackground;
-
     KeyReference            *m_keyReference;
 
-    WaveFileModel *getMainModel();
-    const WaveFileModel *getMainModel() const;
-    void createDocument();
-
     struct PaneConfiguration {
 	PaneConfiguration(LayerFactory::LayerType _layer
 			                       = LayerFactory::TimeRuler,
@@ -360,87 +220,22 @@
     typedef std::map<QAction *, int> NumberingActionMap;
     NumberingActionMap m_numberingActions;
 
-    void setupMenus();
-    void setupFileMenu();
-    void setupEditMenu();
-    void setupViewMenu();
-    void setupPaneAndLayerMenus();
-    void setupTransformsMenu();
-    void setupHelpMenu();
-    void setupExistingLayersMenus();
-    void setupToolbars();
+    virtual void setupMenus();
+    virtual void setupFileMenu();
+    virtual void setupEditMenu();
+    virtual void setupViewMenu();
+    virtual void setupPaneAndLayerMenus();
+    virtual void setupTransformsMenu();
+    virtual void setupHelpMenu();
+    virtual void setupExistingLayersMenus();
+    virtual void setupToolbars();
 
-    Pane *addPaneToStack();
-
-    void addPane(const PaneConfiguration &configuration, QString text);
-
-    Layer *getSnapLayer() const;
-
-    class PaneCallback : public SVFileReaderPaneCallback
-    {
-    public:
-	PaneCallback(MainWindow *mw) : m_mw(mw) { }
-	virtual Pane *addPane() { return m_mw->addPaneToStack(); }
-	virtual void setWindowSize(int width, int height) {
-	    m_mw->resize(width, height);
-	}
-	virtual void addSelection(int start, int end) {
-	    m_mw->m_viewManager->addSelection(Selection(start, end));
-	}
-    protected:
-	MainWindow *m_mw;
-    };
-
-    class AddPaneCommand : public Command
-    {
-    public:
-	AddPaneCommand(MainWindow *mw);
-	virtual ~AddPaneCommand();
-	
-	virtual void execute();
-	virtual void unexecute();
-	virtual QString getName() const;
-
-	Pane *getPane() { return m_pane; }
-
-    protected:
-	MainWindow *m_mw;
-	Pane *m_pane; // Main window owns this, but I determine its lifespan
-	Pane *m_prevCurrentPane; // I don't own this
-	bool m_added;
-    };
-
-    class RemovePaneCommand : public Command
-    {
-    public:
-	RemovePaneCommand(MainWindow *mw, Pane *pane);
-	virtual ~RemovePaneCommand();
-	
-	virtual void execute();
-	virtual void unexecute();
-	virtual QString getName() const;
-
-    protected:
-	MainWindow *m_mw;
-	Pane *m_pane; // Main window owns this, but I determine its lifespan
-	Pane *m_prevCurrentPane; // I don't own this
-	bool m_added;
-    };
+    virtual void addPane(const PaneConfiguration &configuration, QString text);
 
     virtual void closeEvent(QCloseEvent *e);
-    bool checkSaveModified();
-
-    QString getOpenFileName(FileFinder::FileType type);
-    QString getSaveFileName(FileFinder::FileType type);
-    void registerLastOpenedFilePath(FileFinder::FileType type, QString path);
-
-    void createPlayTarget();
-
-    void openHelpUrl(QString url);
-
-    void updateVisibleRangeDisplay(Pane *p) const;
-
-    void toXml(QTextStream &stream);
+    virtual bool checkSaveModified();
+    
+    virtual void updateVisibleRangeDisplay(Pane *p) const;
 };
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/MainWindowBase.cpp	Mon Oct 22 14:24:31 2007 +0000
@@ -0,0 +1,2006 @@
+/* -*- 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 "MainWindowBase.h"
+#include "document/Document.h"
+
+
+#include "view/Pane.h"
+#include "view/PaneStack.h"
+#include "data/model/WaveFileModel.h"
+#include "data/model/SparseOneDimensionalModel.h"
+#include "data/model/NoteModel.h"
+#include "data/model/Labeller.h"
+#include "view/ViewManager.h"
+
+#include "layer/WaveformLayer.h"
+#include "layer/TimeRulerLayer.h"
+#include "layer/TimeInstantLayer.h"
+#include "layer/TimeValueLayer.h"
+#include "layer/Colour3DPlotLayer.h"
+#include "layer/SliceLayer.h"
+#include "layer/SliceableLayer.h"
+#include "layer/ImageLayer.h"
+
+#include "widgets/ListInputDialog.h"
+
+#include "audioio/AudioCallbackPlaySource.h"
+#include "audioio/AudioCallbackPlayTarget.h"
+#include "audioio/AudioTargetFactory.h"
+#include "audioio/PlaySpeedRangeMapper.h"
+#include "data/fileio/DataFileReaderFactory.h"
+#include "data/fileio/PlaylistFileReader.h"
+#include "data/fileio/WavFileWriter.h"
+#include "data/fileio/CSVFileWriter.h"
+#include "data/fileio/MIDIFileWriter.h"
+#include "data/fileio/BZipFileDevice.h"
+#include "data/fileio/FileSource.h"
+
+#include "data/fft/FFTDataServer.h"
+
+#include "base/RecentFiles.h"
+
+#include "base/PlayParameterRepository.h"
+#include "base/XmlExportable.h"
+#include "base/CommandHistory.h"
+#include "base/Profiler.h"
+#include "base/Preferences.h"
+
+#include "osc/OSCQueue.h"
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QGridLayout>
+#include <QLabel>
+#include <QAction>
+#include <QMenuBar>
+#include <QToolBar>
+#include <QInputDialog>
+#include <QStatusBar>
+#include <QTreeView>
+#include <QFile>
+#include <QFileInfo>
+#include <QDir>
+#include <QTextStream>
+#include <QProcess>
+#include <QShortcut>
+#include <QSettings>
+#include <QDateTime>
+#include <QProcess>
+#include <QCheckBox>
+#include <QRegExp>
+#include <QScrollArea>
+
+#include <iostream>
+#include <cstdio>
+#include <errno.h>
+
+using std::cerr;
+using std::endl;
+
+using std::vector;
+using std::map;
+using std::set;
+
+
+MainWindowBase::MainWindowBase(bool withAudioOutput, bool withOSCSupport) :
+    m_document(0),
+    m_paneStack(0),
+    m_viewManager(0),
+    m_timeRulerLayer(0),
+    m_audioOutput(withAudioOutput),
+    m_playSource(0),
+    m_playTarget(0),
+    m_oscQueue(withOSCSupport ? new OSCQueue() : 0),
+    m_recentFiles("RecentFiles", 20),
+    m_recentTransforms("RecentTransforms", 20),
+    m_documentModified(false),
+    m_openingAudioFile(false),
+    m_abandoning(false),
+    m_labeller(0)
+{
+    connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()),
+	    this, SLOT(documentModified()));
+    connect(CommandHistory::getInstance(), SIGNAL(documentRestored()),
+	    this, SLOT(documentRestored()));
+    
+    m_viewManager = new ViewManager();
+    connect(m_viewManager, SIGNAL(selectionChanged()),
+	    this, SLOT(updateMenuStates()));
+    connect(m_viewManager, SIGNAL(inProgressSelectionChanged()),
+	    this, SLOT(inProgressSelectionChanged()));
+
+    Preferences::BackgroundMode mode =
+        Preferences::getInstance()->getBackgroundMode();
+    m_initialDarkBackground = m_viewManager->getGlobalDarkBackground();
+    if (mode != Preferences::BackgroundFromTheme) {
+        m_viewManager->setGlobalDarkBackground
+            (mode == Preferences::DarkBackground);
+    }
+
+    m_paneStack = new PaneStack(0, m_viewManager);
+    connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)),
+	    this, SLOT(currentPaneChanged(Pane *)));
+    connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)),
+	    this, SLOT(currentLayerChanged(Pane *, Layer *)));
+    connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)),
+            this, SLOT(rightButtonMenuRequested(Pane *, QPoint)));
+    connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)),
+            this, SLOT(contextHelpChanged(const QString &)));
+    connect(m_paneStack, SIGNAL(paneAdded(Pane *)),
+            this, SLOT(paneAdded(Pane *)));
+    connect(m_paneStack, SIGNAL(paneHidden(Pane *)),
+            this, SLOT(paneHidden(Pane *)));
+    connect(m_paneStack, SIGNAL(paneAboutToBeDeleted(Pane *)),
+            this, SLOT(paneAboutToBeDeleted(Pane *)));
+    connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QStringList)),
+            this, SLOT(paneDropAccepted(Pane *, QStringList)));
+    connect(m_paneStack, SIGNAL(dropAccepted(Pane *, QString)),
+            this, SLOT(paneDropAccepted(Pane *, QString)));
+
+    m_playSource = new AudioCallbackPlaySource(m_viewManager);
+
+    connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)),
+	    this,           SLOT(sampleRateMismatch(size_t, size_t, bool)));
+    connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()),
+            this,           SLOT(audioOverloadPluginDisabled()));
+
+    connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)),
+	    this, SLOT(outputLevelsChanged(float, float)));
+
+    connect(m_viewManager, SIGNAL(playbackFrameChanged(unsigned long)),
+            this, SLOT(playbackFrameChanged(unsigned long)));
+
+    connect(m_viewManager, SIGNAL(globalCentreFrameChanged(unsigned long)),
+            this, SLOT(globalCentreFrameChanged(unsigned long)));
+
+    connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)),
+            this, SLOT(viewCentreFrameChanged(View *, unsigned long)));
+
+    connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)),
+            this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool)));
+
+    connect(Preferences::getInstance(),
+            SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
+            this,
+            SLOT(preferenceChanged(PropertyContainer::PropertyName)));
+
+    if (m_oscQueue && m_oscQueue->isOK()) {
+        connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC()));
+        QTimer *oscTimer = new QTimer(this);
+        connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC()));
+        oscTimer->start(1000);
+    }
+
+    Labeller::ValueType labellerType = Labeller::ValueFromTwoLevelCounter;
+    QSettings settings;
+    settings.beginGroup("MainWindow");
+    labellerType = (Labeller::ValueType)
+        settings.value("labellertype", (int)labellerType).toInt();
+    int cycle = settings.value("labellercycle", 4).toInt();
+    settings.endGroup();
+
+    m_labeller = new Labeller(labellerType);
+    m_labeller->setCounterCycleSize(cycle);
+}
+
+MainWindowBase::~MainWindowBase()
+{
+    delete m_playTarget;
+    delete m_playSource;
+    delete m_viewManager;
+    delete m_oscQueue;
+    Profiles::getInstance()->dump();
+}
+
+QString
+MainWindowBase::getOpenFileName(FileFinder::FileType type)
+{
+    FileFinder *ff = FileFinder::getInstance();
+    switch (type) {
+    case FileFinder::SessionFile:
+        return ff->getOpenFileName(type, m_sessionFile);
+    case FileFinder::AudioFile:
+        return ff->getOpenFileName(type, m_audioFile);
+    case FileFinder::LayerFile:
+        return ff->getOpenFileName(type, m_sessionFile);
+    case FileFinder::LayerFileNoMidi:
+        return ff->getOpenFileName(type, m_sessionFile);
+    case FileFinder::SessionOrAudioFile:
+        return ff->getOpenFileName(type, m_sessionFile);
+    case FileFinder::ImageFile:
+        return ff->getOpenFileName(type, m_sessionFile);
+    case FileFinder::AnyFile:
+        if (getMainModel() != 0 &&
+            m_paneStack != 0 &&
+            m_paneStack->getCurrentPane() != 0) { // can import a layer
+            return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile);
+        } else {
+            return ff->getOpenFileName(FileFinder::SessionOrAudioFile,
+                                       m_sessionFile);
+        }
+    }
+    return "";
+}
+
+QString
+MainWindowBase::getSaveFileName(FileFinder::FileType type)
+{
+    FileFinder *ff = FileFinder::getInstance();
+    switch (type) {
+    case FileFinder::SessionFile:
+        return ff->getSaveFileName(type, m_sessionFile);
+    case FileFinder::AudioFile:
+        return ff->getSaveFileName(type, m_audioFile);
+    case FileFinder::LayerFile:
+        return ff->getSaveFileName(type, m_sessionFile);
+    case FileFinder::LayerFileNoMidi:
+        return ff->getSaveFileName(type, m_sessionFile);
+    case FileFinder::SessionOrAudioFile:
+        return ff->getSaveFileName(type, m_sessionFile);
+    case FileFinder::ImageFile:
+        return ff->getSaveFileName(type, m_sessionFile);
+    case FileFinder::AnyFile:
+        return ff->getSaveFileName(type, m_sessionFile);
+    }
+    return "";
+}
+
+void
+MainWindowBase::registerLastOpenedFilePath(FileFinder::FileType type, QString path)
+{
+    FileFinder *ff = FileFinder::getInstance();
+    ff->registerLastOpenedFilePath(type, path);
+}
+
+void
+MainWindowBase::updateMenuStates()
+{
+    Pane *currentPane = 0;
+    Layer *currentLayer = 0;
+
+    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentLayer = currentPane->getSelectedLayer();
+
+    bool haveCurrentPane =
+        (currentPane != 0);
+    bool haveCurrentLayer =
+        (haveCurrentPane &&
+         (currentLayer != 0));
+    bool haveMainModel =
+	(getMainModel() != 0);
+    bool havePlayTarget =
+	(m_playTarget != 0);
+    bool haveSelection = 
+	(m_viewManager &&
+	 !m_viewManager->getSelections().empty());
+    bool haveCurrentEditableLayer =
+	(haveCurrentLayer &&
+	 currentLayer->isLayerEditable());
+    bool haveCurrentTimeInstantsLayer = 
+	(haveCurrentLayer &&
+	 dynamic_cast<TimeInstantLayer *>(currentLayer));
+    bool haveCurrentColour3DPlot =
+        (haveCurrentLayer &&
+         dynamic_cast<Colour3DPlotLayer *>(currentLayer));
+    bool haveClipboardContents =
+        (m_viewManager &&
+         !m_viewManager->getClipboard().empty());
+
+    emit canAddPane(haveMainModel);
+    emit canDeleteCurrentPane(haveCurrentPane);
+    emit canZoom(haveMainModel && haveCurrentPane);
+    emit canScroll(haveMainModel && haveCurrentPane);
+    emit canAddLayer(haveMainModel && haveCurrentPane);
+    emit canImportMoreAudio(haveMainModel);
+    emit canImportLayer(haveMainModel && haveCurrentPane);
+    emit canExportAudio(haveMainModel);
+    emit canExportLayer(haveMainModel &&
+                        (haveCurrentEditableLayer || haveCurrentColour3DPlot));
+    emit canExportImage(haveMainModel && haveCurrentPane);
+    emit canDeleteCurrentLayer(haveCurrentLayer);
+    emit canRenameLayer(haveCurrentLayer);
+    emit canEditLayer(haveCurrentEditableLayer);
+    emit canMeasureLayer(haveCurrentLayer);
+    emit canSelect(haveMainModel && haveCurrentPane);
+    emit canPlay(havePlayTarget);
+    emit canFfwd(true);
+    emit canRewind(true);
+    emit canPaste(haveCurrentEditableLayer && haveClipboardContents);
+    emit canInsertInstant(haveCurrentPane);
+    emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection);
+    emit canRenumberInstants(haveCurrentTimeInstantsLayer && haveSelection);
+    emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection);
+    emit canClearSelection(haveSelection);
+    emit canEditSelection(haveSelection && haveCurrentEditableLayer);
+    emit canSave(m_sessionFile != "" && m_documentModified);
+}
+
+void
+MainWindowBase::documentModified()
+{
+//    std::cerr << "MainWindowBase::documentModified" << std::endl;
+
+    if (!m_documentModified) {
+        //!!! this in subclass implementation?
+	setWindowTitle(tr("%1 (modified)").arg(windowTitle()));
+    }
+
+    m_documentModified = true;
+    updateMenuStates();
+}
+
+void
+MainWindowBase::documentRestored()
+{
+//    std::cerr << "MainWindowBase::documentRestored" << std::endl;
+
+    if (m_documentModified) {
+        //!!! this in subclass implementation?
+	QString wt(windowTitle());
+	wt.replace(tr(" (modified)"), "");
+	setWindowTitle(wt);
+    }
+
+    m_documentModified = false;
+    updateMenuStates();
+}
+
+void
+MainWindowBase::playLoopToggled()
+{
+    QAction *action = dynamic_cast<QAction *>(sender());
+    
+    if (action) {
+	m_viewManager->setPlayLoopMode(action->isChecked());
+    } else {
+	m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode());
+    }
+}
+
+void
+MainWindowBase::playSelectionToggled()
+{
+    QAction *action = dynamic_cast<QAction *>(sender());
+    
+    if (action) {
+	m_viewManager->setPlaySelectionMode(action->isChecked());
+    } else {
+	m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode());
+    }
+}
+
+void
+MainWindowBase::playSoloToggled()
+{
+    QAction *action = dynamic_cast<QAction *>(sender());
+    
+    if (action) {
+	m_viewManager->setPlaySoloMode(action->isChecked());
+    } else {
+	m_viewManager->setPlaySoloMode(!m_viewManager->getPlaySoloMode());
+    }
+
+    if (m_viewManager->getPlaySoloMode()) {
+        currentPaneChanged(m_paneStack->getCurrentPane());
+    } else {
+        m_viewManager->setPlaybackModel(0);
+        if (m_playSource) {
+            m_playSource->clearSoloModelSet();
+        }
+    }
+}
+
+void
+MainWindowBase::currentPaneChanged(Pane *p)
+{
+    updateMenuStates();
+    updateVisibleRangeDisplay(p);
+
+    if (!p) return;
+
+    if (!(m_viewManager &&
+          m_playSource &&
+          m_viewManager->getPlaySoloMode())) {
+        if (m_viewManager) m_viewManager->setPlaybackModel(0);
+        return;
+    }
+
+    Model *prevPlaybackModel = m_viewManager->getPlaybackModel();
+
+    View::ModelSet soloModels = p->getModels();
+    
+    for (View::ModelSet::iterator mi = soloModels.begin();
+         mi != soloModels.end(); ++mi) {
+        if (dynamic_cast<RangeSummarisableTimeValueModel *>(*mi)) {
+            m_viewManager->setPlaybackModel(*mi);
+        }
+    }
+    
+    RangeSummarisableTimeValueModel *a = 
+        dynamic_cast<RangeSummarisableTimeValueModel *>(prevPlaybackModel);
+    RangeSummarisableTimeValueModel *b = 
+        dynamic_cast<RangeSummarisableTimeValueModel *>(m_viewManager->
+                                                        getPlaybackModel());
+
+    m_playSource->setSoloModelSet(soloModels);
+
+    if (a && b && (a != b)) {
+        int frame = m_playSource->getCurrentPlayingFrame();
+        //!!! I don't really believe that these functions are the right way around
+        int rframe = a->alignFromReference(frame);
+        int bframe = b->alignToReference(rframe);
+        if (m_playSource->isPlaying()) m_playSource->play(bframe);
+    }
+}
+
+void
+MainWindowBase::currentLayerChanged(Pane *p, Layer *)
+{
+    updateMenuStates();
+    updateVisibleRangeDisplay(p);
+}
+
+void
+MainWindowBase::selectAll()
+{
+    if (!getMainModel()) return;
+    m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(),
+					  getMainModel()->getEndFrame()));
+}
+
+void
+MainWindowBase::selectToStart()
+{
+    if (!getMainModel()) return;
+    m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(),
+					  m_viewManager->getGlobalCentreFrame()));
+}
+
+void
+MainWindowBase::selectToEnd()
+{
+    if (!getMainModel()) return;
+    m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(),
+					  getMainModel()->getEndFrame()));
+}
+
+void
+MainWindowBase::selectVisible()
+{
+    Model *model = getMainModel();
+    if (!model) return;
+
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (!currentPane) return;
+
+    size_t startFrame, endFrame;
+
+    if (currentPane->getStartFrame() < 0) startFrame = 0;
+    else startFrame = currentPane->getStartFrame();
+
+    if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame();
+    else endFrame = currentPane->getEndFrame();
+
+    m_viewManager->setSelection(Selection(startFrame, endFrame));
+}
+
+void
+MainWindowBase::clearSelection()
+{
+    m_viewManager->clearSelections();
+}
+
+void
+MainWindowBase::cut()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (!currentPane) return;
+
+    Layer *layer = currentPane->getSelectedLayer();
+    if (!layer) return;
+
+    Clipboard &clipboard = m_viewManager->getClipboard();
+    clipboard.clear();
+
+    MultiSelection::SelectionList selections = m_viewManager->getSelections();
+
+    CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true);
+
+    for (MultiSelection::SelectionList::iterator i = selections.begin();
+         i != selections.end(); ++i) {
+        layer->copy(*i, clipboard);
+        layer->deleteSelection(*i);
+    }
+
+    CommandHistory::getInstance()->endCompoundOperation();
+}
+
+void
+MainWindowBase::copy()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (!currentPane) return;
+
+    Layer *layer = currentPane->getSelectedLayer();
+    if (!layer) return;
+
+    Clipboard &clipboard = m_viewManager->getClipboard();
+    clipboard.clear();
+
+    MultiSelection::SelectionList selections = m_viewManager->getSelections();
+
+    for (MultiSelection::SelectionList::iterator i = selections.begin();
+         i != selections.end(); ++i) {
+        layer->copy(*i, clipboard);
+    }
+}
+
+void
+MainWindowBase::paste()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (!currentPane) return;
+
+    //!!! if we have no current layer, we should create one of the most
+    // appropriate type
+
+    Layer *layer = currentPane->getSelectedLayer();
+    if (!layer) return;
+
+    Clipboard &clipboard = m_viewManager->getClipboard();
+    Clipboard::PointList contents = clipboard.getPoints();
+/*
+    long minFrame = 0;
+    bool have = false;
+    for (int i = 0; i < contents.size(); ++i) {
+        if (!contents[i].haveFrame()) continue;
+        if (!have || contents[i].getFrame() < minFrame) {
+            minFrame = contents[i].getFrame();
+            have = true;
+        }
+    }
+
+    long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame;
+
+    layer->paste(clipboard, frameOffset);
+*/
+    layer->paste(clipboard, 0, true);
+}
+
+void
+MainWindowBase::deleteSelected()
+{
+    if (m_paneStack->getCurrentPane() &&
+	m_paneStack->getCurrentPane()->getSelectedLayer()) {
+        
+        Layer *layer = m_paneStack->getCurrentPane()->getSelectedLayer();
+
+        if (m_viewManager && 
+            (m_viewManager->getToolMode() == ViewManager::MeasureMode)) {
+
+            layer->deleteCurrentMeasureRect();
+
+        } else {
+
+            MultiSelection::SelectionList selections =
+                m_viewManager->getSelections();
+            
+            for (MultiSelection::SelectionList::iterator i = selections.begin();
+                 i != selections.end(); ++i) {
+                layer->deleteSelection(*i);
+            }
+	}
+    }
+}
+
+void
+MainWindowBase::insertInstant()
+{
+    int frame = m_viewManager->getPlaybackFrame();
+    insertInstantAt(frame);
+}
+
+void
+MainWindowBase::insertInstantsAtBoundaries()
+{
+    MultiSelection::SelectionList selections = m_viewManager->getSelections();
+    for (MultiSelection::SelectionList::iterator i = selections.begin();
+         i != selections.end(); ++i) {
+        size_t start = i->getStartFrame();
+        size_t end = i->getEndFrame();
+        if (start != end) {
+            insertInstantAt(i->getStartFrame());
+            insertInstantAt(i->getEndFrame());
+        }
+    }
+}
+
+void
+MainWindowBase::insertInstantAt(size_t frame)
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (!pane) {
+        return;
+    }
+
+    Layer *layer = dynamic_cast<TimeInstantLayer *>
+        (pane->getSelectedLayer());
+
+    if (!layer) {
+        for (int i = pane->getLayerCount(); i > 0; --i) {
+            layer = dynamic_cast<TimeInstantLayer *>(pane->getLayer(i - 1));
+            if (layer) break;
+        }
+
+        if (!layer) {
+            CommandHistory::getInstance()->startCompoundOperation
+                (tr("Add Point"), true);
+            layer = m_document->createEmptyLayer(LayerFactory::TimeInstants);
+            if (layer) {
+                m_document->addLayerToView(pane, layer);
+                m_paneStack->setCurrentLayer(pane, layer);
+            }
+            CommandHistory::getInstance()->endCompoundOperation();
+        }
+    }
+
+    if (layer) {
+    
+        Model *model = layer->getModel();
+        SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
+            (model);
+
+        if (sodm) {
+            SparseOneDimensionalModel::Point point(frame, "");
+
+            SparseOneDimensionalModel::Point prevPoint(0);
+            bool havePrevPoint = false;
+
+            SparseOneDimensionalModel::EditCommand *command =
+                new SparseOneDimensionalModel::EditCommand(sodm, tr("Add Point"));
+
+            if (m_labeller->actingOnPrevPoint()) {
+
+                SparseOneDimensionalModel::PointList prevPoints =
+                    sodm->getPreviousPoints(frame);
+
+                if (!prevPoints.empty()) {
+                    prevPoint = *prevPoints.begin();
+                    havePrevPoint = true;
+                }
+            }
+
+            if (m_labeller) {
+
+                m_labeller->setSampleRate(sodm->getSampleRate());
+
+                if (havePrevPoint) {
+                    command->deletePoint(prevPoint);
+                }
+
+                m_labeller->label<SparseOneDimensionalModel::Point>
+                    (point, havePrevPoint ? &prevPoint : 0);
+
+                if (havePrevPoint) {
+                    command->addPoint(prevPoint);
+                }
+            }
+            
+            command->addPoint(point);
+
+            command->setName(tr("Add Point at %1 s")
+                             .arg(RealTime::frame2RealTime
+                                  (frame,
+                                   sodm->getSampleRate())
+                                  .toText(false).c_str()));
+
+            command->finish();
+        }
+    }
+}
+
+void
+MainWindowBase::renumberInstants()
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (!pane) return;
+
+    Layer *layer = dynamic_cast<TimeInstantLayer *>(pane->getSelectedLayer());
+    if (!layer) return;
+
+    MultiSelection ms(m_viewManager->getSelection());
+    
+    Model *model = layer->getModel();
+    SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
+        (model);
+    if (!sodm) return;
+
+    if (!m_labeller) return;
+
+    Labeller labeller(*m_labeller);
+    labeller.setSampleRate(sodm->getSampleRate());
+
+    // This uses a command
+
+    labeller.labelAll<SparseOneDimensionalModel::Point>(*sodm, &ms);
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::open(QString fileOrUrl, AudioFileOpenMode mode)
+{
+    return open(FileSource(fileOrUrl), mode);
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::open(FileSource source, AudioFileOpenMode mode)
+{
+    FileOpenStatus status;
+
+    if (!source.isAvailable()) return FileOpenFailed;
+    source.waitForData();
+
+    bool canImportLayer = (getMainModel() != 0 &&
+                           m_paneStack != 0 &&
+                           m_paneStack->getCurrentPane() != 0);
+
+    if ((status = openAudio(source, mode)) != FileOpenFailed) {
+        return status;
+    } else if ((status = openSession(source)) != FileOpenFailed) {
+	return status;
+    } else if ((status = openPlaylist(source, mode)) != FileOpenFailed) {
+        return status;
+    } else if (!canImportLayer) {
+        return FileOpenWrongMode;
+    } else if ((status = openImage(source)) != FileOpenFailed) {
+        return status;
+    } else if ((status = openLayer(source)) != FileOpenFailed) {
+        return status;
+    } else {
+	return FileOpenFailed;
+    }
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::openAudio(FileSource source, AudioFileOpenMode mode)
+{
+    std::cerr << "MainWindowBase::openAudio(" << source.getLocation().toStdString() << ")" << std::endl;
+
+    if (!source.isAvailable()) return FileOpenFailed;
+    source.waitForData();
+
+    m_openingAudioFile = true;
+
+    size_t rate = 0;
+
+    if (Preferences::getInstance()->getResampleOnLoad()) {
+        rate = m_playSource->getSourceSampleRate();
+    }
+
+    WaveFileModel *newModel = new WaveFileModel(source, rate);
+
+    if (!newModel->isOK()) {
+	delete newModel;
+        m_openingAudioFile = false;
+	return FileOpenFailed;
+    }
+
+    std::cerr << "mode = " << mode << std::endl;
+
+    if (mode == AskUser) {
+        if (getMainModel()) {
+
+            static bool prevSetAsMain = true;
+            bool setAsMain = true;
+            
+            QStringList items;
+            items << tr("Replace the existing main waveform")
+                  << tr("Load this file into a new waveform pane");
+
+            bool ok = false;
+            QString item = ListInputDialog::getItem
+                (this, tr("Select target for import"),
+                 tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"),
+                 items, prevSetAsMain ? 0 : 1, &ok);
+            
+            if (!ok || item.isEmpty()) {
+                delete newModel;
+                m_openingAudioFile = false;
+                return FileOpenCancelled;
+            }
+            
+            setAsMain = (item == items[0]);
+            prevSetAsMain = setAsMain;
+
+            if (setAsMain) mode = ReplaceMainModel;
+            else mode = CreateAdditionalModel;
+
+        } else {
+            mode = ReplaceMainModel;
+        }
+    }
+
+    if (mode == ReplaceCurrentPane) {
+
+        Pane *pane = m_paneStack->getCurrentPane();
+        if (pane) {
+            if (getMainModel()) {
+                View::ModelSet models(pane->getModels());
+                if (models.find(getMainModel()) != models.end()) {
+                    mode = ReplaceMainModel;
+                }
+            } else {
+                mode = ReplaceMainModel;
+            }
+        } else {
+            mode = CreateAdditionalModel;
+        }
+    }
+
+    if (mode == CreateAdditionalModel && !getMainModel()) {
+        mode = ReplaceMainModel;
+    }
+
+    if (mode == ReplaceMainModel) {
+
+        Model *prevMain = getMainModel();
+        if (prevMain) {
+            m_playSource->removeModel(prevMain);
+            PlayParameterRepository::getInstance()->removeModel(prevMain);
+        }
+        PlayParameterRepository::getInstance()->addModel(newModel);
+
+	m_document->setMainModel(newModel);
+
+	setupMenus();
+
+	if (m_sessionFile == "") {
+            //!!! shouldn't be dealing directly with title from here -- call a method
+	    setWindowTitle(tr("Sonic Visualiser: %1")
+                           .arg(source.getLocation()));
+	    CommandHistory::getInstance()->clear();
+	    CommandHistory::getInstance()->documentSaved();
+	    m_documentModified = false;
+	} else {
+	    setWindowTitle(tr("Sonic Visualiser: %1 [%2]")
+			   .arg(QFileInfo(m_sessionFile).fileName())
+			   .arg(source.getLocation()));
+	    if (m_documentModified) {
+		m_documentModified = false;
+		documentModified(); // so as to restore "(modified)" window title
+	    }
+	}
+
+        if (!source.isRemote()) m_audioFile = source.getLocalFilename();
+
+    } else if (mode == CreateAdditionalModel) {
+
+	CommandHistory::getInstance()->startCompoundOperation
+	    (tr("Import \"%1\"").arg(source.getLocation()), true);
+
+	m_document->addImportedModel(newModel);
+
+	AddPaneCommand *command = new AddPaneCommand(this);
+	CommandHistory::getInstance()->addCommand(command);
+
+	Pane *pane = command->getPane();
+
+	if (!m_timeRulerLayer) {
+	    m_timeRulerLayer = m_document->createMainModelLayer
+		(LayerFactory::TimeRuler);
+	}
+
+	m_document->addLayerToView(pane, m_timeRulerLayer);
+
+	Layer *newLayer = m_document->createImportedLayer(newModel);
+
+	if (newLayer) {
+	    m_document->addLayerToView(pane, newLayer);
+	}
+	
+	CommandHistory::getInstance()->endCompoundOperation();
+
+    } else if (mode == ReplaceCurrentPane) {
+
+        // We know there is a current pane, otherwise we would have
+        // reset the mode to CreateAdditionalModel above; and we know
+        // the current pane does not contain the main model, otherwise
+        // we would have reset it to ReplaceMainModel.  But we don't
+        // know whether the pane contains a waveform model at all.
+        
+        Pane *pane = m_paneStack->getCurrentPane();
+        Layer *replace = 0;
+
+        for (int i = 0; i < pane->getLayerCount(); ++i) {
+            Layer *layer = pane->getLayer(i);
+            if (dynamic_cast<WaveformLayer *>(layer)) {
+                replace = layer;
+                break;
+            }
+        }
+
+	CommandHistory::getInstance()->startCompoundOperation
+	    (tr("Import \"%1\"").arg(source.getLocation()), true);
+
+	m_document->addImportedModel(newModel);
+
+        if (replace) {
+            m_document->removeLayerFromView(pane, replace);
+        }
+
+	Layer *newLayer = m_document->createImportedLayer(newModel);
+
+	if (newLayer) {
+	    m_document->addLayerToView(pane, newLayer);
+	}
+	
+	CommandHistory::getInstance()->endCompoundOperation();
+    }
+
+    updateMenuStates();
+    m_recentFiles.addFile(source.getLocation());
+    if (!source.isRemote()) {
+        // for file dialog
+        registerLastOpenedFilePath(FileFinder::AudioFile,
+                                   source.getLocalFilename());
+    }
+    m_openingAudioFile = false;
+
+    currentPaneChanged(m_paneStack->getCurrentPane());
+
+    return FileOpenSucceeded;
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::openPlaylist(FileSource source, AudioFileOpenMode mode)
+{
+    std::set<QString> extensions;
+    PlaylistFileReader::getSupportedExtensions(extensions);
+    QString extension = source.getExtension();
+    if (extensions.find(extension) == extensions.end()) return FileOpenFailed;
+
+    if (!source.isAvailable()) return FileOpenFailed;
+    source.waitForData();
+
+    PlaylistFileReader reader(source.getLocalFilename());
+    if (!reader.isOK()) return FileOpenFailed;
+
+    PlaylistFileReader::Playlist playlist = reader.load();
+
+    bool someSuccess = false;
+
+    for (PlaylistFileReader::Playlist::const_iterator i = playlist.begin();
+         i != playlist.end(); ++i) {
+
+        FileOpenStatus status = openAudio(*i, mode);
+
+        if (status == FileOpenCancelled) {
+            return FileOpenCancelled;
+        }
+
+        if (status == FileOpenSucceeded) {
+            someSuccess = true;
+            mode = CreateAdditionalModel;
+        }
+    }
+
+    if (someSuccess) return FileOpenSucceeded;
+    else return FileOpenFailed;
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::openLayer(FileSource source)
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    
+    if (!pane) {
+	// shouldn't happen, as the menu action should have been disabled
+	std::cerr << "WARNING: MainWindowBase::openLayer: no current pane" << std::endl;
+	return FileOpenWrongMode;
+    }
+
+    if (!getMainModel()) {
+	// shouldn't happen, as the menu action should have been disabled
+	std::cerr << "WARNING: MainWindowBase::openLayer: No main model -- hence no default sample rate available" << std::endl;
+	return FileOpenWrongMode;
+    }
+
+    if (!source.isAvailable()) return FileOpenFailed;
+    source.waitForData();
+
+    QString path = source.getLocalFilename();
+
+    if (source.getExtension() == "svl" || source.getExtension() == "xml") {
+
+        PaneCallback callback(this);
+        QFile file(path);
+        
+        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+            std::cerr << "ERROR: MainWindowBase::openLayer("
+                      << source.getLocation().toStdString()
+                      << "): Failed to open file for reading" << std::endl;
+            return FileOpenFailed;
+        }
+        
+        SVFileReader reader(m_document, callback, source.getLocation());
+        reader.setCurrentPane(pane);
+        
+        QXmlInputSource inputSource(&file);
+        reader.parse(inputSource);
+        
+        if (!reader.isOK()) {
+            std::cerr << "ERROR: MainWindowBase::openLayer("
+                      << source.getLocation().toStdString()
+                      << "): Failed to read XML file: "
+                      << reader.getErrorString().toStdString() << std::endl;
+            return FileOpenFailed;
+        }
+
+        m_recentFiles.addFile(source.getLocation());
+
+        if (!source.isRemote()) {
+            registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog
+        }
+
+    } else {
+        
+        try {
+
+            Model *model = DataFileReaderFactory::load
+                (path, getMainModel()->getSampleRate());
+        
+            if (model) {
+
+                std::cerr << "MainWindowBase::openLayer: Have model" << std::endl;
+
+                Layer *newLayer = m_document->createImportedLayer(model);
+
+                if (newLayer) {
+
+                    m_document->addLayerToView(pane, newLayer);
+                    m_recentFiles.addFile(source.getLocation());
+                    
+                    if (!source.isRemote()) {
+                        registerLastOpenedFilePath
+                            (FileFinder::LayerFile,
+                             path); // for file dialog
+                    }
+                    
+                    return FileOpenSucceeded;
+                }
+            }
+        } catch (DataFileReaderFactory::Exception e) {
+            if (e == DataFileReaderFactory::ImportCancelled) {
+                return FileOpenCancelled;
+            }
+        }
+    }
+    
+    source.setLeaveLocalFile(true);
+    return FileOpenFailed;
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::openImage(FileSource source)
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    
+    if (!pane) {
+	// shouldn't happen, as the menu action should have been disabled
+	std::cerr << "WARNING: MainWindowBase::openImage: no current pane" << std::endl;
+	return FileOpenWrongMode;
+    }
+
+    if (!m_document->getMainModel()) {
+        return FileOpenWrongMode;
+    }
+
+    bool newLayer = false;
+
+    ImageLayer *il = dynamic_cast<ImageLayer *>(pane->getSelectedLayer());
+    if (!il) {
+        for (int i = pane->getLayerCount()-1; i >= 0; --i) {
+            il = dynamic_cast<ImageLayer *>(pane->getLayer(i));
+            if (il) break;
+        }
+    }
+    if (!il) {
+        il = dynamic_cast<ImageLayer *>
+            (m_document->createEmptyLayer(LayerFactory::Image));
+        if (!il) return FileOpenFailed;
+        newLayer = true;
+    }
+
+    // We don't put the image file in Recent Files
+
+    std::cerr << "openImage: trying location \"" << source.getLocation().toStdString() << "\" in image layer" << std::endl;
+
+    if (!il->addImage(m_viewManager->getGlobalCentreFrame(), source.getLocation())) {
+        if (newLayer) {
+            m_document->setModel(il, 0); // releasing its model
+            delete il;
+        }
+        return FileOpenFailed;
+    } else {
+        if (newLayer) {
+            m_document->addLayerToView(pane, il);
+        }
+        m_paneStack->setCurrentLayer(pane, il);
+    }
+
+    return FileOpenSucceeded;
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::openSessionFile(QString fileOrUrl)
+{
+    return openSession(FileSource(fileOrUrl));
+}
+
+MainWindowBase::FileOpenStatus
+MainWindowBase::openSession(FileSource source)
+{
+    if (!source.isAvailable()) return FileOpenFailed;
+    if (source.getExtension() != "sv") return FileOpenFailed;
+    source.waitForData();
+
+    BZipFileDevice bzFile(source.getLocalFilename());
+    if (!bzFile.open(QIODevice::ReadOnly)) return FileOpenFailed;
+
+    if (!checkSaveModified()) return FileOpenCancelled;
+
+    QString error;
+    closeSession();
+    createDocument();
+
+    PaneCallback callback(this);
+    m_viewManager->clearSelections();
+
+    SVFileReader reader(m_document, callback, source.getLocation());
+    QXmlInputSource inputSource(&bzFile);
+    reader.parse(inputSource);
+    
+    if (!reader.isOK()) {
+        error = tr("SV XML file read error:\n%1").arg(reader.getErrorString());
+    }
+    
+    bzFile.close();
+
+    bool ok = (error == "");
+
+    if (ok) {
+
+	setWindowTitle(tr("Sonic Visualiser: %1")
+		       .arg(source.getLocation()));
+
+	if (!source.isRemote()) m_sessionFile = source.getLocalFilename();
+
+	setupMenus();
+
+	CommandHistory::getInstance()->clear();
+	CommandHistory::getInstance()->documentSaved();
+	m_documentModified = false;
+	updateMenuStates();
+
+        m_recentFiles.addFile(source.getLocation());
+
+        if (!source.isRemote()) {
+            // for file dialog
+            registerLastOpenedFilePath(FileFinder::SessionFile,
+                                        source.getLocalFilename());
+        }
+
+    } else {
+	setWindowTitle(tr("Sonic Visualiser"));
+    }
+
+    return ok ? FileOpenSucceeded : FileOpenFailed;
+}
+
+void
+MainWindowBase::createPlayTarget()
+{
+    if (m_playTarget) return;
+
+    m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource);
+    if (!m_playTarget) {
+	QMessageBox::warning
+	    (this, tr("Couldn't open audio device"),
+	     tr("<b>No audio available</b><p>Could not open an audio device for playback.<p>Audio playback will not be available during this session."),
+	     QMessageBox::Ok);
+    }
+}
+
+WaveFileModel *
+MainWindowBase::getMainModel()
+{
+    if (!m_document) return 0;
+    return m_document->getMainModel();
+}
+
+const WaveFileModel *
+MainWindowBase::getMainModel() const
+{
+    if (!m_document) return 0;
+    return m_document->getMainModel();
+}
+
+void
+MainWindowBase::createDocument()
+{
+    m_document = new Document;
+
+    connect(m_document, SIGNAL(layerAdded(Layer *)),
+	    this, SLOT(layerAdded(Layer *)));
+    connect(m_document, SIGNAL(layerRemoved(Layer *)),
+	    this, SLOT(layerRemoved(Layer *)));
+    connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)),
+	    this, SLOT(layerAboutToBeDeleted(Layer *)));
+    connect(m_document, SIGNAL(layerInAView(Layer *, bool)),
+	    this, SLOT(layerInAView(Layer *, bool)));
+
+    connect(m_document, SIGNAL(modelAdded(Model *)),
+	    this, SLOT(modelAdded(Model *)));
+    connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)),
+	    this, SLOT(mainModelChanged(WaveFileModel *)));
+    connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)),
+	    this, SLOT(modelAboutToBeDeleted(Model *)));
+
+    connect(m_document, SIGNAL(modelGenerationFailed(QString)),
+            this, SLOT(modelGenerationFailed(QString)));
+    connect(m_document, SIGNAL(modelRegenerationFailed(QString, QString)),
+            this, SLOT(modelRegenerationFailed(QString, QString)));
+}
+
+bool
+MainWindowBase::saveSessionFile(QString path)
+{
+    BZipFileDevice bzFile(path);
+    if (!bzFile.open(QIODevice::WriteOnly)) {
+        std::cerr << "Failed to open session file \"" << path.toStdString()
+                  << "\" for writing: "
+                  << bzFile.errorString().toStdString() << std::endl;
+        return false;
+    }
+
+    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
+
+    QTextStream out(&bzFile);
+    toXml(out);
+    out.flush();
+
+    QApplication::restoreOverrideCursor();
+
+    if (!bzFile.isOK()) {
+	QMessageBox::critical(this, tr("Failed to write file"),
+			      tr("<b>Save failed</b><p>Failed to write to file \"%1\": %2")
+			      .arg(path).arg(bzFile.errorString()));
+        bzFile.close();
+	return false;
+    }
+
+    bzFile.close();
+    return true;
+}
+
+void
+MainWindowBase::toXml(QTextStream &out)
+{
+    QString indent("  ");
+
+    out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+    out << "<!DOCTYPE sonic-visualiser>\n";
+    out << "<sv>\n";
+
+    m_document->toXml(out, "", "");
+
+    out << "<display>\n";
+
+    out << QString("  <window width=\"%1\" height=\"%2\"/>\n")
+	.arg(width()).arg(height());
+
+    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
+
+	Pane *pane = m_paneStack->getPane(i);
+
+	if (pane) {
+            pane->toXml(out, indent);
+	}
+    }
+
+    out << "</display>\n";
+
+    m_viewManager->getSelection().toXml(out);
+
+    out << "</sv>\n";
+}
+
+Pane *
+MainWindowBase::addPaneToStack()
+{
+    AddPaneCommand *command = new AddPaneCommand(this);
+    CommandHistory::getInstance()->addCommand(command);
+    return command->getPane();
+}
+
+void
+MainWindowBase::zoomIn()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->zoom(true);
+}
+
+void
+MainWindowBase::zoomOut()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->zoom(false);
+}
+
+void
+MainWindowBase::zoomToFit()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (!currentPane) return;
+
+    Model *model = getMainModel();
+    if (!model) return;
+    
+    size_t start = model->getStartFrame();
+    size_t end = model->getEndFrame();
+    size_t pixels = currentPane->width();
+
+    size_t sw = currentPane->getVerticalScaleWidth();
+    if (pixels > sw * 2) pixels -= sw * 2;
+    else pixels = 1;
+    if (pixels > 4) pixels -= 4;
+
+    size_t zoomLevel = (end - start) / pixels;
+
+    currentPane->setZoomLevel(zoomLevel);
+    currentPane->setCentreFrame((start + end) / 2);
+}
+
+void
+MainWindowBase::zoomDefault()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->setZoomLevel(1024);
+}
+
+void
+MainWindowBase::scrollLeft()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->scroll(false, false);
+}
+
+void
+MainWindowBase::jumpLeft()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->scroll(false, true);
+}
+
+void
+MainWindowBase::scrollRight()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->scroll(true, false);
+}
+
+void
+MainWindowBase::jumpRight()
+{
+    Pane *currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) currentPane->scroll(true, true);
+}
+
+void
+MainWindowBase::showNoOverlays()
+{
+    m_viewManager->setOverlayMode(ViewManager::NoOverlays);
+}
+
+void
+MainWindowBase::showMinimalOverlays()
+{
+    m_viewManager->setOverlayMode(ViewManager::MinimalOverlays);
+}
+
+void
+MainWindowBase::showStandardOverlays()
+{
+    m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
+}
+
+void
+MainWindowBase::showAllOverlays()
+{
+    m_viewManager->setOverlayMode(ViewManager::AllOverlays);
+}
+
+void
+MainWindowBase::toggleZoomWheels()
+{
+    if (m_viewManager->getZoomWheelsEnabled()) {
+        m_viewManager->setZoomWheelsEnabled(false);
+    } else {
+        m_viewManager->setZoomWheelsEnabled(true);
+    }
+}
+
+void
+MainWindowBase::togglePropertyBoxes()
+{
+    if (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks) {
+        if (Preferences::getInstance()->getPropertyBoxLayout() ==
+            Preferences::VerticallyStacked) {
+            m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
+        } else {
+            m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
+        }
+    } else {
+        m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks);
+    }
+}
+
+void
+MainWindowBase::toggleStatusBar()
+{
+    QSettings settings;
+    settings.beginGroup("MainWindow");
+    bool sb = settings.value("showstatusbar", true).toBool();
+
+    if (sb) {
+        statusBar()->hide();
+    } else {
+        statusBar()->show();
+    }
+
+    settings.setValue("showstatusbar", !sb);
+
+    settings.endGroup();
+}
+
+void
+MainWindowBase::preferenceChanged(PropertyContainer::PropertyName name)
+{
+    if (name == "Property Box Layout") {
+        if (m_paneStack->getLayoutStyle() != PaneStack::NoPropertyStacks) {
+            if (Preferences::getInstance()->getPropertyBoxLayout() ==
+                Preferences::VerticallyStacked) {
+                m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout);
+            } else {
+                m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout);
+            }
+        }
+    } else if (name == "Background Mode" && m_viewManager) {
+        Preferences::BackgroundMode mode =
+            Preferences::getInstance()->getBackgroundMode();
+        if (mode == Preferences::BackgroundFromTheme) {
+            m_viewManager->setGlobalDarkBackground(m_initialDarkBackground);
+        } else if (mode == Preferences::DarkBackground) {
+            m_viewManager->setGlobalDarkBackground(true);
+        } else {
+            m_viewManager->setGlobalDarkBackground(false);
+        }
+    }            
+}
+
+void
+MainWindowBase::play()
+{
+    if (m_playSource->isPlaying()) {
+        stop();
+    } else {
+        playbackFrameChanged(m_viewManager->getPlaybackFrame());
+	m_playSource->play(m_viewManager->getPlaybackFrame());
+    }
+}
+
+void
+MainWindowBase::ffwd()
+{
+    if (!getMainModel()) return;
+
+    int frame = m_viewManager->getPlaybackFrame();
+    ++frame;
+
+    Layer *layer = getSnapLayer();
+    size_t sr = getMainModel()->getSampleRate();
+
+    if (!layer) {
+
+        frame = RealTime::realTime2Frame
+            (RealTime::frame2RealTime(frame, sr) + RealTime(2, 0), sr);
+        if (frame > int(getMainModel()->getEndFrame())) {
+            frame = getMainModel()->getEndFrame();
+        }
+
+    } else {
+
+        size_t resolution = 0;
+        if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(),
+                                       frame, resolution, Layer::SnapRight)) {
+            frame = getMainModel()->getEndFrame();
+        }
+    }
+        
+    if (frame < 0) frame = 0;
+
+    if (m_viewManager->getPlaySelectionMode()) {
+        frame = m_viewManager->constrainFrameToSelection(size_t(frame));
+    }
+    
+    m_viewManager->setPlaybackFrame(frame);
+}
+
+void
+MainWindowBase::ffwdEnd()
+{
+    if (!getMainModel()) return;
+
+    size_t frame = getMainModel()->getEndFrame();
+
+    if (m_viewManager->getPlaySelectionMode()) {
+        frame = m_viewManager->constrainFrameToSelection(frame);
+    }
+
+    m_viewManager->setPlaybackFrame(frame);
+}
+
+void
+MainWindowBase::rewind()
+{
+    if (!getMainModel()) return;
+
+    int frame = m_viewManager->getPlaybackFrame();
+    if (frame > 0) --frame;
+
+    Layer *layer = getSnapLayer();
+    size_t sr = getMainModel()->getSampleRate();
+    
+    // when rewinding during playback, we want to allow a period
+    // following a rewind target point at which the rewind will go to
+    // the prior point instead of the immediately neighbouring one
+    if (m_playSource && m_playSource->isPlaying()) {
+        RealTime ct = RealTime::frame2RealTime(frame, sr);
+        ct = ct - RealTime::fromSeconds(0.25);
+        if (ct < RealTime::zeroTime) ct = RealTime::zeroTime;
+//        std::cerr << "rewind: frame " << frame << " -> ";
+        frame = RealTime::realTime2Frame(ct, sr);
+//        std::cerr << frame << std::endl;
+    }
+
+    if (!layer) {
+        
+        frame = RealTime::realTime2Frame
+            (RealTime::frame2RealTime(frame, sr) - RealTime(2, 0), sr);
+        if (frame < int(getMainModel()->getStartFrame())) {
+            frame = getMainModel()->getStartFrame();
+        }
+
+    } else {
+
+        size_t resolution = 0;
+        if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(),
+                                       frame, resolution, Layer::SnapLeft)) {
+            frame = getMainModel()->getStartFrame();
+        }
+    }
+
+    if (frame < 0) frame = 0;
+
+    if (m_viewManager->getPlaySelectionMode()) {
+        frame = m_viewManager->constrainFrameToSelection(size_t(frame));
+    }
+
+    m_viewManager->setPlaybackFrame(frame);
+}
+
+void
+MainWindowBase::rewindStart()
+{
+    if (!getMainModel()) return;
+
+    size_t frame = getMainModel()->getStartFrame();
+
+    if (m_viewManager->getPlaySelectionMode()) {
+        frame = m_viewManager->constrainFrameToSelection(frame);
+    }
+
+    m_viewManager->setPlaybackFrame(frame);
+}
+
+Layer *
+MainWindowBase::getSnapLayer() const
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (!pane) return 0;
+
+    Layer *layer = pane->getSelectedLayer();
+
+    if (!dynamic_cast<TimeInstantLayer *>(layer) &&
+        !dynamic_cast<TimeValueLayer *>(layer) &&
+        !dynamic_cast<TimeRulerLayer *>(layer)) {
+
+        layer = 0;
+
+        for (int i = pane->getLayerCount(); i > 0; --i) {
+            Layer *l = pane->getLayer(i-1);
+            if (dynamic_cast<TimeRulerLayer *>(l)) {
+                layer = l;
+                break;
+            }
+        }
+    }
+
+    return layer;
+}
+
+void
+MainWindowBase::stop()
+{
+    m_playSource->stop();
+
+    if (m_paneStack && m_paneStack->getCurrentPane()) {
+        updateVisibleRangeDisplay(m_paneStack->getCurrentPane());
+    } else {
+        m_myStatusMessage = "";
+        statusBar()->showMessage("");
+    }
+}
+
+MainWindowBase::AddPaneCommand::AddPaneCommand(MainWindowBase *mw) :
+    m_mw(mw),
+    m_pane(0),
+    m_prevCurrentPane(0),
+    m_added(false)
+{
+}
+
+MainWindowBase::AddPaneCommand::~AddPaneCommand()
+{
+    if (m_pane && !m_added) {
+	m_mw->m_paneStack->deletePane(m_pane);
+    }
+}
+
+QString
+MainWindowBase::AddPaneCommand::getName() const
+{
+    return tr("Add Pane");
+}
+
+void
+MainWindowBase::AddPaneCommand::execute()
+{
+    if (!m_pane) {
+	m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
+	m_pane = m_mw->m_paneStack->addPane();
+
+        connect(m_pane, SIGNAL(contextHelpChanged(const QString &)),
+                m_mw, SLOT(contextHelpChanged(const QString &)));
+    } else {
+	m_mw->m_paneStack->showPane(m_pane);
+    }
+
+    m_mw->m_paneStack->setCurrentPane(m_pane);
+    m_added = true;
+}
+
+void
+MainWindowBase::AddPaneCommand::unexecute()
+{
+    m_mw->m_paneStack->hidePane(m_pane);
+    m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
+    m_added = false;
+}
+
+MainWindowBase::RemovePaneCommand::RemovePaneCommand(MainWindowBase *mw, Pane *pane) :
+    m_mw(mw),
+    m_pane(pane),
+    m_added(true)
+{
+}
+
+MainWindowBase::RemovePaneCommand::~RemovePaneCommand()
+{
+    if (m_pane && !m_added) {
+	m_mw->m_paneStack->deletePane(m_pane);
+    }
+}
+
+QString
+MainWindowBase::RemovePaneCommand::getName() const
+{
+    return tr("Remove Pane");
+}
+
+void
+MainWindowBase::RemovePaneCommand::execute()
+{
+    m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane();
+    m_mw->m_paneStack->hidePane(m_pane);
+    m_added = false;
+}
+
+void
+MainWindowBase::RemovePaneCommand::unexecute()
+{
+    m_mw->m_paneStack->showPane(m_pane);
+    m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane);
+    m_added = true;
+}
+
+void
+MainWindowBase::deleteCurrentPane()
+{
+    CommandHistory::getInstance()->startCompoundOperation
+	(tr("Delete Pane"), true);
+
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (pane) {
+	while (pane->getLayerCount() > 0) {
+	    Layer *layer = pane->getLayer(0);
+	    if (layer) {
+		m_document->removeLayerFromView(pane, layer);
+	    } else {
+		break;
+	    }
+	}
+
+	RemovePaneCommand *command = new RemovePaneCommand(this, pane);
+	CommandHistory::getInstance()->addCommand(command);
+    }
+
+    CommandHistory::getInstance()->endCompoundOperation();
+
+    updateMenuStates();
+}
+
+void
+MainWindowBase::deleteCurrentLayer()
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (pane) {
+	Layer *layer = pane->getSelectedLayer();
+	if (layer) {
+	    m_document->removeLayerFromView(pane, layer);
+	}
+    }
+    updateMenuStates();
+}
+
+void
+MainWindowBase::playbackFrameChanged(unsigned long frame)
+{
+    if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
+
+    RealTime now = RealTime::frame2RealTime
+        (frame, getMainModel()->getSampleRate());
+
+    if (now.sec == m_lastPlayStatusSec) return;
+
+    RealTime then = RealTime::frame2RealTime
+        (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate());
+
+    QString nowStr;
+    QString thenStr;
+    QString remainingStr;
+
+    if (then.sec > 10) {
+        nowStr = now.toSecText().c_str();
+        thenStr = then.toSecText().c_str();
+        remainingStr = (then - now).toSecText().c_str();
+        m_lastPlayStatusSec = now.sec;
+    } else {
+        nowStr = now.toText(true).c_str();
+        thenStr = then.toText(true).c_str();
+        remainingStr = (then - now).toText(true).c_str();
+    }        
+
+    m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)")
+        .arg(nowStr).arg(thenStr).arg(remainingStr);
+
+    statusBar()->showMessage(m_myStatusMessage);
+}
+
+void
+MainWindowBase::globalCentreFrameChanged(unsigned long )
+{
+    if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
+    Pane *p = 0;
+    if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
+    if (!p->getFollowGlobalPan()) return;
+    updateVisibleRangeDisplay(p);
+}
+
+void
+MainWindowBase::viewCentreFrameChanged(View *v, unsigned long )
+{
+    if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
+    Pane *p = 0;
+    if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
+    if (v == p) updateVisibleRangeDisplay(p);
+}
+
+void
+MainWindowBase::viewZoomLevelChanged(View *v, unsigned long , bool )
+{
+    if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
+    Pane *p = 0;
+    if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return;
+    if (v == p) updateVisibleRangeDisplay(p);
+}
+
+void
+MainWindowBase::layerAdded(Layer *)
+{
+//    std::cerr << "MainWindowBase::layerAdded(" << layer << ")" << std::endl;
+    updateMenuStates();
+}
+
+void
+MainWindowBase::layerRemoved(Layer *)
+{
+//    std::cerr << "MainWindowBase::layerRemoved(" << layer << ")" << std::endl;
+    updateMenuStates();
+}
+
+void
+MainWindowBase::layerAboutToBeDeleted(Layer *layer)
+{
+//    std::cerr << "MainWindowBase::layerAboutToBeDeleted(" << layer << ")" << std::endl;
+    if (layer == m_timeRulerLayer) {
+//	std::cerr << "(this is the time ruler layer)" << std::endl;
+	m_timeRulerLayer = 0;
+    }
+}
+
+void
+MainWindowBase::layerInAView(Layer *layer, bool inAView)
+{
+//    std::cerr << "MainWindowBase::layerInAView(" << layer << "," << inAView << ")" << std::endl;
+
+    // Check whether we need to add or remove model from play source
+    Model *model = layer->getModel();
+    if (model) {
+        if (inAView) {
+            m_playSource->addModel(model);
+        } else {
+            bool found = false;
+            for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
+                Pane *pane = m_paneStack->getPane(i);
+                if (!pane) continue;
+                for (int j = 0; j < pane->getLayerCount(); ++j) {
+                    Layer *pl = pane->getLayer(j);
+                    if (pl && pl->getModel() == model) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (found) break;
+            }
+            if (!found) m_playSource->removeModel(model);
+        }
+    }
+
+    updateMenuStates();
+}
+
+void
+MainWindowBase::modelAdded(Model *model)
+{
+//    std::cerr << "MainWindowBase::modelAdded(" << model << ")" << std::endl;
+    m_playSource->addModel(model);
+}
+
+void
+MainWindowBase::mainModelChanged(WaveFileModel *model)
+{
+//    std::cerr << "MainWindowBase::mainModelChanged(" << model << ")" << std::endl;
+    updateDescriptionLabel();
+    if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate());
+    if (model && !m_playTarget && m_audioOutput) createPlayTarget();
+}
+
+void
+MainWindowBase::modelAboutToBeDeleted(Model *model)
+{
+//    std::cerr << "MainWindowBase::modelAboutToBeDeleted(" << model << ")" << std::endl;
+    if (model == m_viewManager->getPlaybackModel()) {
+        m_viewManager->setPlaybackModel(0);
+    }
+    m_playSource->removeModel(model);
+    FFTDataServer::modelAboutToBeDeleted(model);
+}
+
+void
+MainWindowBase::pollOSC()
+{
+    if (!m_oscQueue || m_oscQueue->isEmpty()) return;
+    std::cerr << "MainWindowBase::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << std::endl;
+
+    if (m_openingAudioFile) return;
+
+    OSCMessage message = m_oscQueue->readMessage();
+
+    if (message.getTarget() != 0) {
+        return; //!!! for now -- this class is target 0, others not handled yet
+    }
+
+    handleOSCMessage(message);
+}
+
+void
+MainWindowBase::inProgressSelectionChanged()
+{
+    Pane *currentPane = 0;
+    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
+    if (currentPane) updateVisibleRangeDisplay(currentPane);
+}
+
+void
+MainWindowBase::contextHelpChanged(const QString &s)
+{
+    if (s == "" && m_myStatusMessage != "") {
+        statusBar()->showMessage(m_myStatusMessage);
+        return;
+    }
+    statusBar()->showMessage(s);
+}
+
+void
+MainWindowBase::openHelpUrl(QString url)
+{
+    // This method mostly lifted from Qt Assistant source code
+
+    QProcess *process = new QProcess(this);
+    connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
+
+    QStringList args;
+
+#ifdef Q_OS_MAC
+    args.append(url);
+    process->start("open", args);
+#else
+#ifdef Q_OS_WIN32
+
+	QString pf(getenv("ProgramFiles"));
+	QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE");
+
+	args.append(url);
+	process->start(command, args);
+
+#else
+#ifdef Q_WS_X11
+    if (!qgetenv("KDE_FULL_SESSION").isEmpty()) {
+        args.append("exec");
+        args.append(url);
+        process->start("kfmclient", args);
+    } else if (!qgetenv("BROWSER").isEmpty()) {
+        args.append(url);
+        process->start(qgetenv("BROWSER"), args);
+    } else {
+        args.append(url);
+        process->start("firefox", args);
+    }
+#endif
+#endif
+#endif
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/MainWindowBase.h	Mon Oct 22 14:24:31 2007 +0000
@@ -0,0 +1,339 @@
+/* -*- 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.
+*/
+
+#ifndef _MAIN_WINDOW_BASE_H_
+#define _MAIN_WINDOW_BASE_H_
+
+#include <QFrame>
+#include <QString>
+#include <QUrl>
+#include <QMainWindow>
+#include <QPointer>
+
+#include "base/Command.h"
+#include "view/ViewManager.h"
+#include "base/PropertyContainer.h"
+#include "base/RecentFiles.h"
+#include "layer/LayerFactory.h"
+#include "transform/Transform.h"
+#include "document/SVFileReader.h"
+#include "data/fileio/FileFinder.h"
+#include "data/fileio/FileSource.h"
+#include <map>
+
+class Document;
+class PaneStack;
+class Pane;
+class View;
+class Fader;
+class Overview;
+class Layer;
+class WaveformLayer;
+class WaveFileModel;
+class AudioCallbackPlaySource;
+class AudioCallbackPlayTarget;
+class CommandHistory;
+class QMenu;
+class AudioDial;
+class QLabel;
+class QCheckBox;
+class PreferencesDialog;
+class QTreeView;
+class QPushButton;
+class OSCQueue;
+class OSCMessage;
+class KeyReference;
+class Labeller;
+
+/**
+ * The base class for the SV main window.  This includes everything to
+ * do with general document and pane stack management, but nothing
+ * that involves user interaction -- this doesn't create the widget or
+ * menu structures or editing tools, and if a function needs to open a
+ * dialog, it shouldn't be in here.  This permits "variations on SV"
+ * to use different subclasses retaining the same general structure.
+ */
+
+class MainWindowBase : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    MainWindowBase(bool withAudioOutput, bool withOSCSupport);
+    virtual ~MainWindowBase();
+    
+    enum AudioFileOpenMode {
+        ReplaceMainModel,
+        CreateAdditionalModel,
+        ReplaceCurrentPane,
+        AskUser
+    };
+
+    enum FileOpenStatus {
+        FileOpenSucceeded,
+        FileOpenFailed,
+        FileOpenCancelled,
+        FileOpenWrongMode // attempted to open layer when no main model present
+    };
+
+    virtual FileOpenStatus open(QString fileOrUrl, AudioFileOpenMode = AskUser);
+    virtual FileOpenStatus open(FileSource source, AudioFileOpenMode = AskUser);
+    
+    virtual FileOpenStatus openAudio(FileSource source, AudioFileOpenMode = AskUser);
+    virtual FileOpenStatus openPlaylist(FileSource source, AudioFileOpenMode = AskUser);
+    virtual FileOpenStatus openLayer(FileSource source);
+    virtual FileOpenStatus openImage(FileSource source);
+
+    virtual FileOpenStatus openSessionFile(QString fileOrUrl);
+    virtual FileOpenStatus openSession(FileSource source);
+
+    virtual bool saveSessionFile(QString path);
+
+signals:
+    // Used to toggle the availability of menu actions
+    void canAddPane(bool);
+    void canDeleteCurrentPane(bool);
+    void canAddLayer(bool);
+    void canImportMoreAudio(bool);
+    void canImportLayer(bool);
+    void canExportAudio(bool);
+    void canExportLayer(bool);
+    void canExportImage(bool);
+    void canRenameLayer(bool);
+    void canEditLayer(bool);
+    void canMeasureLayer(bool);
+    void canSelect(bool);
+    void canClearSelection(bool);
+    void canEditSelection(bool);
+    void canDeleteSelection(bool);
+    void canPaste(bool);
+    void canInsertInstant(bool);
+    void canInsertInstantsAtBoundaries(bool);
+    void canRenumberInstants(bool);
+    void canDeleteCurrentLayer(bool);
+    void canZoom(bool);
+    void canScroll(bool);
+    void canPlay(bool);
+    void canFfwd(bool);
+    void canRewind(bool);
+    void canPlaySelection(bool);
+    void canSpeedUpPlayback(bool);
+    void canSlowDownPlayback(bool);
+    void canChangePlaybackSpeed(bool);
+    void canSave(bool);
+
+public slots:
+    virtual void preferenceChanged(PropertyContainer::PropertyName);
+
+protected slots:
+    virtual void zoomIn();
+    virtual void zoomOut();
+    virtual void zoomToFit();
+    virtual void zoomDefault();
+    virtual void scrollLeft();
+    virtual void scrollRight();
+    virtual void jumpLeft();
+    virtual void jumpRight();
+
+    virtual void showNoOverlays();
+    virtual void showMinimalOverlays();
+    virtual void showStandardOverlays();
+    virtual void showAllOverlays();
+
+    virtual void toggleZoomWheels();
+    virtual void togglePropertyBoxes();
+    virtual void toggleStatusBar();
+
+    virtual void play();
+    virtual void ffwd();
+    virtual void ffwdEnd();
+    virtual void rewind();
+    virtual void rewindStart();
+    virtual void stop();
+
+    virtual void deleteCurrentPane();
+    virtual void deleteCurrentLayer();
+
+    virtual void playLoopToggled();
+    virtual void playSelectionToggled();
+    virtual void playSoloToggled();
+
+    virtual void sampleRateMismatch(size_t, size_t, bool) = 0;
+    virtual void audioOverloadPluginDisabled() = 0;
+
+    virtual void playbackFrameChanged(unsigned long);
+    virtual void globalCentreFrameChanged(unsigned long);
+    virtual void viewCentreFrameChanged(View *, unsigned long);
+    virtual void viewZoomLevelChanged(View *, unsigned long, bool);
+    virtual void outputLevelsChanged(float, float) = 0;
+
+    virtual void currentPaneChanged(Pane *);
+    virtual void currentLayerChanged(Pane *, Layer *);
+
+    virtual void selectAll();
+    virtual void selectToStart();
+    virtual void selectToEnd();
+    virtual void selectVisible();
+    virtual void clearSelection();
+
+    virtual void cut();
+    virtual void copy();
+    virtual void paste();
+    virtual void deleteSelected();
+
+    virtual void insertInstant();
+    virtual void insertInstantAt(size_t);
+    virtual void insertInstantsAtBoundaries();
+    virtual void renumberInstants();
+
+    virtual void documentModified();
+    virtual void documentRestored();
+
+    virtual void layerAdded(Layer *);
+    virtual void layerRemoved(Layer *);
+    virtual void layerAboutToBeDeleted(Layer *);
+    virtual void layerInAView(Layer *, bool);
+
+    virtual void mainModelChanged(WaveFileModel *);
+    virtual void modelAdded(Model *);
+    virtual void modelAboutToBeDeleted(Model *);
+
+    virtual void updateMenuStates();
+    virtual void updateDescriptionLabel() = 0;
+
+    virtual void modelGenerationFailed(QString) = 0;
+    virtual void modelRegenerationFailed(QString, QString) = 0;
+
+    virtual void rightButtonMenuRequested(Pane *, QPoint point) = 0;
+
+    virtual void paneAdded(Pane *) = 0;
+    virtual void paneHidden(Pane *) = 0;
+    virtual void paneAboutToBeDeleted(Pane *) = 0;
+    virtual void paneDropAccepted(Pane *, QStringList) = 0;
+    virtual void paneDropAccepted(Pane *, QString) = 0;
+
+    virtual void pollOSC();
+    virtual void handleOSCMessage(const OSCMessage &) = 0;
+
+    virtual void contextHelpChanged(const QString &);
+    virtual void inProgressSelectionChanged();
+
+    virtual void closeSession() = 0;
+
+protected:
+    QString                  m_sessionFile;
+    QString                  m_audioFile;
+    Document                *m_document;
+
+    QLabel                  *m_descriptionLabel;
+    PaneStack               *m_paneStack;
+    ViewManager             *m_viewManager;
+    Layer                   *m_timeRulerLayer;
+
+    bool                     m_audioOutput;
+    AudioCallbackPlaySource *m_playSource;
+    AudioCallbackPlayTarget *m_playTarget;
+
+    OSCQueue                *m_oscQueue;
+
+    RecentFiles              m_recentFiles;
+    RecentFiles              m_recentTransforms;
+
+    bool                     m_documentModified;
+    bool                     m_openingAudioFile;
+    bool                     m_abandoning;
+
+    Labeller                *m_labeller;
+
+    int                      m_lastPlayStatusSec;
+    mutable QString          m_myStatusMessage;
+
+    bool                     m_initialDarkBackground;
+
+    WaveFileModel *getMainModel();
+    const WaveFileModel *getMainModel() const;
+    void createDocument();
+
+    Pane *addPaneToStack();
+    Layer *getSnapLayer() const;
+
+    class PaneCallback : public SVFileReaderPaneCallback
+    {
+    public:
+	PaneCallback(MainWindowBase *mw) : m_mw(mw) { }
+	virtual Pane *addPane() { return m_mw->addPaneToStack(); }
+	virtual void setWindowSize(int width, int height) {
+	    m_mw->resize(width, height);
+	}
+	virtual void addSelection(int start, int end) {
+	    m_mw->m_viewManager->addSelection(Selection(start, end));
+	}
+    protected:
+	MainWindowBase *m_mw;
+    };
+
+    class AddPaneCommand : public Command
+    {
+    public:
+	AddPaneCommand(MainWindowBase *mw);
+	virtual ~AddPaneCommand();
+	
+	virtual void execute();
+	virtual void unexecute();
+	virtual QString getName() const;
+
+	Pane *getPane() { return m_pane; }
+
+    protected:
+	MainWindowBase *m_mw;
+	Pane *m_pane; // Main window owns this, but I determine its lifespan
+	Pane *m_prevCurrentPane; // I don't own this
+	bool m_added;
+    };
+
+    class RemovePaneCommand : public Command
+    {
+    public:
+	RemovePaneCommand(MainWindowBase *mw, Pane *pane);
+	virtual ~RemovePaneCommand();
+	
+	virtual void execute();
+	virtual void unexecute();
+	virtual QString getName() const;
+
+    protected:
+	MainWindowBase *m_mw;
+	Pane *m_pane; // Main window owns this, but I determine its lifespan
+	Pane *m_prevCurrentPane; // I don't own this
+	bool m_added;
+    };
+
+    virtual bool checkSaveModified() = 0;
+
+    virtual QString getOpenFileName(FileFinder::FileType type);
+    virtual QString getSaveFileName(FileFinder::FileType type);
+    virtual void registerLastOpenedFilePath(FileFinder::FileType type, QString path);
+
+    virtual void createPlayTarget();
+    virtual void openHelpUrl(QString url);
+
+    virtual void setupMenus() = 0;
+    virtual void updateVisibleRangeDisplay(Pane *p) const = 0;
+
+    virtual void toXml(QTextStream &stream);
+};
+
+
+#endif
--- a/main/main.cpp	Mon Oct 22 09:45:35 2007 +0000
+++ b/main/main.cpp	Mon Oct 22 14:24:31 2007 +0000
@@ -304,7 +304,7 @@
 
         if (path.endsWith("sv")) {
             if (!haveSession) {
-                status = gui.openSession(path);
+                status = gui.openSessionFile(path);
                 if (status == MainWindow::FileOpenSucceeded) {
                     haveSession = true;
                     haveMainModel = true;
--- a/sv.pro	Mon Oct 22 09:45:35 2007 +0000
+++ b/sv.pro	Mon Oct 22 14:24:31 2007 +0000
@@ -43,6 +43,7 @@
            document/Document.h \
            document/SVFileReader.h \
            main/MainWindow.h \
+           main/MainWindowBase.h \
            main/PreferencesDialog.h \
            osc/OSCMessage.h \
            osc/OSCQueue.h \
@@ -64,6 +65,7 @@
            document/SVFileReader.cpp \
            main/main.cpp \
            main/MainWindow.cpp \
+           main/MainWindowBase.cpp \
            main/PreferencesDialog.cpp \
            osc/OSCMessage.cpp \
            osc/OSCQueue.cpp \