| 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@1837 | 27 #include "framework/TransformUserConfigurator.h" | 
| Chris@315 | 28 #include "transform/TransformFactory.h" | 
| Chris@1837 | 29 #include "plugin/PluginScan.h" | 
| Chris@1837 | 30 #include "plugin/PluginPathSetter.h" | 
| Chris@0 | 31 | 
| Chris@0 | 32 #include <QMetaType> | 
| Chris@0 | 33 #include <QApplication> | 
| Chris@2300 | 34 #include <QScreen> | 
| Chris@0 | 35 #include <QMessageBox> | 
| Chris@0 | 36 #include <QTranslator> | 
| Chris@0 | 37 #include <QLocale> | 
| Chris@5 | 38 #include <QSettings> | 
| Chris@7 | 39 #include <QIcon> | 
| Chris@11 | 40 #include <QSessionManager> | 
| Chris@165 | 41 #include <QDir> | 
| Chris@252 | 42 #include <QTimer> | 
| Chris@331 | 43 #include <QPainter> | 
| dan@365 | 44 #include <QFileOpenEvent> | 
| Chris@2232 | 45 #include <QCommandLineParser> | 
| Chris@331 | 46 | 
| Chris@0 | 47 #include <iostream> | 
| Chris@0 | 48 #include <signal.h> | 
| Chris@0 | 49 | 
| Chris@2053 | 50 #include "../version.h" | 
| Chris@2053 | 51 | 
| Chris@215 | 52 #ifdef HAVE_FFTW3F | 
| Chris@215 | 53 #include <fftw3.h> | 
| Chris@215 | 54 #endif | 
| Chris@215 | 55 | 
| Chris@127 | 56 /*! \mainpage Sonic Visualiser | 
| Chris@127 | 57 | 
| Chris@127 | 58 \section interesting Summary of interesting classes | 
| Chris@127 | 59 | 
| Chris@127 | 60  - Data models: Model and subclasses, e.g. WaveFileModel | 
| Chris@127 | 61 | 
| Chris@127 | 62  - Graphical layers: Layer and subclasses, displayed on View and its | 
| Chris@127 | 63  subclass widgets. | 
| Chris@127 | 64 | 
| Chris@127 | 65  - Main window class, document class, and file parser: MainWindow, | 
| Chris@127 | 66  Document, SVFileReader | 
| Chris@127 | 67 | 
| Chris@127 | 68  - Turning one model (e.g. audio) into another (e.g. more audio, or a | 
| Chris@244 | 69  curve extracted from it): Transform, encapsulating the data that need | 
| Chris@244 | 70  to be stored to be able to reproduce a given transformation; | 
| Chris@244 | 71  TransformFactory, for discovering the available types of transform; | 
| Chris@244 | 72  ModelTransformerFactory, ModelTransformer and subclasses, providing | 
| Chris@244 | 73  the mechanisms for applying transforms to data models | 
| Chris@127 | 74 | 
| Chris@127 | 75  - Creating the plugins used by transforms: RealTimePluginFactory, | 
| Chris@129 | 76  FeatureExtractionPluginFactory.  See also the API documentation for | 
| Chris@129 | 77  Vamp feature extraction plugins at | 
| Chris@129 | 78  http://www.vamp-plugins.org/code-doc/. | 
| Chris@127 | 79 | 
| Chris@127 | 80  - File reading and writing code: AudioFileReader and subclasses, | 
| Chris@127 | 81  WavFileWriter, DataFileReader, SVFileReader | 
| Chris@127 | 82 | 
| Chris@127 | 83  - FFT calculation and cacheing: FFTModel, FFTDataServer | 
| Chris@127 | 84 | 
| Chris@127 | 85  - Widgets that show groups of editable properties: PropertyBox for | 
| Chris@127 | 86  layer properties (contained in a PropertyStack), PluginParameterBox | 
| Chris@127 | 87  for plugins (contained in a PluginParameterDialog) | 
| Chris@127 | 88 | 
| Chris@127 | 89  - Audio playback: AudioCallbackPlaySource and subclasses, | 
| Chris@127 | 90  AudioCallbackPlayTarget and subclasses, AudioGenerator | 
| Chris@127 | 91 | 
| Chris@127 | 92 \section model Data sources: the Model hierarchy | 
| Chris@127 | 93 | 
| Chris@2232 | 94 ***!!! todo: update this | 
| Chris@2232 | 95 | 
| Chris@127 | 96    A Model is something containing, or knowing how to obtain, data. | 
| Chris@127 | 97 | 
| Chris@127 | 98    For example, WaveFileModel is a model that knows how to get data | 
| Chris@127 | 99    from an audio file; SparseTimeValueModel is a model containing | 
| Chris@127 | 100    editable "curve" data. | 
| Chris@127 | 101 | 
| Chris@127 | 102    Models typically subclass one of a number of abstract subclasses of | 
| Chris@127 | 103    Model.  For example, WaveFileModel subclasses DenseTimeValueModel, | 
| Chris@127 | 104    which describes an interface for models that have a value at each | 
| Chris@127 | 105    time point for a given sampling resolution.  (Note that | 
| Chris@127 | 106    WaveFileModel does not actually read the files itself: it uses | 
| Chris@127 | 107    AudioFileReader classes for that.  It just makes data from the | 
| Chris@127 | 108    files available in a Model.)  SparseTimeValueModel uses the | 
| Chris@127 | 109    SparseModel template class, which provides most of the | 
| Chris@127 | 110    implementation for models that contain a series of points of some | 
| Chris@127 | 111    sort -- also used by NoteModel, TextModel, and | 
| Chris@127 | 112    SparseOneDimensionalModel. | 
| Chris@127 | 113 | 
| Chris@127 | 114    Everything that goes on the screen originates from a model, via a | 
| Chris@127 | 115    layer (see below).  The models are contained in a Document object. | 
| Chris@127 | 116    There is no containment hierarchy or ordering of models in the | 
| Chris@127 | 117    document.  One model is the main model, which defines the sample | 
| Chris@127 | 118    rate for playback. | 
| Chris@127 | 119 | 
| Chris@127 | 120    A model may also be marked as a "derived" model, which means it was | 
| Chris@127 | 121    generated from another model using some transform (feature | 
| Chris@127 | 122    extraction or effect plugin, etc) -- the idea being that they can | 
| Chris@127 | 123    be re-generated using the same transform if a new source model is | 
| Chris@127 | 124    loaded. | 
| Chris@127 | 125 | 
| Chris@127 | 126 \section layer Things that can display data: the Layer hierarchy | 
| Chris@127 | 127 | 
| Chris@127 | 128    A Layer is something that knows how to draw parts of a model onto a | 
| Chris@127 | 129    timeline. | 
| Chris@127 | 130 | 
| Chris@127 | 131    For example, WaveformLayer is a layer which draws waveforms, based | 
| Chris@127 | 132    on WaveFileModel; TimeValueLayer draws curves, based on | 
| Chris@127 | 133    SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on | 
| Chris@127 | 134    WaveFileModel (via FFTModel). | 
| Chris@127 | 135 | 
| Chris@127 | 136    The most basic functions of a layer are: to draw itself onto a | 
| Chris@127 | 137    Pane, against a timeline on the x axis; and to permit user | 
| Chris@127 | 138    interaction.  If you were thinking of adding the capability to | 
| Chris@127 | 139    display a new sort of something, then you would want to add a new | 
| Chris@127 | 140    layer type.  (You may also need a new model type, depending on | 
| Chris@127 | 141    whether any existing model can capture the data you need.) | 
| Chris@127 | 142    Depending on the sort of data in question, there are various | 
| Chris@127 | 143    existing layers that might be appropriate to start from -- for | 
| Chris@127 | 144    example, a layer that displays images that the user has imported | 
| Chris@127 | 145    and associated with particular times might have something in common | 
| Chris@127 | 146    with the existing TextLayer which displays pieces of text that are | 
| Chris@127 | 147    associated with particular times. | 
| Chris@127 | 148 | 
| Chris@127 | 149    Although layers are visual objects, they are contained in the | 
| Chris@127 | 150    Document in Sonic Visualiser rather than being managed together | 
| Chris@127 | 151    with display widgets.  The Sonic Visualiser file format has | 
| Chris@127 | 152    separate data and layout sections, and the layers are defined in | 
| Chris@127 | 153    the data section and then referred to in the layout section which | 
| Chris@127 | 154    determines which layers may go on which panes (see Pane below). | 
| Chris@127 | 155 | 
| Chris@127 | 156    Once a layer class is defined, some basic data about it needs to be | 
| Chris@127 | 157    set up in the LayerFactory class, and then it will appear in the | 
| Chris@127 | 158    menus and so on on the main window. | 
| Chris@127 | 159 | 
| Chris@127 | 160 \section view Widgets that are used to show layers: The View hierarchy | 
| Chris@127 | 161 | 
| Chris@127 | 162    A View is a widget that displays a stack of layers.  The most | 
| Chris@127 | 163    important subclass is Pane, the widget that is used to show most of | 
| Chris@127 | 164    the data in the main window of Sonic Visualiser. | 
| Chris@127 | 165 | 
| Chris@127 | 166    All a pane really does is contain a set of layers and get them to | 
| Chris@127 | 167    render themselves (one on top of the other, with the topmost layer | 
| Chris@127 | 168    being the one that is currently interacted with), cache the | 
| Chris@127 | 169    results, negotiate user interaction with them, and so on.  This is | 
| Chris@127 | 170    generally fiddly, if not especially interesting.  Panes are | 
| Chris@127 | 171    strictly layout objects and are not stored in the Document class; | 
| Chris@127 | 172    instead the MainWindow contains a PaneStack widget (the widget that | 
| Chris@127 | 173    takes up most of Sonic Visualiser's main window) which contains a | 
| Chris@127 | 174    set of panes stacked vertically. | 
| Chris@127 | 175 | 
| Chris@127 | 176    Another View subclass is Overview, which is the widget that | 
| Chris@127 | 177    contains that green waveform showing the entire file at the bottom | 
| Chris@127 | 178    of the window. | 
| Chris@127 | 179 | 
| Chris@127 | 180 */ | 
| Chris@127 | 181 | 
| Chris@0 | 182 static QMutex cleanupMutex; | 
| Chris@589 | 183 static bool cleanedUp = false; | 
| Chris@0 | 184 | 
| Chris@0 | 185 static void | 
| Chris@0 | 186 signalHandler(int /* signal */) | 
| Chris@0 | 187 { | 
| Chris@0 | 188     // Avoid this happening more than once across threads | 
| Chris@0 | 189 | 
| Chris@665 | 190     cerr << "signalHandler: cleaning up and exiting" << endl; | 
| Chris@2338 | 191 | 
| Chris@2338 | 192     if (cleanupMutex.tryLock(5000)) { | 
| Chris@2338 | 193         if (!cleanedUp) { | 
| Chris@2338 | 194             TempDirectory::getInstance()->cleanup(); | 
| Chris@2338 | 195             cleanedUp = true; | 
| Chris@2338 | 196         } | 
| Chris@2338 | 197         cleanupMutex.unlock(); | 
| Chris@589 | 198     } | 
| Chris@2338 | 199 | 
| Chris@589 | 200     exit(0); | 
| Chris@0 | 201 } | 
| Chris@0 | 202 | 
| Chris@11 | 203 class SVApplication : public QApplication | 
| Chris@11 | 204 { | 
| Chris@11 | 205 public: | 
| Chris@296 | 206     SVApplication(int &argc, char **argv) : | 
| Chris@11 | 207         QApplication(argc, argv), | 
| dan@365 | 208         m_readyForFiles(false), | 
| dan@365 | 209         m_filepathQueue(QStringList()), | 
| Chris@2126 | 210         m_mainWindow(nullptr) | 
| Chris@509 | 211     { | 
| Chris@509 | 212     } | 
| Chris@2121 | 213     ~SVApplication() override { } | 
| Chris@11 | 214 | 
| Chris@11 | 215     void setMainWindow(MainWindow *mw) { m_mainWindow = mw; } | 
| Chris@2126 | 216     void releaseMainWindow() { m_mainWindow = nullptr; } | 
| Chris@11 | 217 | 
| Chris@11 | 218     virtual void commitData(QSessionManager &manager) { | 
| Chris@11 | 219         if (!m_mainWindow) return; | 
| Chris@11 | 220         bool mayAskUser = manager.allowsInteraction(); | 
| Chris@11 | 221         bool success = m_mainWindow->commitData(mayAskUser); | 
| Chris@11 | 222         manager.release(); | 
| Chris@11 | 223         if (!success) manager.cancel(); | 
| Chris@11 | 224     } | 
| Chris@11 | 225 | 
| Chris@953 | 226     void handleFilepathArgument(QString path, SVSplash *splash); | 
| dan@362 | 227 | 
| dan@365 | 228     bool m_readyForFiles; | 
| dan@365 | 229     QStringList m_filepathQueue; | 
| dan@362 | 230 | 
| Chris@11 | 231 protected: | 
| Chris@11 | 232     MainWindow *m_mainWindow; | 
| Chris@2121 | 233     bool event(QEvent *) override; | 
| Chris@11 | 234 }; | 
| Chris@11 | 235 | 
| Chris@0 | 236 int | 
| Chris@0 | 237 main(int argc, char **argv) | 
| Chris@0 | 238 { | 
| Chris@2058 | 239     if (argc == 2 && (QString(argv[1]) == "--version" || | 
| Chris@2058 | 240                       QString(argv[1]) == "-v")) { | 
| Chris@2058 | 241         cerr << SV_VERSION << endl; | 
| Chris@2058 | 242         exit(0); | 
| Chris@2058 | 243     } | 
| Chris@2296 | 244 | 
| Chris@376 | 245     svSystemSpecificInitialisation(); | 
| Chris@376 | 246 | 
| Chris@11 | 247     SVApplication application(argc, argv); | 
| Chris@0 | 248 | 
| Chris@2232 | 249     QApplication::setOrganizationName("sonic-visualiser"); | 
| Chris@2232 | 250     QApplication::setOrganizationDomain("sonicvisualiser.org"); | 
| Chris@2232 | 251     QApplication::setApplicationName(QApplication::tr("Sonic Visualiser")); | 
| Chris@2232 | 252     QApplication::setApplicationVersion(SV_VERSION); | 
| Chris@2232 | 253 | 
| Chris@2232 | 254     //!!! todo hand-update translations | 
| Chris@2232 | 255     QCommandLineParser parser; | 
| Chris@2241 | 256     parser.setApplicationDescription(QApplication::tr("\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation.")); | 
| Chris@2232 | 257     parser.addHelpOption(); | 
| Chris@2232 | 258     parser.addVersionOption(); | 
| Chris@2232 | 259 | 
| Chris@2361 | 260     parser.addOption(QCommandLineOption | 
| Chris@2361 | 261                      ("no-audio", QApplication::tr | 
| Chris@2361 | 262                       ("Do not attempt to open an audio output device."))); | 
| Chris@2361 | 263     parser.addOption(QCommandLineOption | 
| Chris@2361 | 264                      ("no-osc", QApplication::tr | 
| Chris@2361 | 265                       ("Do not provide an Open Sound Control port for remote control."))); | 
| Chris@2361 | 266     parser.addOption(QCommandLineOption | 
| Chris@2361 | 267                      ("no-splash", QApplication::tr | 
| Chris@2361 | 268                       ("Do not show a splash screen."))); | 
| Chris@2361 | 269     parser.addOption(QCommandLineOption | 
| Chris@2361 | 270                      ("osc-script", QApplication::tr | 
| Chris@2361 | 271                       ("Batch run the Open Sound Control script found in the given file. Supply \"-\" as file to read from stdin. Scripts consist of /command arg1 arg2 ... OSC control lines, optionally interleaved with numbers to specify pauses in seconds."), | 
| Chris@2361 | 272                       "osc.txt")); | 
| Chris@2375 | 273     parser.addOption(QCommandLineOption | 
| Chris@2375 | 274                      ("first-run", QApplication::tr | 
| Chris@2375 | 275                       ("Clear any saved settings and reset to first-run behaviour."))); | 
| Chris@2232 | 276 | 
| Chris@2232 | 277     parser.addPositionalArgument | 
| Chris@2232 | 278         ("[<file> ...]", QApplication::tr("One or more Sonic Visualiser (.sv) and audio files may be provided.")); | 
| Chris@2232 | 279 | 
| Chris@46 | 280     QStringList args = application.arguments(); | 
| Chris@2232 | 281     if (!parser.parse(args)) { | 
| Chris@2232 | 282         if (parser.unknownOptionNames().contains("?")) { | 
| Chris@2232 | 283             // QCommandLineParser only understands -? for help on Windows, | 
| Chris@2232 | 284             // but we historically accepted it everywhere - provide this | 
| Chris@2232 | 285             // backward compatibility | 
| Chris@2232 | 286             parser.showHelp(); | 
| Chris@2232 | 287         } | 
| Chris@2232 | 288     } | 
| Chris@2232 | 289 | 
| Chris@2232 | 290     parser.process(args); | 
| Chris@2232 | 291 | 
| Chris@2375 | 292     if (parser.isSet("first-run")) { | 
| Chris@2375 | 293         QSettings settings; | 
| Chris@2375 | 294         settings.clear(); | 
| Chris@2375 | 295     } | 
| Chris@2375 | 296 | 
| Chris@2233 | 297     bool audioOutput = !(parser.isSet("no-audio")); | 
| Chris@2233 | 298     bool oscSupport = !(parser.isSet("no-osc")); | 
| Chris@2233 | 299     bool showSplash = !(parser.isSet("no-splash")); | 
| Chris@2233 | 300 | 
| Chris@2240 | 301     if (!audioOutput) { | 
| Chris@2240 | 302         SVDEBUG << "Note: --no-audio flag set, will not use audio device" << endl; | 
| Chris@2240 | 303     } | 
| Chris@2240 | 304     if (!oscSupport) { | 
| Chris@2240 | 305         SVDEBUG << "Note: --no-osc flag set, will not open OSC port" << endl; | 
| Chris@2240 | 306     } | 
| Chris@2240 | 307 | 
| Chris@2232 | 308     args = parser.positionalArguments(); | 
| Chris@2232 | 309 | 
| Chris@2232 | 310     QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); | 
| Chris@46 | 311 | 
| Chris@0 | 312     signal(SIGINT,  signalHandler); | 
| Chris@0 | 313     signal(SIGTERM, signalHandler); | 
| Chris@0 | 314 | 
| Chris@640 | 315 #ifndef Q_OS_WIN32 | 
| Chris@0 | 316     signal(SIGHUP,  signalHandler); | 
| Chris@0 | 317     signal(SIGQUIT, signalHandler); | 
| Chris@0 | 318 #endif | 
| Chris@0 | 319 | 
| Chris@2126 | 320     SVSplash *splash = nullptr; | 
| Chris@231 | 321 | 
| Chris@231 | 322     QSettings settings; | 
| Chris@237 | 323 | 
| Chris@237 | 324     settings.beginGroup("Preferences"); | 
| Chris@1274 | 325     // Default to using Piper server; can change in preferences | 
| Chris@1274 | 326     if (!settings.contains("run-vamp-plugins-in-process")) { | 
| Chris@1274 | 327         settings.setValue("run-vamp-plugins-in-process", false); | 
| Chris@1274 | 328     } | 
| Chris@1274 | 329     settings.endGroup(); | 
| Chris@1274 | 330 | 
| Chris@1274 | 331     settings.beginGroup("Preferences"); | 
| Chris@2233 | 332     if (showSplash && settings.value("show-splash", true).toBool()) { | 
| Chris@953 | 333         splash = new SVSplash(); | 
| Chris@283 | 334         splash->show(); | 
| Chris@283 | 335         QTimer::singleShot(5000, splash, SLOT(hide())); | 
| Chris@231 | 336         application.processEvents(); | 
| Chris@231 | 337     } | 
| Chris@237 | 338     settings.endGroup(); | 
| Chris@231 | 339 | 
| Chris@278 | 340     settings.beginGroup("RDF"); | 
| Chris@278 | 341     if (!settings.contains("rdf-indices")) { | 
| Chris@278 | 342         QStringList list; | 
| Chris@278 | 343         list << "http://www.vamp-plugins.org/rdf/plugins/index.txt"; | 
| Chris@278 | 344         settings.setValue("rdf-indices", list); | 
| Chris@278 | 345     } | 
| Chris@278 | 346     settings.endGroup(); | 
| Chris@278 | 347 | 
| Chris@1838 | 348     PluginPathSetter::initialiseEnvironmentVariables(); | 
| Chris@1837 | 349 | 
| Chris@141 | 350     QIcon icon; | 
| Chris@141 | 351     int sizes[] = { 16, 22, 24, 32, 48, 64, 128 }; | 
| Chris@730 | 352     for (int i = 0; i < int(sizeof(sizes)/sizeof(sizes[0])); ++i) { | 
| Chris@141 | 353         icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i])); | 
| Chris@141 | 354     } | 
| Chris@141 | 355     QApplication::setWindowIcon(icon); | 
| Chris@7 | 356 | 
| Chris@0 | 357     QString language = QLocale::system().name(); | 
| Chris@1469 | 358     SVDEBUG << "System language is: " << language << endl; | 
| Chris@0 | 359 | 
| Chris@658 | 360     settings.beginGroup("Preferences"); | 
| Chris@1469 | 361     QString prefLanguage = settings.value("locale", language).toString(); | 
| Chris@1469 | 362     if (prefLanguage != QString()) language = prefLanguage; | 
| Chris@658 | 363     settings.endGroup(); | 
| Chris@658 | 364 | 
| Chris@0 | 365     QTranslator qtTranslator; | 
| Chris@0 | 366     QString qtTrName = QString("qt_%1").arg(language); | 
| Chris@438 | 367     SVDEBUG << "Loading " << qtTrName << "... "; | 
| Chris@165 | 368     bool success = false; | 
| Chris@165 | 369     if (!(success = qtTranslator.load(qtTrName))) { | 
| Chris@165 | 370         QString qtDir = getenv("QTDIR"); | 
| Chris@165 | 371         if (qtDir != "") { | 
| Chris@165 | 372             success = qtTranslator.load | 
| Chris@165 | 373                 (qtTrName, QDir(qtDir).filePath("translations")); | 
| Chris@165 | 374         } | 
| Chris@165 | 375     } | 
| Chris@165 | 376     if (!success) { | 
| Chris@438 | 377         SVDEBUG << "Failed\nFailed to load Qt translation for locale" << endl; | 
| Chris@253 | 378     } else { | 
| Chris@665 | 379         cerr << "Done" << endl; | 
| Chris@165 | 380     } | 
| Chris@0 | 381     application.installTranslator(&qtTranslator); | 
| Chris@0 | 382 | 
| Chris@0 | 383     QTranslator svTranslator; | 
| Chris@0 | 384     QString svTrName = QString("sonic-visualiser_%1").arg(language); | 
| Chris@438 | 385     SVDEBUG << "Loading " << svTrName << "... "; | 
| Chris@0 | 386     svTranslator.load(svTrName, ":i18n"); | 
| Chris@438 | 387     SVDEBUG << "Done" << endl; | 
| Chris@0 | 388     application.installTranslator(&svTranslator); | 
| Chris@0 | 389 | 
| Chris@187 | 390     StoreStartupLocale(); | 
| Chris@187 | 391 | 
| Chris@1144 | 392     // Make known-plugins query as early as possible after showing | 
| Chris@1307 | 393     // splash screen. | 
| Chris@1307 | 394     PluginScan::getInstance()->scan(); | 
| Chris@1144 | 395 | 
| Chris@1412 | 396     // Permit these types to be used as args in queued signal calls | 
| Chris@0 | 397     qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName"); | 
| Chris@1412 | 398     qRegisterMetaType<ZoomLevel>("ZoomLevel"); | 
| Chris@0 | 399 | 
| Chris@2375 | 400     MainWindow::AudioMode audioMode = | 
| Chris@2375 | 401         MainWindow::AUDIO_PLAYBACK_NOW_RECORD_LATER; | 
| Chris@2375 | 402     MainWindow::MIDIMode midiMode = | 
| Chris@2375 | 403         MainWindow::MIDI_LISTEN; | 
| Chris@2375 | 404 | 
| Chris@2375 | 405     if (!audioOutput) { | 
| Chris@2375 | 406         audioMode = MainWindow::AUDIO_NONE; | 
| Chris@2375 | 407         midiMode = MainWindow::MIDI_NONE; | 
| Chris@2375 | 408     } | 
| Chris@1045 | 409 | 
| Chris@2375 | 410     MainWindow *gui = new MainWindow(audioMode, midiMode, oscSupport); | 
| Chris@222 | 411     application.setMainWindow(gui); | 
| Chris@2375 | 412 | 
| Chris@763 | 413     InteractiveFileFinder::setParentWidget(gui); | 
| Chris@763 | 414     TransformUserConfigurator::setParentWidget(gui); | 
| Chris@283 | 415     if (splash) { | 
| Chris@283 | 416         QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide())); | 
| Chris@953 | 417         QObject::connect(gui, SIGNAL(hideSplash(QWidget *)), | 
| Chris@953 | 418                          splash, SLOT(finishSplash(QWidget *))); | 
| Chris@283 | 419     } | 
| Chris@0 | 420 | 
| Chris@2300 | 421     QScreen *screen = QApplication::primaryScreen(); | 
| Chris@2300 | 422     QRect available = screen->availableGeometry(); | 
| Chris@0 | 423 | 
| Chris@378 | 424     int width = (available.width() * 2) / 3; | 
| Chris@0 | 425     int height = available.height() / 2; | 
| Chris@378 | 426     if (height < 450) height = (available.height() * 2) / 3; | 
| Chris@0 | 427     if (width > height * 2) width = height * 2; | 
| Chris@0 | 428 | 
| Chris@237 | 429     settings.beginGroup("MainWindow"); | 
| Chris@624 | 430 | 
| Chris@5 | 431     QSize size = settings.value("size", QSize(width, height)).toSize(); | 
| Chris@319 | 432     gui->resizeConstrained(size); | 
| Chris@624 | 433 | 
| Chris@5 | 434     if (settings.contains("position")) { | 
| Chris@297 | 435         QRect prevrect(settings.value("position").toPoint(), size); | 
| Chris@297 | 436         if (!(available & prevrect).isEmpty()) { | 
| Chris@297 | 437             gui->move(prevrect.topLeft()); | 
| Chris@297 | 438         } | 
| Chris@5 | 439     } | 
| Chris@624 | 440 | 
| Chris@624 | 441     if (settings.value("maximised", false).toBool()) { | 
| Chris@624 | 442         gui->setWindowState(Qt::WindowMaximized); | 
| Chris@624 | 443     } | 
| Chris@624 | 444 | 
| Chris@5 | 445     settings.endGroup(); | 
| Chris@5 | 446 | 
| Chris@222 | 447     gui->show(); | 
| Chris@64 | 448 | 
| Chris@118 | 449     // The MainWindow class seems to have trouble dealing with this if | 
| Chris@118 | 450     // it tries to adapt to this preference before the constructor is | 
| Chris@118 | 451     // complete.  As a lazy hack, apply it explicitly from here | 
| Chris@222 | 452     gui->preferenceChanged("Property Box Layout"); | 
| Chris@118 | 453 | 
| dan@365 | 454     application.m_readyForFiles = true; // Ready to receive files from e.g. Apple Events | 
| dan@365 | 455 | 
| Chris@54 | 456     for (QStringList::iterator i = args.begin(); i != args.end(); ++i) { | 
| Chris@54 | 457 | 
| Chris@2232 | 458         // Note QCommandLineParser has now pulled out argv[0] and all | 
| Chris@2232 | 459         // the options, so in theory everything here from the very | 
| Chris@2232 | 460         // first arg should be relevant. But let's reject names | 
| Chris@2232 | 461         // starting with "-" just in case. | 
| Chris@2232 | 462 | 
| Chris@54 | 463         if (i->startsWith('-')) continue; | 
| Chris@54 | 464 | 
| Chris@54 | 465         QString path = *i; | 
| Chris@54 | 466 | 
| dan@365 | 467         application.handleFilepathArgument(path, splash); | 
| dan@365 | 468     } | 
| dan@365 | 469 | 
| Chris@2232 | 470     for (QStringList::iterator i = application.m_filepathQueue.begin(); | 
| Chris@2232 | 471          i != application.m_filepathQueue.end(); ++i) { | 
| dan@365 | 472         QString path = *i; | 
| dan@365 | 473         application.handleFilepathArgument(path, splash); | 
| Chris@180 | 474     } | 
| Chris@180 | 475 | 
| Chris@215 | 476 #ifdef HAVE_FFTW3F | 
| Chris@215 | 477     settings.beginGroup("FFTWisdom"); | 
| Chris@215 | 478     QString wisdom = settings.value("wisdom").toString(); | 
| Chris@215 | 479     if (wisdom != "") { | 
| Chris@215 | 480         fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data()); | 
| Chris@215 | 481     } | 
| Chris@267 | 482 #ifdef HAVE_FFTW3 | 
| Chris@267 | 483     wisdom = settings.value("wisdom_d").toString(); | 
| Chris@267 | 484     if (wisdom != "") { | 
| Chris@267 | 485         fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data()); | 
| Chris@267 | 486     } | 
| Chris@267 | 487 #endif | 
| Chris@215 | 488     settings.endGroup(); | 
| Chris@215 | 489 #endif | 
| Chris@180 | 490 | 
| Chris@2232 | 491     QString scriptFile = parser.value("osc-script"); | 
| Chris@2232 | 492     if (scriptFile != "") { | 
| Chris@2240 | 493         SVDEBUG << "Note: Cueing OSC script from filename \"" << scriptFile | 
| Chris@2240 | 494                 << "\"" << endl; | 
| Chris@2232 | 495         gui->cueOSCScript(scriptFile); | 
| Chris@2232 | 496     } | 
| Chris@2232 | 497 | 
| Chris@0 | 498     int rv = application.exec(); | 
| Chris@0 | 499 | 
| Chris@298 | 500     gui->hide(); | 
| Chris@298 | 501 | 
| Chris@0 | 502     cleanupMutex.lock(); | 
| Chris@332 | 503 | 
| Chris@589 | 504     if (!cleanedUp) { | 
| Chris@589 | 505         TransformFactory::deleteInstance(); | 
| Chris@589 | 506         TempDirectory::getInstance()->cleanup(); | 
| Chris@589 | 507         cleanedUp = true; | 
| Chris@589 | 508     } | 
| Chris@589 | 509 | 
| Chris@11 | 510     application.releaseMainWindow(); | 
| Chris@5 | 511 | 
| Chris@215 | 512 #ifdef HAVE_FFTW3F | 
| Chris@267 | 513     settings.beginGroup("FFTWisdom"); | 
| Chris@215 | 514     char *cwisdom = fftwf_export_wisdom_to_string(); | 
| Chris@215 | 515     if (cwisdom) { | 
| Chris@215 | 516         settings.setValue("wisdom", cwisdom); | 
| Chris@332 | 517         free(cwisdom); | 
| Chris@215 | 518     } | 
| Chris@267 | 519 #ifdef HAVE_FFTW3 | 
| Chris@267 | 520     cwisdom = fftw_export_wisdom_to_string(); | 
| Chris@267 | 521     if (cwisdom) { | 
| Chris@267 | 522         settings.setValue("wisdom_d", cwisdom); | 
| Chris@332 | 523         free(cwisdom); | 
| Chris@267 | 524     } | 
| Chris@267 | 525 #endif | 
| Chris@267 | 526     settings.endGroup(); | 
| Chris@215 | 527 #endif | 
| Chris@215 | 528 | 
| Chris@908 | 529     FileSource::debugReport(); | 
| Chris@908 | 530 | 
| Chris@222 | 531     delete gui; | 
| Chris@222 | 532 | 
| Chris@573 | 533     cleanupMutex.unlock(); | 
| Chris@573 | 534 | 
| Chris@0 | 535     return rv; | 
| Chris@0 | 536 } | 
| dan@365 | 537 | 
| dan@365 | 538 bool SVApplication::event(QEvent *event){ | 
| Chris@730 | 539 | 
| Chris@730 | 540 // Avoid warnings/errors with -Wextra because we aren't explicitly | 
| Chris@730 | 541 // handling all event types (-Wall is OK with this because of the | 
| Chris@730 | 542 // default but the stricter level insists) | 
| Chris@730 | 543 #pragma GCC diagnostic ignored "-Wswitch-enum" | 
| Chris@730 | 544 | 
| dan@365 | 545     QString thePath; | 
| Chris@730 | 546 | 
| dan@365 | 547     switch (event->type()) { | 
| dan@365 | 548     case QEvent::FileOpen: | 
| dan@365 | 549         thePath = static_cast<QFileOpenEvent *>(event)->file(); | 
| dan@365 | 550         if(m_readyForFiles) | 
| Chris@2126 | 551             handleFilepathArgument(thePath, nullptr); | 
| dan@365 | 552         else | 
| dan@365 | 553             m_filepathQueue.append(thePath); | 
| dan@365 | 554         return true; | 
| dan@365 | 555     default: | 
| dan@365 | 556         return QApplication::event(event); | 
| dan@365 | 557     } | 
| dan@365 | 558 } | 
| dan@365 | 559 | 
| dan@365 | 560 /** Application-global handler for filepaths passed in, e.g. as command-line arguments or apple events */ | 
| Chris@953 | 561 void SVApplication::handleFilepathArgument(QString path, SVSplash *splash){ | 
| dan@365 | 562     static bool haveSession = false; | 
| dan@365 | 563     static bool haveMainModel = false; | 
| dan@365 | 564     static bool havePriorCommandLineModel = false; | 
| dan@365 | 565 | 
| dan@365 | 566     MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; | 
| dan@365 | 567 | 
| Chris@603 | 568 #ifdef Q_OS_WIN32 | 
| Chris@603 | 569     path.replace("\\", "/"); | 
| Chris@603 | 570 #endif | 
| Chris@603 | 571 | 
| dan@365 | 572     if (path.endsWith("sv")) { | 
| dan@365 | 573         if (!haveSession) { | 
| Chris@738 | 574             status = m_mainWindow->openSessionPath(path); | 
| dan@365 | 575             if (status == MainWindow::FileOpenSucceeded) { | 
| dan@365 | 576                 haveSession = true; | 
| dan@365 | 577                 haveMainModel = true; | 
| dan@365 | 578             } | 
| dan@365 | 579         } else { | 
| Chris@665 | 580             cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << endl; | 
| dan@365 | 581             status = MainWindow::FileOpenSucceeded; | 
| dan@365 | 582         } | 
| dan@365 | 583     } | 
| dan@365 | 584     if (status != MainWindow::FileOpenSucceeded) { | 
| dan@365 | 585         if (!haveMainModel) { | 
| Chris@844 | 586             status = m_mainWindow->openPath(path, MainWindow::ReplaceSession); | 
| dan@365 | 587             if (status == MainWindow::FileOpenSucceeded) { | 
| dan@365 | 588                 haveMainModel = true; | 
| dan@365 | 589             } | 
| dan@365 | 590         } else { | 
| dan@365 | 591             if (haveSession && !havePriorCommandLineModel) { | 
| Chris@844 | 592                 status = m_mainWindow->openPath(path, MainWindow::AskUser); | 
| dan@365 | 593                 if (status == MainWindow::FileOpenSucceeded) { | 
| dan@365 | 594                     havePriorCommandLineModel = true; | 
| dan@365 | 595                 } | 
| dan@365 | 596             } else { | 
| Chris@844 | 597                 status = m_mainWindow->openPath(path, MainWindow::CreateAdditionalModel); | 
| dan@365 | 598             } | 
| dan@365 | 599         } | 
| dan@365 | 600     } | 
| dan@365 | 601     if (status == MainWindow::FileOpenFailed) { | 
| dan@365 | 602         if (splash) splash->hide(); | 
| dan@365 | 603         QMessageBox::critical | 
| dan@365 | 604             (m_mainWindow, QMessageBox::tr("Failed to open file"), | 
| dan@365 | 605              QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path)); | 
| dan@365 | 606     } else if (status == MainWindow::FileOpenWrongMode) { | 
| dan@365 | 607         if (splash) splash->hide(); | 
| dan@365 | 608         QMessageBox::critical | 
| dan@365 | 609             (m_mainWindow, QMessageBox::tr("Failed to open file"), | 
| dan@365 | 610              QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); | 
| dan@365 | 611     } | 
| dan@365 | 612 } |