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