| Chris@0 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@0 | 2 | 
| Chris@0 | 3 /* | 
| Chris@0 | 4     Sonic Visualiser | 
| Chris@0 | 5     An audio file viewer and annotation editor. | 
| Chris@0 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@77 | 7     This file copyright 2006 Chris Cannam and QMUL. | 
| Chris@0 | 8 | 
| Chris@0 | 9     This program is free software; you can redistribute it and/or | 
| Chris@0 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@0 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@0 | 12     License, or (at your option) any later version.  See the file | 
| Chris@0 | 13     COPYING included with this distribution for more information. | 
| Chris@0 | 14 */ | 
| Chris@0 | 15 | 
| Chris@0 | 16 #include "MainWindow.h" | 
| Chris@953 | 17 #include "SVSplash.h" | 
| Chris@0 | 18 | 
| Chris@1 | 19 #include "system/System.h" | 
| Chris@1 | 20 #include "system/Init.h" | 
| Chris@0 | 21 #include "base/TempDirectory.h" | 
| Chris@0 | 22 #include "base/PropertyContainer.h" | 
| Chris@0 | 23 #include "base/Preferences.h" | 
| Chris@908 | 24 #include "data/fileio/FileSource.h" | 
| Chris@120 | 25 #include "widgets/TipDialog.h" | 
| Chris@763 | 26 #include "widgets/InteractiveFileFinder.h" | 
| Chris@763 | 27 #include "svapp/framework/TransformUserConfigurator.h" | 
| Chris@315 | 28 #include "transform/TransformFactory.h" | 
| Chris@0 | 29 | 
| Chris@0 | 30 #include <QMetaType> | 
| Chris@0 | 31 #include <QApplication> | 
| Chris@0 | 32 #include <QDesktopWidget> | 
| Chris@0 | 33 #include <QMessageBox> | 
| Chris@0 | 34 #include <QTranslator> | 
| Chris@0 | 35 #include <QLocale> | 
| Chris@5 | 36 #include <QSettings> | 
| Chris@7 | 37 #include <QIcon> | 
| Chris@11 | 38 #include <QSessionManager> | 
| Chris@165 | 39 #include <QDir> | 
| Chris@252 | 40 #include <QTimer> | 
| Chris@331 | 41 #include <QPainter> | 
| dan@365 | 42 #include <QFileOpenEvent> | 
| Chris@331 | 43 | 
| Chris@0 | 44 #include <iostream> | 
| Chris@0 | 45 #include <signal.h> | 
| Chris@0 | 46 | 
| Chris@215 | 47 #ifdef HAVE_FFTW3F | 
| Chris@215 | 48 #include <fftw3.h> | 
| Chris@215 | 49 #endif | 
| Chris@215 | 50 | 
| Chris@127 | 51 /*! \mainpage Sonic Visualiser | 
| Chris@127 | 52 | 
| Chris@127 | 53 \section interesting Summary of interesting classes | 
| Chris@127 | 54 | 
| Chris@127 | 55  - Data models: Model and subclasses, e.g. WaveFileModel | 
| Chris@127 | 56 | 
| Chris@127 | 57  - Graphical layers: Layer and subclasses, displayed on View and its | 
| Chris@127 | 58  subclass widgets. | 
| Chris@127 | 59 | 
| Chris@127 | 60  - Main window class, document class, and file parser: MainWindow, | 
| Chris@127 | 61  Document, SVFileReader | 
| Chris@127 | 62 | 
| Chris@127 | 63  - Turning one model (e.g. audio) into another (e.g. more audio, or a | 
| Chris@244 | 64  curve extracted from it): Transform, encapsulating the data that need | 
| Chris@244 | 65  to be stored to be able to reproduce a given transformation; | 
| Chris@244 | 66  TransformFactory, for discovering the available types of transform; | 
| Chris@244 | 67  ModelTransformerFactory, ModelTransformer and subclasses, providing | 
| Chris@244 | 68  the mechanisms for applying transforms to data models | 
| Chris@127 | 69 | 
| Chris@127 | 70  - Creating the plugins used by transforms: RealTimePluginFactory, | 
| Chris@129 | 71  FeatureExtractionPluginFactory.  See also the API documentation for | 
| Chris@129 | 72  Vamp feature extraction plugins at | 
| Chris@129 | 73  http://www.vamp-plugins.org/code-doc/. | 
| Chris@127 | 74 | 
| Chris@127 | 75  - File reading and writing code: AudioFileReader and subclasses, | 
| Chris@127 | 76  WavFileWriter, DataFileReader, SVFileReader | 
| Chris@127 | 77 | 
| Chris@127 | 78  - FFT calculation and cacheing: FFTModel, FFTDataServer | 
| Chris@127 | 79 | 
| Chris@127 | 80  - Widgets that show groups of editable properties: PropertyBox for | 
| Chris@127 | 81  layer properties (contained in a PropertyStack), PluginParameterBox | 
| Chris@127 | 82  for plugins (contained in a PluginParameterDialog) | 
| Chris@127 | 83 | 
| Chris@127 | 84  - Audio playback: AudioCallbackPlaySource and subclasses, | 
| Chris@127 | 85  AudioCallbackPlayTarget and subclasses, AudioGenerator | 
| Chris@127 | 86 | 
| Chris@127 | 87 \section model Data sources: the Model hierarchy | 
| Chris@127 | 88 | 
| Chris@127 | 89    A Model is something containing, or knowing how to obtain, data. | 
| Chris@127 | 90 | 
| Chris@127 | 91    For example, WaveFileModel is a model that knows how to get data | 
| Chris@127 | 92    from an audio file; SparseTimeValueModel is a model containing | 
| Chris@127 | 93    editable "curve" data. | 
| Chris@127 | 94 | 
| Chris@127 | 95    Models typically subclass one of a number of abstract subclasses of | 
| Chris@127 | 96    Model.  For example, WaveFileModel subclasses DenseTimeValueModel, | 
| Chris@127 | 97    which describes an interface for models that have a value at each | 
| Chris@127 | 98    time point for a given sampling resolution.  (Note that | 
| Chris@127 | 99    WaveFileModel does not actually read the files itself: it uses | 
| Chris@127 | 100    AudioFileReader classes for that.  It just makes data from the | 
| Chris@127 | 101    files available in a Model.)  SparseTimeValueModel uses the | 
| Chris@127 | 102    SparseModel template class, which provides most of the | 
| Chris@127 | 103    implementation for models that contain a series of points of some | 
| Chris@127 | 104    sort -- also used by NoteModel, TextModel, and | 
| Chris@127 | 105    SparseOneDimensionalModel. | 
| Chris@127 | 106 | 
| Chris@127 | 107    Everything that goes on the screen originates from a model, via a | 
| Chris@127 | 108    layer (see below).  The models are contained in a Document object. | 
| Chris@127 | 109    There is no containment hierarchy or ordering of models in the | 
| Chris@127 | 110    document.  One model is the main model, which defines the sample | 
| Chris@127 | 111    rate for playback. | 
| Chris@127 | 112 | 
| Chris@127 | 113    A model may also be marked as a "derived" model, which means it was | 
| Chris@127 | 114    generated from another model using some transform (feature | 
| Chris@127 | 115    extraction or effect plugin, etc) -- the idea being that they can | 
| Chris@127 | 116    be re-generated using the same transform if a new source model is | 
| Chris@127 | 117    loaded. | 
| Chris@127 | 118 | 
| Chris@127 | 119 \section layer Things that can display data: the Layer hierarchy | 
| Chris@127 | 120 | 
| Chris@127 | 121    A Layer is something that knows how to draw parts of a model onto a | 
| Chris@127 | 122    timeline. | 
| Chris@127 | 123 | 
| Chris@127 | 124    For example, WaveformLayer is a layer which draws waveforms, based | 
| Chris@127 | 125    on WaveFileModel; TimeValueLayer draws curves, based on | 
| Chris@127 | 126    SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on | 
| Chris@127 | 127    WaveFileModel (via FFTModel). | 
| Chris@127 | 128 | 
| Chris@127 | 129    The most basic functions of a layer are: to draw itself onto a | 
| Chris@127 | 130    Pane, against a timeline on the x axis; and to permit user | 
| Chris@127 | 131    interaction.  If you were thinking of adding the capability to | 
| Chris@127 | 132    display a new sort of something, then you would want to add a new | 
| Chris@127 | 133    layer type.  (You may also need a new model type, depending on | 
| Chris@127 | 134    whether any existing model can capture the data you need.) | 
| Chris@127 | 135    Depending on the sort of data in question, there are various | 
| Chris@127 | 136    existing layers that might be appropriate to start from -- for | 
| Chris@127 | 137    example, a layer that displays images that the user has imported | 
| Chris@127 | 138    and associated with particular times might have something in common | 
| Chris@127 | 139    with the existing TextLayer which displays pieces of text that are | 
| Chris@127 | 140    associated with particular times. | 
| Chris@127 | 141 | 
| Chris@127 | 142    Although layers are visual objects, they are contained in the | 
| Chris@127 | 143    Document in Sonic Visualiser rather than being managed together | 
| Chris@127 | 144    with display widgets.  The Sonic Visualiser file format has | 
| Chris@127 | 145    separate data and layout sections, and the layers are defined in | 
| Chris@127 | 146    the data section and then referred to in the layout section which | 
| Chris@127 | 147    determines which layers may go on which panes (see Pane below). | 
| Chris@127 | 148 | 
| Chris@127 | 149    Once a layer class is defined, some basic data about it needs to be | 
| Chris@127 | 150    set up in the LayerFactory class, and then it will appear in the | 
| Chris@127 | 151    menus and so on on the main window. | 
| Chris@127 | 152 | 
| Chris@127 | 153 \section view Widgets that are used to show layers: The View hierarchy | 
| Chris@127 | 154 | 
| Chris@127 | 155    A View is a widget that displays a stack of layers.  The most | 
| Chris@127 | 156    important subclass is Pane, the widget that is used to show most of | 
| Chris@127 | 157    the data in the main window of Sonic Visualiser. | 
| Chris@127 | 158 | 
| Chris@127 | 159    All a pane really does is contain a set of layers and get them to | 
| Chris@127 | 160    render themselves (one on top of the other, with the topmost layer | 
| Chris@127 | 161    being the one that is currently interacted with), cache the | 
| Chris@127 | 162    results, negotiate user interaction with them, and so on.  This is | 
| Chris@127 | 163    generally fiddly, if not especially interesting.  Panes are | 
| Chris@127 | 164    strictly layout objects and are not stored in the Document class; | 
| Chris@127 | 165    instead the MainWindow contains a PaneStack widget (the widget that | 
| Chris@127 | 166    takes up most of Sonic Visualiser's main window) which contains a | 
| Chris@127 | 167    set of panes stacked vertically. | 
| Chris@127 | 168 | 
| Chris@127 | 169    Another View subclass is Overview, which is the widget that | 
| Chris@127 | 170    contains that green waveform showing the entire file at the bottom | 
| Chris@127 | 171    of the window. | 
| Chris@127 | 172 | 
| Chris@127 | 173 */ | 
| Chris@127 | 174 | 
| Chris@0 | 175 static QMutex cleanupMutex; | 
| Chris@589 | 176 static bool cleanedUp = false; | 
| Chris@0 | 177 | 
| Chris@0 | 178 static void | 
| Chris@0 | 179 signalHandler(int /* signal */) | 
| Chris@0 | 180 { | 
| Chris@0 | 181     // Avoid this happening more than once across threads | 
| Chris@0 | 182 | 
| Chris@665 | 183     cerr << "signalHandler: cleaning up and exiting" << endl; | 
| Chris@0 | 184     cleanupMutex.lock(); | 
| Chris@589 | 185     if (!cleanedUp) { | 
| Chris@589 | 186         TempDirectory::getInstance()->cleanup(); | 
| Chris@589 | 187         cleanedUp = true; | 
| Chris@589 | 188     } | 
| Chris@589 | 189     cleanupMutex.unlock(); | 
| Chris@589 | 190     exit(0); | 
| Chris@0 | 191 } | 
| Chris@0 | 192 | 
| Chris@11 | 193 class SVApplication : public QApplication | 
| Chris@11 | 194 { | 
| Chris@11 | 195 public: | 
| Chris@296 | 196     SVApplication(int &argc, char **argv) : | 
| Chris@11 | 197         QApplication(argc, argv), | 
| dan@365 | 198         m_readyForFiles(false), | 
| dan@365 | 199         m_filepathQueue(QStringList()), | 
| dan@365 | 200         m_mainWindow(0) | 
| Chris@509 | 201     { | 
| Chris@509 | 202     } | 
| Chris@11 | 203     virtual ~SVApplication() { } | 
| Chris@11 | 204 | 
| Chris@11 | 205     void setMainWindow(MainWindow *mw) { m_mainWindow = mw; } | 
| Chris@11 | 206     void releaseMainWindow() { m_mainWindow = 0; } | 
| Chris@11 | 207 | 
| Chris@11 | 208     virtual void commitData(QSessionManager &manager) { | 
| Chris@11 | 209         if (!m_mainWindow) return; | 
| Chris@11 | 210         bool mayAskUser = manager.allowsInteraction(); | 
| Chris@11 | 211         bool success = m_mainWindow->commitData(mayAskUser); | 
| Chris@11 | 212         manager.release(); | 
| Chris@11 | 213         if (!success) manager.cancel(); | 
| Chris@11 | 214     } | 
| Chris@11 | 215 | 
| Chris@953 | 216     void handleFilepathArgument(QString path, SVSplash *splash); | 
| dan@362 | 217 | 
| dan@365 | 218     bool m_readyForFiles; | 
| dan@365 | 219     QStringList m_filepathQueue; | 
| dan@362 | 220 | 
| Chris@11 | 221 protected: | 
| Chris@11 | 222     MainWindow *m_mainWindow; | 
| dan@365 | 223     bool event(QEvent *); | 
| Chris@11 | 224 }; | 
| Chris@11 | 225 | 
| Chris@0 | 226 int | 
| Chris@0 | 227 main(int argc, char **argv) | 
| Chris@0 | 228 { | 
| Chris@376 | 229     svSystemSpecificInitialisation(); | 
| Chris@376 | 230 | 
| Chris@316 | 231 #ifdef Q_WS_X11 | 
| Chris@317 | 232 #if QT_VERSION >= 0x040500 | 
| Chris@342 | 233 //    QApplication::setGraphicsSystem("raster"); | 
| Chris@316 | 234 #endif | 
| Chris@316 | 235 #endif | 
| Chris@316 | 236 | 
| Chris@678 | 237 #ifdef Q_OS_MAC | 
| Chris@678 | 238     if (QSysInfo::MacintoshVersion > QSysInfo::MV_10_8) { | 
| Chris@678 | 239         // Fix for OS/X 10.9 font problem | 
| Chris@678 | 240         QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande"); | 
| Chris@678 | 241     } | 
| Chris@678 | 242 #endif | 
| Chris@678 | 243 | 
| Chris@11 | 244     SVApplication application(argc, argv); | 
| Chris@0 | 245 | 
| Chris@46 | 246     QStringList args = application.arguments(); | 
| Chris@46 | 247 | 
| Chris@0 | 248     signal(SIGINT,  signalHandler); | 
| Chris@0 | 249     signal(SIGTERM, signalHandler); | 
| Chris@0 | 250 | 
| Chris@640 | 251 #ifndef Q_OS_WIN32 | 
| Chris@0 | 252     signal(SIGHUP,  signalHandler); | 
| Chris@0 | 253     signal(SIGQUIT, signalHandler); | 
| Chris@0 | 254 #endif | 
| Chris@0 | 255 | 
| Chris@46 | 256     bool audioOutput = true; | 
| Chris@70 | 257     bool oscSupport = true; | 
| Chris@70 | 258 | 
| Chris@133 | 259     if (args.contains("--help") || args.contains("-h") || args.contains("-?")) { | 
| Chris@665 | 260         cerr << QApplication::tr( | 
| Chris@665 | 261             "\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]) << endl; | 
| Chris@70 | 262         exit(2); | 
| Chris@70 | 263     } | 
| Chris@70 | 264 | 
| Chris@46 | 265     if (args.contains("--no-audio")) audioOutput = false; | 
| Chris@70 | 266     if (args.contains("--no-osc")) oscSupport = false; | 
| Chris@46 | 267 | 
| Chris@6 | 268     QApplication::setOrganizationName("sonic-visualiser"); | 
| Chris@5 | 269     QApplication::setOrganizationDomain("sonicvisualiser.org"); | 
| Chris@213 | 270     QApplication::setApplicationName(QApplication::tr("Sonic Visualiser")); | 
| Chris@141 | 271 | 
| Chris@952 | 272     QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); | 
| Chris@952 | 273 | 
| Chris@953 | 274     SVSplash *splash = 0; | 
| Chris@231 | 275 | 
| Chris@231 | 276     QSettings settings; | 
| Chris@237 | 277 | 
| Chris@237 | 278     settings.beginGroup("Preferences"); | 
| Chris@237 | 279     if (settings.value("show-splash", true).toBool()) { | 
| Chris@953 | 280         splash = new SVSplash(); | 
| Chris@283 | 281         splash->show(); | 
| Chris@283 | 282         QTimer::singleShot(5000, splash, SLOT(hide())); | 
| Chris@231 | 283         application.processEvents(); | 
| Chris@231 | 284     } | 
| Chris@237 | 285     settings.endGroup(); | 
| Chris@231 | 286 | 
| Chris@278 | 287     settings.beginGroup("RDF"); | 
| Chris@278 | 288     if (!settings.contains("rdf-indices")) { | 
| Chris@278 | 289         QStringList list; | 
| Chris@278 | 290         list << "http://www.vamp-plugins.org/rdf/plugins/index.txt"; | 
| Chris@278 | 291         settings.setValue("rdf-indices", list); | 
| Chris@278 | 292     } | 
| Chris@278 | 293     settings.endGroup(); | 
| Chris@278 | 294 | 
| Chris@141 | 295     QIcon icon; | 
| Chris@141 | 296     int sizes[] = { 16, 22, 24, 32, 48, 64, 128 }; | 
| Chris@730 | 297     for (int i = 0; i < int(sizeof(sizes)/sizeof(sizes[0])); ++i) { | 
| Chris@141 | 298         icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i])); | 
| Chris@141 | 299     } | 
| Chris@141 | 300     QApplication::setWindowIcon(icon); | 
| Chris@7 | 301 | 
| Chris@0 | 302     QString language = QLocale::system().name(); | 
| Chris@0 | 303 | 
| Chris@658 | 304     settings.beginGroup("Preferences"); | 
| Chris@658 | 305     language = settings.value("locale", language).toString(); | 
| Chris@658 | 306     settings.endGroup(); | 
| Chris@658 | 307 | 
| Chris@0 | 308     QTranslator qtTranslator; | 
| Chris@0 | 309     QString qtTrName = QString("qt_%1").arg(language); | 
| Chris@438 | 310     SVDEBUG << "Loading " << qtTrName << "... "; | 
| Chris@165 | 311     bool success = false; | 
| Chris@165 | 312     if (!(success = qtTranslator.load(qtTrName))) { | 
| Chris@165 | 313         QString qtDir = getenv("QTDIR"); | 
| Chris@165 | 314         if (qtDir != "") { | 
| Chris@165 | 315             success = qtTranslator.load | 
| Chris@165 | 316                 (qtTrName, QDir(qtDir).filePath("translations")); | 
| Chris@165 | 317         } | 
| Chris@165 | 318     } | 
| Chris@165 | 319     if (!success) { | 
| Chris@438 | 320         SVDEBUG << "Failed\nFailed to load Qt translation for locale" << endl; | 
| Chris@253 | 321     } else { | 
| Chris@665 | 322         cerr << "Done" << endl; | 
| Chris@165 | 323     } | 
| Chris@0 | 324     application.installTranslator(&qtTranslator); | 
| Chris@0 | 325 | 
| Chris@0 | 326     QTranslator svTranslator; | 
| Chris@0 | 327     QString svTrName = QString("sonic-visualiser_%1").arg(language); | 
| Chris@438 | 328     SVDEBUG << "Loading " << svTrName << "... "; | 
| Chris@0 | 329     svTranslator.load(svTrName, ":i18n"); | 
| Chris@438 | 330     SVDEBUG << "Done" << endl; | 
| Chris@0 | 331     application.installTranslator(&svTranslator); | 
| Chris@0 | 332 | 
| Chris@187 | 333     StoreStartupLocale(); | 
| Chris@187 | 334 | 
| Chris@0 | 335     // Permit size_t and PropertyName to be used as args in queued signal calls | 
| Chris@0 | 336     qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName"); | 
| Chris@0 | 337 | 
| dan@365 | 338     MainWindow *gui = new MainWindow(audioOutput, oscSupport); | 
| Chris@222 | 339     application.setMainWindow(gui); | 
| Chris@763 | 340     InteractiveFileFinder::setParentWidget(gui); | 
| Chris@763 | 341     TransformUserConfigurator::setParentWidget(gui); | 
| Chris@283 | 342     if (splash) { | 
| Chris@283 | 343         QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide())); | 
| Chris@953 | 344         QObject::connect(gui, SIGNAL(hideSplash(QWidget *)), | 
| Chris@953 | 345                          splash, SLOT(finishSplash(QWidget *))); | 
| Chris@283 | 346     } | 
| Chris@0 | 347 | 
| Chris@0 | 348     QDesktopWidget *desktop = QApplication::desktop(); | 
| Chris@0 | 349     QRect available = desktop->availableGeometry(); | 
| Chris@0 | 350 | 
| Chris@378 | 351     int width = (available.width() * 2) / 3; | 
| Chris@0 | 352     int height = available.height() / 2; | 
| Chris@378 | 353     if (height < 450) height = (available.height() * 2) / 3; | 
| Chris@0 | 354     if (width > height * 2) width = height * 2; | 
| Chris@0 | 355 | 
| Chris@237 | 356     settings.beginGroup("MainWindow"); | 
| Chris@624 | 357 | 
| Chris@5 | 358     QSize size = settings.value("size", QSize(width, height)).toSize(); | 
| Chris@319 | 359     gui->resizeConstrained(size); | 
| Chris@624 | 360 | 
| Chris@5 | 361     if (settings.contains("position")) { | 
| Chris@297 | 362         QRect prevrect(settings.value("position").toPoint(), size); | 
| Chris@297 | 363         if (!(available & prevrect).isEmpty()) { | 
| Chris@297 | 364             gui->move(prevrect.topLeft()); | 
| Chris@297 | 365         } | 
| Chris@5 | 366     } | 
| Chris@624 | 367 | 
| Chris@624 | 368     if (settings.value("maximised", false).toBool()) { | 
| Chris@624 | 369         gui->setWindowState(Qt::WindowMaximized); | 
| Chris@624 | 370     } | 
| Chris@624 | 371 | 
| Chris@5 | 372     settings.endGroup(); | 
| Chris@5 | 373 | 
| Chris@222 | 374     gui->show(); | 
| Chris@64 | 375 | 
| Chris@118 | 376     // The MainWindow class seems to have trouble dealing with this if | 
| Chris@118 | 377     // it tries to adapt to this preference before the constructor is | 
| Chris@118 | 378     // complete.  As a lazy hack, apply it explicitly from here | 
| Chris@222 | 379     gui->preferenceChanged("Property Box Layout"); | 
| Chris@118 | 380 | 
| dan@365 | 381     application.m_readyForFiles = true; // Ready to receive files from e.g. Apple Events | 
| dan@365 | 382 | 
| Chris@54 | 383     for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { | 
| Chris@54 | 384 | 
| Chris@54 | 385         if (i == args.begin()) continue; | 
| Chris@54 | 386         if (i->startsWith('-')) continue; | 
| Chris@54 | 387 | 
| Chris@54 | 388         QString path = *i; | 
| Chris@54 | 389 | 
| dan@365 | 390         application.handleFilepathArgument(path, splash); | 
| dan@365 | 391     } | 
| dan@365 | 392 | 
| dan@365 | 393     for (QStringList::iterator i = application.m_filepathQueue.begin(); i != application.m_filepathQueue.end(); ++i) { | 
| dan@365 | 394         QString path = *i; | 
| dan@365 | 395         application.handleFilepathArgument(path, splash); | 
| Chris@180 | 396     } | 
| Chris@180 | 397 | 
| Chris@215 | 398 #ifdef HAVE_FFTW3F | 
| Chris@215 | 399     settings.beginGroup("FFTWisdom"); | 
| Chris@215 | 400     QString wisdom = settings.value("wisdom").toString(); | 
| Chris@215 | 401     if (wisdom != "") { | 
| Chris@215 | 402         fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data()); | 
| Chris@215 | 403     } | 
| Chris@267 | 404 #ifdef HAVE_FFTW3 | 
| Chris@267 | 405     wisdom = settings.value("wisdom_d").toString(); | 
| Chris@267 | 406     if (wisdom != "") { | 
| Chris@267 | 407         fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data()); | 
| Chris@267 | 408     } | 
| Chris@267 | 409 #endif | 
| Chris@215 | 410     settings.endGroup(); | 
| Chris@215 | 411 #endif | 
| Chris@180 | 412 | 
| Chris@0 | 413     int rv = application.exec(); | 
| Chris@0 | 414 | 
| Chris@298 | 415     gui->hide(); | 
| Chris@298 | 416 | 
| Chris@0 | 417     cleanupMutex.lock(); | 
| Chris@332 | 418 | 
| Chris@589 | 419     if (!cleanedUp) { | 
| Chris@589 | 420         TransformFactory::deleteInstance(); | 
| Chris@589 | 421         TempDirectory::getInstance()->cleanup(); | 
| Chris@589 | 422         cleanedUp = true; | 
| Chris@589 | 423     } | 
| Chris@589 | 424 | 
| Chris@11 | 425     application.releaseMainWindow(); | 
| Chris@5 | 426 | 
| Chris@215 | 427 #ifdef HAVE_FFTW3F | 
| Chris@267 | 428     settings.beginGroup("FFTWisdom"); | 
| Chris@215 | 429     char *cwisdom = fftwf_export_wisdom_to_string(); | 
| Chris@215 | 430     if (cwisdom) { | 
| Chris@215 | 431         settings.setValue("wisdom", cwisdom); | 
| Chris@332 | 432         free(cwisdom); | 
| Chris@215 | 433     } | 
| Chris@267 | 434 #ifdef HAVE_FFTW3 | 
| Chris@267 | 435     cwisdom = fftw_export_wisdom_to_string(); | 
| Chris@267 | 436     if (cwisdom) { | 
| Chris@267 | 437         settings.setValue("wisdom_d", cwisdom); | 
| Chris@332 | 438         free(cwisdom); | 
| Chris@267 | 439     } | 
| Chris@267 | 440 #endif | 
| Chris@267 | 441     settings.endGroup(); | 
| Chris@215 | 442 #endif | 
| Chris@215 | 443 | 
| Chris@908 | 444     FileSource::debugReport(); | 
| Chris@908 | 445 | 
| Chris@222 | 446     delete gui; | 
| Chris@222 | 447 | 
| Chris@573 | 448     cleanupMutex.unlock(); | 
| Chris@573 | 449 | 
| Chris@0 | 450     return rv; | 
| Chris@0 | 451 } | 
| dan@365 | 452 | 
| dan@365 | 453 bool SVApplication::event(QEvent *event){ | 
| Chris@730 | 454 | 
| Chris@730 | 455 // Avoid warnings/errors with -Wextra because we aren't explicitly | 
| Chris@730 | 456 // handling all event types (-Wall is OK with this because of the | 
| Chris@730 | 457 // default but the stricter level insists) | 
| Chris@730 | 458 #pragma GCC diagnostic ignored "-Wswitch-enum" | 
| Chris@730 | 459 | 
| dan@365 | 460     QString thePath; | 
| Chris@730 | 461 | 
| dan@365 | 462     switch (event->type()) { | 
| dan@365 | 463     case QEvent::FileOpen: | 
| dan@365 | 464         thePath = static_cast<QFileOpenEvent *>(event)->file(); | 
| dan@365 | 465         if(m_readyForFiles) | 
| dan@365 | 466             handleFilepathArgument(thePath, NULL); | 
| dan@365 | 467         else | 
| dan@365 | 468             m_filepathQueue.append(thePath); | 
| dan@365 | 469         return true; | 
| dan@365 | 470     default: | 
| dan@365 | 471         return QApplication::event(event); | 
| dan@365 | 472     } | 
| dan@365 | 473 } | 
| dan@365 | 474 | 
| dan@365 | 475 /** Application-global handler for filepaths passed in, e.g. as command-line arguments or apple events */ | 
| Chris@953 | 476 void SVApplication::handleFilepathArgument(QString path, SVSplash *splash){ | 
| dan@365 | 477     static bool haveSession = false; | 
| dan@365 | 478     static bool haveMainModel = false; | 
| dan@365 | 479     static bool havePriorCommandLineModel = false; | 
| dan@365 | 480 | 
| dan@365 | 481     MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; | 
| dan@365 | 482 | 
| Chris@603 | 483 #ifdef Q_OS_WIN32 | 
| Chris@603 | 484     path.replace("\\", "/"); | 
| Chris@603 | 485 #endif | 
| Chris@603 | 486 | 
| dan@365 | 487     if (path.endsWith("sv")) { | 
| dan@365 | 488         if (!haveSession) { | 
| Chris@738 | 489             status = m_mainWindow->openSessionPath(path); | 
| dan@365 | 490             if (status == MainWindow::FileOpenSucceeded) { | 
| dan@365 | 491                 haveSession = true; | 
| dan@365 | 492                 haveMainModel = true; | 
| dan@365 | 493             } | 
| dan@365 | 494         } else { | 
| Chris@665 | 495             cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << endl; | 
| dan@365 | 496             status = MainWindow::FileOpenSucceeded; | 
| dan@365 | 497         } | 
| dan@365 | 498     } | 
| dan@365 | 499     if (status != MainWindow::FileOpenSucceeded) { | 
| dan@365 | 500         if (!haveMainModel) { | 
| Chris@844 | 501             status = m_mainWindow->openPath(path, MainWindow::ReplaceSession); | 
| dan@365 | 502             if (status == MainWindow::FileOpenSucceeded) { | 
| dan@365 | 503                 haveMainModel = true; | 
| dan@365 | 504             } | 
| dan@365 | 505         } else { | 
| dan@365 | 506             if (haveSession && !havePriorCommandLineModel) { | 
| Chris@844 | 507                 status = m_mainWindow->openPath(path, MainWindow::AskUser); | 
| dan@365 | 508                 if (status == MainWindow::FileOpenSucceeded) { | 
| dan@365 | 509                     havePriorCommandLineModel = true; | 
| dan@365 | 510                 } | 
| dan@365 | 511             } else { | 
| Chris@844 | 512                 status = m_mainWindow->openPath(path, MainWindow::CreateAdditionalModel); | 
| dan@365 | 513             } | 
| dan@365 | 514         } | 
| dan@365 | 515     } | 
| dan@365 | 516     if (status == MainWindow::FileOpenFailed) { | 
| dan@365 | 517         if (splash) splash->hide(); | 
| dan@365 | 518         QMessageBox::critical | 
| dan@365 | 519             (m_mainWindow, QMessageBox::tr("Failed to open file"), | 
| dan@365 | 520              QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path)); | 
| dan@365 | 521     } else if (status == MainWindow::FileOpenWrongMode) { | 
| dan@365 | 522         if (splash) splash->hide(); | 
| dan@365 | 523         QMessageBox::critical | 
| dan@365 | 524             (m_mainWindow, QMessageBox::tr("Failed to open file"), | 
| dan@365 | 525              QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); | 
| dan@365 | 526     } | 
| dan@365 | 527 } |