main.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006 Chris Cannam and QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "MainWindow.h"
17 #include "SVSplash.h"
18 
19 #include "system/System.h"
20 #include "system/Init.h"
21 #include "base/TempDirectory.h"
22 #include "base/PropertyContainer.h"
23 #include "base/Preferences.h"
24 #include "data/fileio/FileSource.h"
25 #include "widgets/TipDialog.h"
26 #include "widgets/InteractiveFileFinder.h"
27 #include "framework/TransformUserConfigurator.h"
28 #include "transform/TransformFactory.h"
29 #include "plugin/PluginScan.h"
30 #include "plugin/PluginPathSetter.h"
31 
32 #include <QMetaType>
33 #include <QApplication>
34 #include <QScreen>
35 #include <QMessageBox>
36 #include <QTranslator>
37 #include <QLocale>
38 #include <QSettings>
39 #include <QIcon>
40 #include <QSessionManager>
41 #include <QDir>
42 #include <QTimer>
43 #include <QPainter>
44 #include <QFileOpenEvent>
45 #include <QCommandLineParser>
46 #include <QSslSocket>
47 #include <QFont>
48 #include <QFontInfo>
49 
50 #include <iostream>
51 #include <signal.h>
52 
53 #include "../version.h"
54 
55 #ifdef HAVE_FFTW3F
56 #include <fftw3.h>
57 #endif
58 
185 static QMutex cleanupMutex;
186 static bool cleanedUp = false;
187 
188 static void
189 signalHandler(int /* signal */)
190 {
191  // Avoid this happening more than once across threads
192 
193  cerr << "signalHandler: cleaning up and exiting" << endl;
194 
195  if (cleanupMutex.tryLock(5000)) {
196  if (!cleanedUp) {
197  TempDirectory::getInstance()->cleanup();
198  cleanedUp = true;
199  }
200  cleanupMutex.unlock();
201  }
202 
203  exit(0);
204 }
205 
206 class SVApplication : public QApplication
207 {
208 public:
209  SVApplication(int &argc, char **argv) :
210  QApplication(argc, argv),
211  m_readyForFiles(false),
212  m_filepathQueue(QStringList()),
213  m_mainWindow(nullptr)
214  {
215  }
216  ~SVApplication() override { }
217 
219  void releaseMainWindow() { m_mainWindow = nullptr; }
220 
221  virtual void commitData(QSessionManager &manager) {
222  if (!m_mainWindow) return;
223  bool mayAskUser = manager.allowsInteraction();
224  bool success = m_mainWindow->commitData(mayAskUser);
225  manager.release();
226  if (!success) manager.cancel();
227  }
228 
229  void handleFilepathArgument(QString path, SVSplash *splash);
230 
232  QStringList m_filepathQueue;
233 
234 protected:
236  bool event(QEvent *) override;
237 };
238 
239 int
240 main(int argc, char **argv)
241 {
242  if (argc == 2 && (QString(argv[1]) == "--version" ||
243  QString(argv[1]) == "-v")) {
244  cerr << SV_VERSION << endl;
245  exit(0);
246  }
247 
248  svSystemSpecificInitialisation();
249 
250  SVApplication application(argc, argv);
251 
252  QApplication::setOrganizationName("sonic-visualiser");
253  QApplication::setOrganizationDomain("sonicvisualiser.org");
254  QApplication::setApplicationName(QApplication::tr("Sonic Visualiser"));
255  QApplication::setApplicationVersion(SV_VERSION);
256 
258  QCommandLineParser parser;
259  parser.setApplicationDescription(QApplication::tr("\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation."));
260  parser.addHelpOption();
261  parser.addVersionOption();
262 
263  parser.addOption(QCommandLineOption
264  ("no-audio", QApplication::tr
265  ("Do not attempt to open an audio output device.")));
266  parser.addOption(QCommandLineOption
267  ("no-osc", QApplication::tr
268  ("Do not provide an Open Sound Control port for remote control.")));
269  parser.addOption(QCommandLineOption
270  ("no-splash", QApplication::tr
271  ("Do not show a splash screen.")));
272  parser.addOption(QCommandLineOption
273  ("osc-script", QApplication::tr
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."),
275  "osc.txt"));
276  parser.addOption(QCommandLineOption
277  ("first-run", QApplication::tr
278  ("Clear any saved settings and reset to first-run behaviour.")));
279 
280  parser.addPositionalArgument
281  ("[<file> ...]", QApplication::tr("One or more Sonic Visualiser (.sv) and audio files may be provided."));
282 
283  QStringList args = application.arguments();
284  if (!parser.parse(args)) {
285  if (parser.unknownOptionNames().contains("?")) {
286  // QCommandLineParser only understands -? for help on Windows,
287  // but we historically accepted it everywhere - provide this
288  // backward compatibility
289  parser.showHelp();
290  }
291  }
292 
293  parser.process(args);
294 
295  if (parser.isSet("first-run")) {
296  QSettings settings;
297  settings.clear();
298  }
299 
300  bool audioOutput = !(parser.isSet("no-audio"));
301  bool oscSupport = !(parser.isSet("no-osc"));
302  bool showSplash = !(parser.isSet("no-splash"));
303 
304  if (!audioOutput) {
305  SVDEBUG << "Note: --no-audio flag set, will not use audio device" << endl;
306  }
307  if (!oscSupport) {
308  SVDEBUG << "Note: --no-osc flag set, will not open OSC port" << endl;
309  }
310 
311  args = parser.positionalArguments();
312 
313  QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
314 
315  signal(SIGINT, signalHandler);
316  signal(SIGTERM, signalHandler);
317 
318 #ifndef Q_OS_WIN32
319  signal(SIGHUP, signalHandler);
320  signal(SIGQUIT, signalHandler);
321 #endif
322 
323  SVSplash *splash = nullptr;
324 
325  QSettings settings;
326 
327  QString language = QLocale::system().name();
328  SVDEBUG << "System language is: " << language << endl;
329 
330  settings.beginGroup("Preferences");
331  QString prefLanguage = settings.value("locale", language).toString();
332  if (prefLanguage != QString()) language = prefLanguage;
333  settings.endGroup();
334 
335  settings.beginGroup("Preferences");
336  if (!(settings.value("always-use-default-font", false).toBool())) {
337 #ifdef Q_OS_WIN32
338  if (!language.startsWith("ru_")) { // + any future non-Latin i18ns
339  QFont font(QApplication::font());
340  QString preferredFamily = "Segoe UI";
341  font.setFamily(preferredFamily);
342  if (QFontInfo(font).family() == preferredFamily) {
343  font.setPointSize(9);
344  QApplication::setFont(font);
345  }
346  }
347 #endif
348  }
349  settings.endGroup();
350 
351  settings.beginGroup("Preferences");
352  // Default to using Piper server; can change in preferences
353  if (!settings.contains("run-vamp-plugins-in-process")) {
354  settings.setValue("run-vamp-plugins-in-process", false);
355  }
356  settings.endGroup();
357 
358  settings.beginGroup("Preferences");
359  if (showSplash) {
360  if (!settings.value("show-splash", true).toBool()) {
361  showSplash = false;
362  }
363  }
364  settings.endGroup();
365 
366  if (showSplash) {
367  splash = new SVSplash();
368  splash->show();
369  QTimer::singleShot(5000, splash, SLOT(hide()));
370  application.processEvents();
371  }
372 
373  settings.beginGroup("RDF");
374  QStringList list;
375  bool absent = !(settings.contains("rdf-indices"));
376  QString plugIndex("http://www.vamp-plugins.org/rdf/plugins/index.txt");
377  QString packsIndex("http://www.vamp-plugins.org/rdf/packs/index.txt");
378  if (absent) {
379  list << plugIndex;
380  list << packsIndex;
381  } else {
382  list = settings.value("rdf-indices").toStringList();
383  if (!settings.contains("rdf-indices-refreshed-for-4.1")) {
384  // Packs introduced
385  if (!list.contains(packsIndex)) {
386  list << packsIndex;
387  }
388  settings.setValue("rdf-indices-refreshed-for-4.1", true);
389  }
390  }
391  settings.setValue("rdf-indices", list);
392  settings.endGroup();
393 
394  PluginPathSetter::initialiseEnvironmentVariables();
395 
396  QIcon icon;
397  int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
398  for (int i = 0; i < int(sizeof(sizes)/sizeof(sizes[0])); ++i) {
399  icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
400  }
401  QApplication::setWindowIcon(icon);
402 
403  if (showSplash) {
404  application.processEvents();
405  }
406 
407  QTranslator qtTranslator;
408  QString qtTrName = QString("qt_%1").arg(language);
409  SVDEBUG << "Loading " << qtTrName << "... ";
410  bool success = false;
411  if (!(success = qtTranslator.load(qtTrName))) {
412  QString qtDir = getenv("QTDIR");
413  if (qtDir != "") {
414  success = qtTranslator.load
415  (qtTrName, QDir(qtDir).filePath("translations"));
416  }
417  }
418  if (!success) {
419  SVDEBUG << "Failed\nFailed to load Qt translation for locale" << endl;
420  } else {
421  cerr << "Done" << endl;
422  }
423  application.installTranslator(&qtTranslator);
424 
425  QTranslator svTranslator;
426  QString svTrName = QString("sonic-visualiser_%1").arg(language);
427  SVDEBUG << "Loading " << svTrName << "... ";
428  svTranslator.load(svTrName, ":i18n");
429  SVDEBUG << "Done" << endl;
430  application.installTranslator(&svTranslator);
431 
432  StoreStartupLocale();
433 
434 #if (QT_VERSION >= 0x050400)
435  SVDEBUG << "Note: SSL library build version is: "
436  << QSslSocket::sslLibraryBuildVersionString()
437  << endl;
438 #endif
439 
440  if (showSplash) {
441  application.processEvents();
442  }
443 
444  // Permit these types to be used as args in queued signal calls
445  qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
446  qRegisterMetaType<ZoomLevel>("ZoomLevel");
447 
448  MainWindow::AudioMode audioMode =
449  MainWindow::AUDIO_PLAYBACK_NOW_RECORD_LATER;
450  MainWindow::MIDIMode midiMode =
451  MainWindow::MIDI_LISTEN;
452 
453  if (!audioOutput) {
454  audioMode = MainWindow::AUDIO_NONE;
455  midiMode = MainWindow::MIDI_NONE;
456  }
457 
458  MainWindow *gui = new MainWindow(audioMode, midiMode, oscSupport);
459  application.setMainWindow(gui);
460 
461  InteractiveFileFinder::setParentWidget(gui);
462  TransformUserConfigurator::setParentWidget(gui);
463  if (splash) {
464  QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide()));
465  QObject::connect(gui, SIGNAL(hideSplash(QWidget *)),
466  splash, SLOT(finishSplash(QWidget *)));
467  }
468 
469  QScreen *screen = QApplication::primaryScreen();
470  QRect available = screen->availableGeometry();
471 
472  int width = (available.width() * 2) / 3;
473  int height = available.height() / 2;
474  if (height < 450) height = (available.height() * 2) / 3;
475  if (width > height * 2) width = height * 2;
476 
477  settings.beginGroup("MainWindow");
478 
479  QSize size = settings.value("size", QSize(width, height)).toSize();
480  gui->resizeConstrained(size);
481 
482  if (settings.contains("position")) {
483  QRect prevrect(settings.value("position").toPoint(), size);
484  if (!(available & prevrect).isEmpty()) {
485  gui->move(prevrect.topLeft());
486  }
487  }
488 
489  if (settings.value("maximised", false).toBool()) {
490  gui->setWindowState(Qt::WindowMaximized);
491  }
492 
493  settings.endGroup();
494 
495  gui->show();
496 
497  // The MainWindow class seems to have trouble dealing with this if
498  // it tries to adapt to this preference before the constructor is
499  // complete. As a lazy hack, apply it explicitly from here
500  gui->preferenceChanged("Property Box Layout");
501 
502  application.m_readyForFiles = true; // Ready to receive files from e.g. Apple Events
503 
504  for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
505 
506  // Note QCommandLineParser has now pulled out argv[0] and all
507  // the options, so in theory everything here from the very
508  // first arg should be relevant. But let's reject names
509  // starting with "-" just in case.
510 
511  if (i->startsWith('-')) continue;
512 
513  QString path = *i;
514 
515  application.handleFilepathArgument(path, splash);
516  }
517 
518  for (QStringList::iterator i = application.m_filepathQueue.begin();
519  i != application.m_filepathQueue.end(); ++i) {
520  QString path = *i;
521  application.handleFilepathArgument(path, splash);
522  }
523 
524 #ifdef HAVE_FFTW3F
525  settings.beginGroup("FFTWisdom");
526  QString wisdom = settings.value("wisdom").toString();
527  if (wisdom != "") {
528  fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data());
529  }
530 #ifdef HAVE_FFTW3
531  wisdom = settings.value("wisdom_d").toString();
532  if (wisdom != "") {
533  fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
534  }
535 #endif
536  settings.endGroup();
537 #endif
538 
539  QString scriptFile = parser.value("osc-script");
540  if (scriptFile != "") {
541  SVDEBUG << "Note: Cueing OSC script from filename \"" << scriptFile
542  << "\"" << endl;
543  gui->cueOSCScript(scriptFile);
544  }
545 
546  SVDEBUG << "Entering main event loop" << endl;
547 
548  int rv = application.exec();
549 
550  gui->hide();
551 
552  cleanupMutex.lock();
553 
554  if (!cleanedUp) {
555  TransformFactory::deleteInstance();
556  TempDirectory::getInstance()->cleanup();
557  cleanedUp = true;
558  }
559 
560  application.releaseMainWindow();
561 
562 #ifdef HAVE_FFTW3F
563  settings.beginGroup("FFTWisdom");
564  char *cwisdom = fftwf_export_wisdom_to_string();
565  if (cwisdom) {
566  settings.setValue("wisdom", cwisdom);
567  free(cwisdom);
568  }
569 #ifdef HAVE_FFTW3
570  cwisdom = fftw_export_wisdom_to_string();
571  if (cwisdom) {
572  settings.setValue("wisdom_d", cwisdom);
573  free(cwisdom);
574  }
575 #endif
576  settings.endGroup();
577 #endif
578 
579  FileSource::debugReport();
580 
581  delete gui;
582 
583  cleanupMutex.unlock();
584 
585  return rv;
586 }
587 
589 
590 // Avoid warnings/errors with -Wextra because we aren't explicitly
591 // handling all event types (-Wall is OK with this because of the
592 // default but the stricter level insists)
593 #pragma GCC diagnostic ignored "-Wswitch-enum"
594 
595  QString thePath;
596 
597  switch (event->type()) {
598  case QEvent::FileOpen:
599  thePath = static_cast<QFileOpenEvent *>(event)->file();
600  if(m_readyForFiles)
601  handleFilepathArgument(thePath, nullptr);
602  else
603  m_filepathQueue.append(thePath);
604  return true;
605  default:
606  return QApplication::event(event);
607  }
608 }
609 
612  static bool haveSession = false;
613  static bool haveMainModel = false;
614  static bool havePriorCommandLineModel = false;
615 
616  MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
617 
618 #ifdef Q_OS_WIN32
619  path.replace("\\", "/");
620 #endif
621 
622  if (path.endsWith("sv")) {
623  if (!haveSession) {
624  status = m_mainWindow->openSessionPath(path);
625  if (status == MainWindow::FileOpenSucceeded) {
626  haveSession = true;
627  haveMainModel = true;
628  }
629  } else {
630  cerr << "WARNING: Ignoring additional session file argument \"" << path << "\"" << endl;
631  status = MainWindow::FileOpenSucceeded;
632  }
633  }
634  if (status != MainWindow::FileOpenSucceeded) {
635  if (!haveMainModel) {
636  status = m_mainWindow->openPath(path, MainWindow::ReplaceSession);
637  if (status == MainWindow::FileOpenSucceeded) {
638  haveMainModel = true;
639  }
640  } else {
641  if (haveSession && !havePriorCommandLineModel) {
642  status = m_mainWindow->openPath(path, MainWindow::AskUser);
643  if (status == MainWindow::FileOpenSucceeded) {
644  havePriorCommandLineModel = true;
645  }
646  } else {
647  status = m_mainWindow->openPath(path, MainWindow::CreateAdditionalModel);
648  }
649  }
650  }
651  if (status == MainWindow::FileOpenFailed) {
652  if (splash) splash->hide();
653  QMessageBox::critical
654  (m_mainWindow, QMessageBox::tr("Failed to open file"),
655  QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
656  } else if (status == MainWindow::FileOpenWrongMode) {
657  if (splash) splash->hide();
658  QMessageBox::critical
659  (m_mainWindow, QMessageBox::tr("Failed to open file"),
660  QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
661  }
662 }
void handleFilepathArgument(QString path, SVSplash *splash)
Application-global handler for filepaths passed in, e.g.
Definition: main.cpp:611
virtual void commitData(QSessionManager &manager)
Definition: main.cpp:221
int main(int argc, char **argv)
Definition: main.cpp:240
static void signalHandler(int)
Definition: main.cpp:189
bool m_readyForFiles
Definition: main.cpp:231
static QMutex cleanupMutex
Definition: main.cpp:185
SVApplication(int &argc, char **argv)
Definition: main.cpp:209
static bool cleanedUp
Definition: main.cpp:186
QStringList m_filepathQueue
Definition: main.cpp:232
void setMainWindow(MainWindow *mw)
Definition: main.cpp:218
bool event(QEvent *) override
Definition: main.cpp:588
void preferenceChanged(PropertyContainer::PropertyName) override
~SVApplication() override
Definition: main.cpp:216
MainWindow * m_mainWindow
Definition: main.cpp:235
virtual bool commitData(bool mayAskUser)
void releaseMainWindow()
Definition: main.cpp:219