annotate main/main.cpp @ 2596:04d381f0d89a tip

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