diff main/main.cpp @ 590:7a6fd7150f2d with-dependencies

Line endings
author Chris Cannam
date Tue, 23 Jul 2013 16:12:08 +0100
parents 31940304272f
children c06a9e8e2748
line wrap: on
line diff
--- a/main/main.cpp	Mon Jul 08 14:45:12 2013 +0100
+++ b/main/main.cpp	Tue Jul 23 16:12:08 2013 +0100
@@ -1,516 +1,516 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam and QMUL.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#include "MainWindow.h"
-
-#include "system/System.h"
-#include "system/Init.h"
-#include "base/TempDirectory.h"
-#include "base/PropertyContainer.h"
-#include "base/Preferences.h"
-#include "widgets/TipDialog.h"
-#include "transform/TransformFactory.h"
-
-#include <QMetaType>
-#include <QApplication>
-#include <QDesktopWidget>
-#include <QMessageBox>
-#include <QTranslator>
-#include <QLocale>
-#include <QSettings>
-#include <QIcon>
-#include <QSessionManager>
-#include <QDir>
-#include <QSplashScreen>
-#include <QTimer>
-#include <QPainter>
-#include <QFileOpenEvent>
-
-#include "../version.h"
-
-#include <iostream>
-#include <signal.h>
-
-#ifdef HAVE_FFTW3F
-#include <fftw3.h>
-#endif
-
-/*! \mainpage Sonic Visualiser
-
-\section interesting Summary of interesting classes
-
- - Data models: Model and subclasses, e.g. WaveFileModel
-
- - Graphical layers: Layer and subclasses, displayed on View and its
- subclass widgets.
-
- - Main window class, document class, and file parser: MainWindow,
- Document, SVFileReader
-
- - Turning one model (e.g. audio) into another (e.g. more audio, or a
- curve extracted from it): Transform, encapsulating the data that need
- to be stored to be able to reproduce a given transformation;
- TransformFactory, for discovering the available types of transform;
- ModelTransformerFactory, ModelTransformer and subclasses, providing
- the mechanisms for applying transforms to data models
-
- - Creating the plugins used by transforms: RealTimePluginFactory,
- FeatureExtractionPluginFactory.  See also the API documentation for
- Vamp feature extraction plugins at
- http://www.vamp-plugins.org/code-doc/.
-
- - File reading and writing code: AudioFileReader and subclasses,
- WavFileWriter, DataFileReader, SVFileReader
-
- - FFT calculation and cacheing: FFTModel, FFTDataServer
-
- - Widgets that show groups of editable properties: PropertyBox for
- layer properties (contained in a PropertyStack), PluginParameterBox
- for plugins (contained in a PluginParameterDialog)
-
- - Audio playback: AudioCallbackPlaySource and subclasses,
- AudioCallbackPlayTarget and subclasses, AudioGenerator
-
-\section model Data sources: the Model hierarchy
-
-   A Model is something containing, or knowing how to obtain, data.
-
-   For example, WaveFileModel is a model that knows how to get data
-   from an audio file; SparseTimeValueModel is a model containing
-   editable "curve" data.
-
-   Models typically subclass one of a number of abstract subclasses of
-   Model.  For example, WaveFileModel subclasses DenseTimeValueModel,
-   which describes an interface for models that have a value at each
-   time point for a given sampling resolution.  (Note that
-   WaveFileModel does not actually read the files itself: it uses
-   AudioFileReader classes for that.  It just makes data from the
-   files available in a Model.)  SparseTimeValueModel uses the
-   SparseModel template class, which provides most of the
-   implementation for models that contain a series of points of some
-   sort -- also used by NoteModel, TextModel, and
-   SparseOneDimensionalModel.
-
-   Everything that goes on the screen originates from a model, via a
-   layer (see below).  The models are contained in a Document object.
-   There is no containment hierarchy or ordering of models in the
-   document.  One model is the main model, which defines the sample
-   rate for playback.
-
-   A model may also be marked as a "derived" model, which means it was
-   generated from another model using some transform (feature
-   extraction or effect plugin, etc) -- the idea being that they can
-   be re-generated using the same transform if a new source model is
-   loaded.
-
-\section layer Things that can display data: the Layer hierarchy
-
-   A Layer is something that knows how to draw parts of a model onto a
-   timeline.
-
-   For example, WaveformLayer is a layer which draws waveforms, based
-   on WaveFileModel; TimeValueLayer draws curves, based on
-   SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on
-   WaveFileModel (via FFTModel).
-
-   The most basic functions of a layer are: to draw itself onto a
-   Pane, against a timeline on the x axis; and to permit user
-   interaction.  If you were thinking of adding the capability to
-   display a new sort of something, then you would want to add a new
-   layer type.  (You may also need a new model type, depending on
-   whether any existing model can capture the data you need.)
-   Depending on the sort of data in question, there are various
-   existing layers that might be appropriate to start from -- for
-   example, a layer that displays images that the user has imported
-   and associated with particular times might have something in common
-   with the existing TextLayer which displays pieces of text that are
-   associated with particular times.
-
-   Although layers are visual objects, they are contained in the
-   Document in Sonic Visualiser rather than being managed together
-   with display widgets.  The Sonic Visualiser file format has
-   separate data and layout sections, and the layers are defined in
-   the data section and then referred to in the layout section which
-   determines which layers may go on which panes (see Pane below).
-
-   Once a layer class is defined, some basic data about it needs to be
-   set up in the LayerFactory class, and then it will appear in the
-   menus and so on on the main window.
-
-\section view Widgets that are used to show layers: The View hierarchy
-
-   A View is a widget that displays a stack of layers.  The most
-   important subclass is Pane, the widget that is used to show most of
-   the data in the main window of Sonic Visualiser.
-
-   All a pane really does is contain a set of layers and get them to
-   render themselves (one on top of the other, with the topmost layer
-   being the one that is currently interacted with), cache the
-   results, negotiate user interaction with them, and so on.  This is
-   generally fiddly, if not especially interesting.  Panes are
-   strictly layout objects and are not stored in the Document class;
-   instead the MainWindow contains a PaneStack widget (the widget that
-   takes up most of Sonic Visualiser's main window) which contains a
-   set of panes stacked vertically.
-
-   Another View subclass is Overview, which is the widget that
-   contains that green waveform showing the entire file at the bottom
-   of the window.
-
-*/
-
-static QMutex cleanupMutex;
-
-static void
-signalHandler(int /* signal */)
-{
-    // Avoid this happening more than once across threads
-
-    cleanupMutex.lock();
-    std::cerr << "signalHandler: cleaning up and exiting" << std::endl;
-    TempDirectory::getInstance()->cleanup();
-    exit(0); // without releasing mutex
-}
-
-class SVApplication : public QApplication
-{
-public:
-    SVApplication(int &argc, char **argv) :
-        QApplication(argc, argv),
-        m_readyForFiles(false),
-        m_filepathQueue(QStringList()),
-        m_mainWindow(0)
-    {
-#ifdef Q_OS_MAC
-        // Override the Qt plugin load path. The default contains the
-        // Qt installation location as well as the application
-        // directory, but we don't ever want to load plugins from
-        // outside the app bundle because we don't know for sure what
-        // (potentially different) versions of the Qt framework
-        // libraries they may have dyld dependencies on.
-        QString apploc(applicationFilePath());
-        apploc.truncate(apploc.lastIndexOf(QLatin1Char('/')));
-        apploc = QDir(apploc).canonicalPath();
-        if (QFile::exists(apploc)) {
-            setLibraryPaths(QStringList() << apploc);
-        } else {
-            setLibraryPaths(QStringList());
-        }
-#endif
-    }
-    virtual ~SVApplication() { }
-
-    void setMainWindow(MainWindow *mw) { m_mainWindow = mw; }
-    void releaseMainWindow() { m_mainWindow = 0; }
-
-    virtual void commitData(QSessionManager &manager) {
-        if (!m_mainWindow) return;
-        bool mayAskUser = manager.allowsInteraction();
-        bool success = m_mainWindow->commitData(mayAskUser);
-        manager.release();
-        if (!success) manager.cancel();
-    }
-
-    void handleFilepathArgument(QString path, QSplashScreen *splash);
-
-    bool m_readyForFiles;
-    QStringList m_filepathQueue;
-
-protected:
-    MainWindow *m_mainWindow;
-    bool event(QEvent *);
-
-};
-
-int
-main(int argc, char **argv)
-{
-    svSystemSpecificInitialisation();
-
-#ifdef Q_WS_X11
-#if QT_VERSION >= 0x040500
-//    QApplication::setGraphicsSystem("raster");
-#endif
-#endif
-
-    SVApplication application(argc, argv);
-
-    QStringList args = application.arguments();
-
-    signal(SIGINT,  signalHandler);
-    signal(SIGTERM, signalHandler);
-
-#ifndef Q_WS_WIN32
-//???    signal(SIGHUP,  signalHandler);
-//???    signal(SIGQUIT, signalHandler);
-#endif
-
-    bool audioOutput = true;
-    bool oscSupport = true;
-
-    if (args.contains("--help") || args.contains("-h") || args.contains("-?")) {
-        std::cerr << QApplication::tr(
-            "\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation.\n\nUsage:\n\n  %1 [--no-audio] [--no-osc] [<file> ...]\n\n  --no-audio: Do not attempt to open an audio output device\n  --no-osc: Do not provide an Open Sound Control port for remote control\n  <file>: One or more Sonic Visualiser (.sv) and audio files may be provided.\n").arg(argv[0]) << std::endl;
-        exit(2);
-    }
-
-    if (args.contains("--no-audio")) audioOutput = false;
-    if (args.contains("--no-osc")) oscSupport = false;
-
-    QApplication::setOrganizationName("sonic-visualiser");
-    QApplication::setOrganizationDomain("sonicvisualiser.org");
-    QApplication::setApplicationName(QApplication::tr("Sonic Visualiser"));
-
-    QSplashScreen *splash = 0;
-
-    QSettings settings;
-
-    settings.beginGroup("Preferences");
-    if (settings.value("show-splash", true).toBool()) {
-        QPixmap pixmap(":/icons/sv-splash.png");
-        QPainter painter;
-        painter.begin(&pixmap);
-        QString text = QString("v%1").arg(SV_VERSION);
-        painter.drawText
-            (pixmap.width() - painter.fontMetrics().width(text) - 10,
-             10 + painter.fontMetrics().ascent(),
-             text);
-        painter.end();
-        splash = new QSplashScreen(pixmap);
-        splash->show();
-        QTimer::singleShot(5000, splash, SLOT(hide()));
-        application.processEvents();
-    }
-    settings.endGroup();
-
-    settings.beginGroup("RDF");
-    if (!settings.contains("rdf-indices")) {
-        QStringList list;
-        list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
-        settings.setValue("rdf-indices", list);
-    }
-    settings.endGroup();
-
-    QIcon icon;
-    int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
-    for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
-        icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
-    }
-    QApplication::setWindowIcon(icon);
-
-    QString language = QLocale::system().name();
-
-    QTranslator qtTranslator;
-    QString qtTrName = QString("qt_%1").arg(language);
-    SVDEBUG << "Loading " << qtTrName << "... ";
-    bool success = false;
-    if (!(success = qtTranslator.load(qtTrName))) {
-        QString qtDir = getenv("QTDIR");
-        if (qtDir != "") {
-            success = qtTranslator.load
-                (qtTrName, QDir(qtDir).filePath("translations"));
-        }
-    }
-    if (!success) {
-        SVDEBUG << "Failed\nFailed to load Qt translation for locale" << endl;
-    } else {
-        std::cerr << "Done" << std::endl;
-    }
-    application.installTranslator(&qtTranslator);
-
-    QTranslator svTranslator;
-    QString svTrName = QString("sonic-visualiser_%1").arg(language);
-    SVDEBUG << "Loading " << svTrName << "... ";
-    svTranslator.load(svTrName, ":i18n");
-    SVDEBUG << "Done" << endl;
-    application.installTranslator(&svTranslator);
-
-    StoreStartupLocale();
-
-    // Permit size_t and PropertyName to be used as args in queued signal calls
-    qRegisterMetaType<size_t>("size_t");
-    qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
-
-    MainWindow *gui = new MainWindow(audioOutput, oscSupport);
-    application.setMainWindow(gui);
-    if (splash) {
-        QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide()));
-    }
-
-    QDesktopWidget *desktop = QApplication::desktop();
-    QRect available = desktop->availableGeometry();
-
-    int width = (available.width() * 2) / 3;
-    int height = available.height() / 2;
-    if (height < 450) height = (available.height() * 2) / 3;
-    if (width > height * 2) width = height * 2;
-
-    settings.beginGroup("MainWindow");
-    QSize size = settings.value("size", QSize(width, height)).toSize();
-    gui->resizeConstrained(size);
-    if (settings.contains("position")) {
-        QRect prevrect(settings.value("position").toPoint(), size);
-        if (!(available & prevrect).isEmpty()) {
-            gui->move(prevrect.topLeft());
-        }
-    }
-    settings.endGroup();
-    
-    gui->show();
-
-    // The MainWindow class seems to have trouble dealing with this if
-    // it tries to adapt to this preference before the constructor is
-    // complete.  As a lazy hack, apply it explicitly from here
-    gui->preferenceChanged("Property Box Layout");
-
-    application.m_readyForFiles = true; // Ready to receive files from e.g. Apple Events
-
-    for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
-
-        if (i == args.begin()) continue;
-        if (i->startsWith('-')) continue;
-
-        QString path = *i;
-
-        application.handleFilepathArgument(path, splash);
-    }
-    
-    for (QStringList::iterator i = application.m_filepathQueue.begin(); i != application.m_filepathQueue.end(); ++i) {
-        QString path = *i;
-        application.handleFilepathArgument(path, splash);
-    }
-    
-#ifdef HAVE_FFTW3F
-    settings.beginGroup("FFTWisdom");
-    QString wisdom = settings.value("wisdom").toString();
-    if (wisdom != "") {
-        fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data());
-    }
-#ifdef HAVE_FFTW3
-    wisdom = settings.value("wisdom_d").toString();
-    if (wisdom != "") {
-        fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
-    }
-#endif
-    settings.endGroup();
-#endif
-
-    if (splash) splash->finish(gui);
-    delete splash;
-
-/*
-    TipDialog tipDialog;
-    if (tipDialog.isOK()) {
-        tipDialog.exec();
-    }
-*/
-    int rv = application.exec();
-
-    gui->hide();
-
-    cleanupMutex.lock();
-
-    TransformFactory::deleteInstance();
-    TempDirectory::getInstance()->cleanup();
-    application.releaseMainWindow();
-
-#ifdef HAVE_FFTW3F
-    settings.beginGroup("FFTWisdom");
-    char *cwisdom = fftwf_export_wisdom_to_string();
-    if (cwisdom) {
-        settings.setValue("wisdom", cwisdom);
-        free(cwisdom);
-    }
-#ifdef HAVE_FFTW3
-    cwisdom = fftw_export_wisdom_to_string();
-    if (cwisdom) {
-        settings.setValue("wisdom_d", cwisdom);
-        free(cwisdom);
-    }
-#endif
-    settings.endGroup();
-#endif
-
-    delete gui;
-
-    cleanupMutex.unlock();
-
-    return rv;
-}
-
-bool SVApplication::event(QEvent *event){
-    QString thePath;
-    switch (event->type()) {
-    case QEvent::FileOpen:
-        thePath = static_cast<QFileOpenEvent *>(event)->file();
-        if(m_readyForFiles)
-            handleFilepathArgument(thePath, NULL);
-        else
-            m_filepathQueue.append(thePath);
-        return true;
-    default:
-        return QApplication::event(event);
-    }
-}
-
-/** Application-global handler for filepaths passed in, e.g. as command-line arguments or apple events */
-void SVApplication::handleFilepathArgument(QString path, QSplashScreen *splash){
-    static bool haveSession = false;
-    static bool haveMainModel = false;
-    static bool havePriorCommandLineModel = false;
-
-    MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
-
-    if (path.endsWith("sv")) {
-        if (!haveSession) {
-            status = m_mainWindow->openSessionFile(path);
-            if (status == MainWindow::FileOpenSucceeded) {
-                haveSession = true;
-                haveMainModel = true;
-            }
-        } else {
-            std::cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << std::endl;
-            status = MainWindow::FileOpenSucceeded;
-        }
-    }
-    if (status != MainWindow::FileOpenSucceeded) {
-        if (!haveMainModel) {
-            status = m_mainWindow->open(path, MainWindow::ReplaceSession);
-            if (status == MainWindow::FileOpenSucceeded) {
-                haveMainModel = true;
-            }
-        } else {
-            if (haveSession && !havePriorCommandLineModel) {
-                status = m_mainWindow->open(path, MainWindow::AskUser);
-                if (status == MainWindow::FileOpenSucceeded) {
-                    havePriorCommandLineModel = true;
-                }
-            } else {
-                status = m_mainWindow->open(path, MainWindow::CreateAdditionalModel);
-            }
-        }
-    }
-    if (status == MainWindow::FileOpenFailed) {
-        if (splash) splash->hide();
-        QMessageBox::critical
-            (m_mainWindow, QMessageBox::tr("Failed to open file"),
-             QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
-    } else if (status == MainWindow::FileOpenWrongMode) {
-        if (splash) splash->hide();
-        QMessageBox::critical
-            (m_mainWindow, QMessageBox::tr("Failed to open file"),
-             QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
-    }
-}
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    This file copyright 2006 Chris Cannam and QMUL.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "MainWindow.h"
+
+#include "system/System.h"
+#include "system/Init.h"
+#include "base/TempDirectory.h"
+#include "base/PropertyContainer.h"
+#include "base/Preferences.h"
+#include "widgets/TipDialog.h"
+#include "transform/TransformFactory.h"
+
+#include <QMetaType>
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QMessageBox>
+#include <QTranslator>
+#include <QLocale>
+#include <QSettings>
+#include <QIcon>
+#include <QSessionManager>
+#include <QDir>
+#include <QSplashScreen>
+#include <QTimer>
+#include <QPainter>
+#include <QFileOpenEvent>
+
+#include "../version.h"
+
+#include <iostream>
+#include <signal.h>
+
+#ifdef HAVE_FFTW3F
+#include <fftw3.h>
+#endif
+
+/*! \mainpage Sonic Visualiser
+
+\section interesting Summary of interesting classes
+
+ - Data models: Model and subclasses, e.g. WaveFileModel
+
+ - Graphical layers: Layer and subclasses, displayed on View and its
+ subclass widgets.
+
+ - Main window class, document class, and file parser: MainWindow,
+ Document, SVFileReader
+
+ - Turning one model (e.g. audio) into another (e.g. more audio, or a
+ curve extracted from it): Transform, encapsulating the data that need
+ to be stored to be able to reproduce a given transformation;
+ TransformFactory, for discovering the available types of transform;
+ ModelTransformerFactory, ModelTransformer and subclasses, providing
+ the mechanisms for applying transforms to data models
+
+ - Creating the plugins used by transforms: RealTimePluginFactory,
+ FeatureExtractionPluginFactory.  See also the API documentation for
+ Vamp feature extraction plugins at
+ http://www.vamp-plugins.org/code-doc/.
+
+ - File reading and writing code: AudioFileReader and subclasses,
+ WavFileWriter, DataFileReader, SVFileReader
+
+ - FFT calculation and cacheing: FFTModel, FFTDataServer
+
+ - Widgets that show groups of editable properties: PropertyBox for
+ layer properties (contained in a PropertyStack), PluginParameterBox
+ for plugins (contained in a PluginParameterDialog)
+
+ - Audio playback: AudioCallbackPlaySource and subclasses,
+ AudioCallbackPlayTarget and subclasses, AudioGenerator
+
+\section model Data sources: the Model hierarchy
+
+   A Model is something containing, or knowing how to obtain, data.
+
+   For example, WaveFileModel is a model that knows how to get data
+   from an audio file; SparseTimeValueModel is a model containing
+   editable "curve" data.
+
+   Models typically subclass one of a number of abstract subclasses of
+   Model.  For example, WaveFileModel subclasses DenseTimeValueModel,
+   which describes an interface for models that have a value at each
+   time point for a given sampling resolution.  (Note that
+   WaveFileModel does not actually read the files itself: it uses
+   AudioFileReader classes for that.  It just makes data from the
+   files available in a Model.)  SparseTimeValueModel uses the
+   SparseModel template class, which provides most of the
+   implementation for models that contain a series of points of some
+   sort -- also used by NoteModel, TextModel, and
+   SparseOneDimensionalModel.
+
+   Everything that goes on the screen originates from a model, via a
+   layer (see below).  The models are contained in a Document object.
+   There is no containment hierarchy or ordering of models in the
+   document.  One model is the main model, which defines the sample
+   rate for playback.
+
+   A model may also be marked as a "derived" model, which means it was
+   generated from another model using some transform (feature
+   extraction or effect plugin, etc) -- the idea being that they can
+   be re-generated using the same transform if a new source model is
+   loaded.
+
+\section layer Things that can display data: the Layer hierarchy
+
+   A Layer is something that knows how to draw parts of a model onto a
+   timeline.
+
+   For example, WaveformLayer is a layer which draws waveforms, based
+   on WaveFileModel; TimeValueLayer draws curves, based on
+   SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on
+   WaveFileModel (via FFTModel).
+
+   The most basic functions of a layer are: to draw itself onto a
+   Pane, against a timeline on the x axis; and to permit user
+   interaction.  If you were thinking of adding the capability to
+   display a new sort of something, then you would want to add a new
+   layer type.  (You may also need a new model type, depending on
+   whether any existing model can capture the data you need.)
+   Depending on the sort of data in question, there are various
+   existing layers that might be appropriate to start from -- for
+   example, a layer that displays images that the user has imported
+   and associated with particular times might have something in common
+   with the existing TextLayer which displays pieces of text that are
+   associated with particular times.
+
+   Although layers are visual objects, they are contained in the
+   Document in Sonic Visualiser rather than being managed together
+   with display widgets.  The Sonic Visualiser file format has
+   separate data and layout sections, and the layers are defined in
+   the data section and then referred to in the layout section which
+   determines which layers may go on which panes (see Pane below).
+
+   Once a layer class is defined, some basic data about it needs to be
+   set up in the LayerFactory class, and then it will appear in the
+   menus and so on on the main window.
+
+\section view Widgets that are used to show layers: The View hierarchy
+
+   A View is a widget that displays a stack of layers.  The most
+   important subclass is Pane, the widget that is used to show most of
+   the data in the main window of Sonic Visualiser.
+
+   All a pane really does is contain a set of layers and get them to
+   render themselves (one on top of the other, with the topmost layer
+   being the one that is currently interacted with), cache the
+   results, negotiate user interaction with them, and so on.  This is
+   generally fiddly, if not especially interesting.  Panes are
+   strictly layout objects and are not stored in the Document class;
+   instead the MainWindow contains a PaneStack widget (the widget that
+   takes up most of Sonic Visualiser's main window) which contains a
+   set of panes stacked vertically.
+
+   Another View subclass is Overview, which is the widget that
+   contains that green waveform showing the entire file at the bottom
+   of the window.
+
+*/
+
+static QMutex cleanupMutex;
+
+static void
+signalHandler(int /* signal */)
+{
+    // Avoid this happening more than once across threads
+
+    cleanupMutex.lock();
+    std::cerr << "signalHandler: cleaning up and exiting" << std::endl;
+    TempDirectory::getInstance()->cleanup();
+    exit(0); // without releasing mutex
+}
+
+class SVApplication : public QApplication
+{
+public:
+    SVApplication(int &argc, char **argv) :
+        QApplication(argc, argv),
+        m_readyForFiles(false),
+        m_filepathQueue(QStringList()),
+        m_mainWindow(0)
+    {
+#ifdef Q_OS_MAC
+        // Override the Qt plugin load path. The default contains the
+        // Qt installation location as well as the application
+        // directory, but we don't ever want to load plugins from
+        // outside the app bundle because we don't know for sure what
+        // (potentially different) versions of the Qt framework
+        // libraries they may have dyld dependencies on.
+        QString apploc(applicationFilePath());
+        apploc.truncate(apploc.lastIndexOf(QLatin1Char('/')));
+        apploc = QDir(apploc).canonicalPath();
+        if (QFile::exists(apploc)) {
+            setLibraryPaths(QStringList() << apploc);
+        } else {
+            setLibraryPaths(QStringList());
+        }
+#endif
+    }
+    virtual ~SVApplication() { }
+
+    void setMainWindow(MainWindow *mw) { m_mainWindow = mw; }
+    void releaseMainWindow() { m_mainWindow = 0; }
+
+    virtual void commitData(QSessionManager &manager) {
+        if (!m_mainWindow) return;
+        bool mayAskUser = manager.allowsInteraction();
+        bool success = m_mainWindow->commitData(mayAskUser);
+        manager.release();
+        if (!success) manager.cancel();
+    }
+
+    void handleFilepathArgument(QString path, QSplashScreen *splash);
+
+    bool m_readyForFiles;
+    QStringList m_filepathQueue;
+
+protected:
+    MainWindow *m_mainWindow;
+    bool event(QEvent *);
+
+};
+
+int
+main(int argc, char **argv)
+{
+    svSystemSpecificInitialisation();
+
+#ifdef Q_WS_X11
+#if QT_VERSION >= 0x040500
+//    QApplication::setGraphicsSystem("raster");
+#endif
+#endif
+
+    SVApplication application(argc, argv);
+
+    QStringList args = application.arguments();
+
+    signal(SIGINT,  signalHandler);
+    signal(SIGTERM, signalHandler);
+
+#ifndef Q_WS_WIN32
+//???    signal(SIGHUP,  signalHandler);
+//???    signal(SIGQUIT, signalHandler);
+#endif
+
+    bool audioOutput = true;
+    bool oscSupport = true;
+
+    if (args.contains("--help") || args.contains("-h") || args.contains("-?")) {
+        std::cerr << QApplication::tr(
+            "\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation.\n\nUsage:\n\n  %1 [--no-audio] [--no-osc] [<file> ...]\n\n  --no-audio: Do not attempt to open an audio output device\n  --no-osc: Do not provide an Open Sound Control port for remote control\n  <file>: One or more Sonic Visualiser (.sv) and audio files may be provided.\n").arg(argv[0]) << std::endl;
+        exit(2);
+    }
+
+    if (args.contains("--no-audio")) audioOutput = false;
+    if (args.contains("--no-osc")) oscSupport = false;
+
+    QApplication::setOrganizationName("sonic-visualiser");
+    QApplication::setOrganizationDomain("sonicvisualiser.org");
+    QApplication::setApplicationName(QApplication::tr("Sonic Visualiser"));
+
+    QSplashScreen *splash = 0;
+
+    QSettings settings;
+
+    settings.beginGroup("Preferences");
+    if (settings.value("show-splash", true).toBool()) {
+        QPixmap pixmap(":/icons/sv-splash.png");
+        QPainter painter;
+        painter.begin(&pixmap);
+        QString text = QString("v%1").arg(SV_VERSION);
+        painter.drawText
+            (pixmap.width() - painter.fontMetrics().width(text) - 10,
+             10 + painter.fontMetrics().ascent(),
+             text);
+        painter.end();
+        splash = new QSplashScreen(pixmap);
+        splash->show();
+        QTimer::singleShot(5000, splash, SLOT(hide()));
+        application.processEvents();
+    }
+    settings.endGroup();
+
+    settings.beginGroup("RDF");
+    if (!settings.contains("rdf-indices")) {
+        QStringList list;
+        list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
+        settings.setValue("rdf-indices", list);
+    }
+    settings.endGroup();
+
+    QIcon icon;
+    int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
+    for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
+        icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
+    }
+    QApplication::setWindowIcon(icon);
+
+    QString language = QLocale::system().name();
+
+    QTranslator qtTranslator;
+    QString qtTrName = QString("qt_%1").arg(language);
+    SVDEBUG << "Loading " << qtTrName << "... ";
+    bool success = false;
+    if (!(success = qtTranslator.load(qtTrName))) {
+        QString qtDir = getenv("QTDIR");
+        if (qtDir != "") {
+            success = qtTranslator.load
+                (qtTrName, QDir(qtDir).filePath("translations"));
+        }
+    }
+    if (!success) {
+        SVDEBUG << "Failed\nFailed to load Qt translation for locale" << endl;
+    } else {
+        std::cerr << "Done" << std::endl;
+    }
+    application.installTranslator(&qtTranslator);
+
+    QTranslator svTranslator;
+    QString svTrName = QString("sonic-visualiser_%1").arg(language);
+    SVDEBUG << "Loading " << svTrName << "... ";
+    svTranslator.load(svTrName, ":i18n");
+    SVDEBUG << "Done" << endl;
+    application.installTranslator(&svTranslator);
+
+    StoreStartupLocale();
+
+    // Permit size_t and PropertyName to be used as args in queued signal calls
+    qRegisterMetaType<size_t>("size_t");
+    qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
+
+    MainWindow *gui = new MainWindow(audioOutput, oscSupport);
+    application.setMainWindow(gui);
+    if (splash) {
+        QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide()));
+    }
+
+    QDesktopWidget *desktop = QApplication::desktop();
+    QRect available = desktop->availableGeometry();
+
+    int width = (available.width() * 2) / 3;
+    int height = available.height() / 2;
+    if (height < 450) height = (available.height() * 2) / 3;
+    if (width > height * 2) width = height * 2;
+
+    settings.beginGroup("MainWindow");
+    QSize size = settings.value("size", QSize(width, height)).toSize();
+    gui->resizeConstrained(size);
+    if (settings.contains("position")) {
+        QRect prevrect(settings.value("position").toPoint(), size);
+        if (!(available & prevrect).isEmpty()) {
+            gui->move(prevrect.topLeft());
+        }
+    }
+    settings.endGroup();
+    
+    gui->show();
+
+    // The MainWindow class seems to have trouble dealing with this if
+    // it tries to adapt to this preference before the constructor is
+    // complete.  As a lazy hack, apply it explicitly from here
+    gui->preferenceChanged("Property Box Layout");
+
+    application.m_readyForFiles = true; // Ready to receive files from e.g. Apple Events
+
+    for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
+
+        if (i == args.begin()) continue;
+        if (i->startsWith('-')) continue;
+
+        QString path = *i;
+
+        application.handleFilepathArgument(path, splash);
+    }
+    
+    for (QStringList::iterator i = application.m_filepathQueue.begin(); i != application.m_filepathQueue.end(); ++i) {
+        QString path = *i;
+        application.handleFilepathArgument(path, splash);
+    }
+    
+#ifdef HAVE_FFTW3F
+    settings.beginGroup("FFTWisdom");
+    QString wisdom = settings.value("wisdom").toString();
+    if (wisdom != "") {
+        fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data());
+    }
+#ifdef HAVE_FFTW3
+    wisdom = settings.value("wisdom_d").toString();
+    if (wisdom != "") {
+        fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
+    }
+#endif
+    settings.endGroup();
+#endif
+
+    if (splash) splash->finish(gui);
+    delete splash;
+
+/*
+    TipDialog tipDialog;
+    if (tipDialog.isOK()) {
+        tipDialog.exec();
+    }
+*/
+    int rv = application.exec();
+
+    gui->hide();
+
+    cleanupMutex.lock();
+
+    TransformFactory::deleteInstance();
+    TempDirectory::getInstance()->cleanup();
+    application.releaseMainWindow();
+
+#ifdef HAVE_FFTW3F
+    settings.beginGroup("FFTWisdom");
+    char *cwisdom = fftwf_export_wisdom_to_string();
+    if (cwisdom) {
+        settings.setValue("wisdom", cwisdom);
+        free(cwisdom);
+    }
+#ifdef HAVE_FFTW3
+    cwisdom = fftw_export_wisdom_to_string();
+    if (cwisdom) {
+        settings.setValue("wisdom_d", cwisdom);
+        free(cwisdom);
+    }
+#endif
+    settings.endGroup();
+#endif
+
+    delete gui;
+
+    cleanupMutex.unlock();
+
+    return rv;
+}
+
+bool SVApplication::event(QEvent *event){
+    QString thePath;
+    switch (event->type()) {
+    case QEvent::FileOpen:
+        thePath = static_cast<QFileOpenEvent *>(event)->file();
+        if(m_readyForFiles)
+            handleFilepathArgument(thePath, NULL);
+        else
+            m_filepathQueue.append(thePath);
+        return true;
+    default:
+        return QApplication::event(event);
+    }
+}
+
+/** Application-global handler for filepaths passed in, e.g. as command-line arguments or apple events */
+void SVApplication::handleFilepathArgument(QString path, QSplashScreen *splash){
+    static bool haveSession = false;
+    static bool haveMainModel = false;
+    static bool havePriorCommandLineModel = false;
+
+    MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
+
+    if (path.endsWith("sv")) {
+        if (!haveSession) {
+            status = m_mainWindow->openSessionFile(path);
+            if (status == MainWindow::FileOpenSucceeded) {
+                haveSession = true;
+                haveMainModel = true;
+            }
+        } else {
+            std::cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << std::endl;
+            status = MainWindow::FileOpenSucceeded;
+        }
+    }
+    if (status != MainWindow::FileOpenSucceeded) {
+        if (!haveMainModel) {
+            status = m_mainWindow->open(path, MainWindow::ReplaceSession);
+            if (status == MainWindow::FileOpenSucceeded) {
+                haveMainModel = true;
+            }
+        } else {
+            if (haveSession && !havePriorCommandLineModel) {
+                status = m_mainWindow->open(path, MainWindow::AskUser);
+                if (status == MainWindow::FileOpenSucceeded) {
+                    havePriorCommandLineModel = true;
+                }
+            } else {
+                status = m_mainWindow->open(path, MainWindow::CreateAdditionalModel);
+            }
+        }
+    }
+    if (status == MainWindow::FileOpenFailed) {
+        if (splash) splash->hide();
+        QMessageBox::critical
+            (m_mainWindow, QMessageBox::tr("Failed to open file"),
+             QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
+    } else if (status == MainWindow::FileOpenWrongMode) {
+        if (splash) splash->hide();
+        QMessageBox::critical
+            (m_mainWindow, QMessageBox::tr("Failed to open file"),
+             QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
+    }
+}