annotate main/main.cpp @ 406:d70890996156 kiosk

Modifications for the Kiosk edition
author mathieub <mathieu.barthet@eecs.qmul.ac.uk>
date Tue, 22 Mar 2011 14:29:38 +0000
parents 3d1c69b2215e
children b3b9cc8f5466
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@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>
Chris@331 39
Chris@331 40 #include "../version.h"
Chris@0 41
Chris@0 42 #include <iostream>
Chris@0 43 #include <signal.h>
Chris@0 44
Chris@215 45 #ifdef HAVE_FFTW3F
Chris@215 46 #include <fftw3.h>
Chris@215 47 #endif
Chris@215 48
Chris@127 49 /*! \mainpage Sonic Visualiser
Chris@127 50
Chris@127 51 \section interesting Summary of interesting classes
Chris@127 52
Chris@127 53 - Data models: Model and subclasses, e.g. WaveFileModel
Chris@127 54
Chris@127 55 - Graphical layers: Layer and subclasses, displayed on View and its
Chris@127 56 subclass widgets.
Chris@127 57
Chris@127 58 - Main window class, document class, and file parser: MainWindow,
Chris@127 59 Document, SVFileReader
Chris@127 60
Chris@127 61 - Turning one model (e.g. audio) into another (e.g. more audio, or a
Chris@244 62 curve extracted from it): Transform, encapsulating the data that need
Chris@244 63 to be stored to be able to reproduce a given transformation;
Chris@244 64 TransformFactory, for discovering the available types of transform;
Chris@244 65 ModelTransformerFactory, ModelTransformer and subclasses, providing
Chris@244 66 the mechanisms for applying transforms to data models
Chris@127 67
Chris@127 68 - Creating the plugins used by transforms: RealTimePluginFactory,
Chris@129 69 FeatureExtractionPluginFactory. See also the API documentation for
Chris@129 70 Vamp feature extraction plugins at
Chris@129 71 http://www.vamp-plugins.org/code-doc/.
Chris@127 72
Chris@127 73 - File reading and writing code: AudioFileReader and subclasses,
Chris@127 74 WavFileWriter, DataFileReader, SVFileReader
Chris@127 75
Chris@127 76 - FFT calculation and cacheing: FFTModel, FFTDataServer
Chris@127 77
Chris@127 78 - Widgets that show groups of editable properties: PropertyBox for
Chris@127 79 layer properties (contained in a PropertyStack), PluginParameterBox
Chris@127 80 for plugins (contained in a PluginParameterDialog)
Chris@127 81
Chris@127 82 - Audio playback: AudioCallbackPlaySource and subclasses,
Chris@127 83 AudioCallbackPlayTarget and subclasses, AudioGenerator
Chris@127 84
Chris@127 85 \section model Data sources: the Model hierarchy
Chris@127 86
Chris@127 87 A Model is something containing, or knowing how to obtain, data.
Chris@127 88
Chris@127 89 For example, WaveFileModel is a model that knows how to get data
Chris@127 90 from an audio file; SparseTimeValueModel is a model containing
Chris@127 91 editable "curve" data.
Chris@127 92
Chris@127 93 Models typically subclass one of a number of abstract subclasses of
Chris@127 94 Model. For example, WaveFileModel subclasses DenseTimeValueModel,
Chris@127 95 which describes an interface for models that have a value at each
Chris@127 96 time point for a given sampling resolution. (Note that
Chris@127 97 WaveFileModel does not actually read the files itself: it uses
Chris@127 98 AudioFileReader classes for that. It just makes data from the
Chris@127 99 files available in a Model.) SparseTimeValueModel uses the
Chris@127 100 SparseModel template class, which provides most of the
Chris@127 101 implementation for models that contain a series of points of some
Chris@127 102 sort -- also used by NoteModel, TextModel, and
Chris@127 103 SparseOneDimensionalModel.
Chris@127 104
Chris@127 105 Everything that goes on the screen originates from a model, via a
Chris@127 106 layer (see below). The models are contained in a Document object.
Chris@127 107 There is no containment hierarchy or ordering of models in the
Chris@127 108 document. One model is the main model, which defines the sample
Chris@127 109 rate for playback.
Chris@127 110
Chris@127 111 A model may also be marked as a "derived" model, which means it was
Chris@127 112 generated from another model using some transform (feature
Chris@127 113 extraction or effect plugin, etc) -- the idea being that they can
Chris@127 114 be re-generated using the same transform if a new source model is
Chris@127 115 loaded.
Chris@127 116
Chris@127 117 \section layer Things that can display data: the Layer hierarchy
Chris@127 118
Chris@127 119 A Layer is something that knows how to draw parts of a model onto a
Chris@127 120 timeline.
Chris@127 121
Chris@127 122 For example, WaveformLayer is a layer which draws waveforms, based
Chris@127 123 on WaveFileModel; TimeValueLayer draws curves, based on
Chris@127 124 SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on
Chris@127 125 WaveFileModel (via FFTModel).
Chris@127 126
Chris@127 127 The most basic functions of a layer are: to draw itself onto a
Chris@127 128 Pane, against a timeline on the x axis; and to permit user
Chris@127 129 interaction. If you were thinking of adding the capability to
Chris@127 130 display a new sort of something, then you would want to add a new
Chris@127 131 layer type. (You may also need a new model type, depending on
Chris@127 132 whether any existing model can capture the data you need.)
Chris@127 133 Depending on the sort of data in question, there are various
Chris@127 134 existing layers that might be appropriate to start from -- for
Chris@127 135 example, a layer that displays images that the user has imported
Chris@127 136 and associated with particular times might have something in common
Chris@127 137 with the existing TextLayer which displays pieces of text that are
Chris@127 138 associated with particular times.
Chris@127 139
Chris@127 140 Although layers are visual objects, they are contained in the
Chris@127 141 Document in Sonic Visualiser rather than being managed together
Chris@127 142 with display widgets. The Sonic Visualiser file format has
Chris@127 143 separate data and layout sections, and the layers are defined in
Chris@127 144 the data section and then referred to in the layout section which
Chris@127 145 determines which layers may go on which panes (see Pane below).
Chris@127 146
Chris@127 147 Once a layer class is defined, some basic data about it needs to be
Chris@127 148 set up in the LayerFactory class, and then it will appear in the
Chris@127 149 menus and so on on the main window.
Chris@127 150
Chris@127 151 \section view Widgets that are used to show layers: The View hierarchy
Chris@127 152
Chris@127 153 A View is a widget that displays a stack of layers. The most
Chris@127 154 important subclass is Pane, the widget that is used to show most of
Chris@127 155 the data in the main window of Sonic Visualiser.
Chris@127 156
Chris@127 157 All a pane really does is contain a set of layers and get them to
Chris@127 158 render themselves (one on top of the other, with the topmost layer
Chris@127 159 being the one that is currently interacted with), cache the
Chris@127 160 results, negotiate user interaction with them, and so on. This is
Chris@127 161 generally fiddly, if not especially interesting. Panes are
Chris@127 162 strictly layout objects and are not stored in the Document class;
Chris@127 163 instead the MainWindow contains a PaneStack widget (the widget that
Chris@127 164 takes up most of Sonic Visualiser's main window) which contains a
Chris@127 165 set of panes stacked vertically.
Chris@127 166
Chris@127 167 Another View subclass is Overview, which is the widget that
Chris@127 168 contains that green waveform showing the entire file at the bottom
Chris@127 169 of the window.
Chris@127 170
Chris@127 171 */
Chris@127 172
Chris@0 173 static QMutex cleanupMutex;
Chris@0 174
Chris@0 175 static void
Chris@0 176 signalHandler(int /* signal */)
Chris@0 177 {
Chris@0 178 // Avoid this happening more than once across threads
Chris@0 179
Chris@0 180 cleanupMutex.lock();
Chris@0 181 std::cerr << "signalHandler: cleaning up and exiting" << std::endl;
Chris@0 182 TempDirectory::getInstance()->cleanup();
Chris@0 183 exit(0); // without releasing mutex
Chris@0 184 }
Chris@0 185
Chris@11 186 class SVApplication : public QApplication
Chris@11 187 {
Chris@11 188 public:
Chris@296 189 SVApplication(int &argc, char **argv) :
Chris@11 190 QApplication(argc, argv),
Chris@11 191 m_mainWindow(0) { }
Chris@11 192 virtual ~SVApplication() { }
Chris@11 193
Chris@11 194 void setMainWindow(MainWindow *mw) { m_mainWindow = mw; }
Chris@11 195 void releaseMainWindow() { m_mainWindow = 0; }
Chris@11 196
Chris@11 197 virtual void commitData(QSessionManager &manager) {
Chris@11 198 if (!m_mainWindow) return;
Chris@11 199 bool mayAskUser = manager.allowsInteraction();
Chris@11 200 bool success = m_mainWindow->commitData(mayAskUser);
Chris@11 201 manager.release();
Chris@11 202 if (!success) manager.cancel();
Chris@11 203 }
Chris@11 204
Chris@11 205 protected:
Chris@11 206 MainWindow *m_mainWindow;
Chris@11 207 };
Chris@11 208
Chris@0 209 int
Chris@0 210 main(int argc, char **argv)
Chris@0 211 {
Chris@376 212 svSystemSpecificInitialisation();
Chris@376 213
Chris@316 214 #ifdef Q_WS_X11
Chris@317 215 #if QT_VERSION >= 0x040500
Chris@342 216 // QApplication::setGraphicsSystem("raster");
Chris@316 217 #endif
Chris@316 218 #endif
Chris@316 219
Chris@11 220 SVApplication application(argc, argv);
Chris@0 221
Chris@46 222 QStringList args = application.arguments();
Chris@46 223
Chris@0 224 signal(SIGINT, signalHandler);
Chris@0 225 signal(SIGTERM, signalHandler);
Chris@0 226
Chris@0 227 #ifndef Q_WS_WIN32
Chris@0 228 signal(SIGHUP, signalHandler);
Chris@0 229 signal(SIGQUIT, signalHandler);
Chris@0 230 #endif
Chris@0 231
Chris@46 232 bool audioOutput = true;
Chris@70 233 bool oscSupport = true;
Chris@70 234
Chris@133 235 if (args.contains("--help") || args.contains("-h") || args.contains("-?")) {
Chris@70 236 std::cerr << QApplication::tr(
mathieu@406 237 "\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation. The Kiosk edition provides all of the features of the full Sonic Visualiser, except that audio files cannot be saved from it.\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]).toStdString() << std::endl;
Chris@70 238 exit(2);
Chris@70 239 }
Chris@70 240
Chris@46 241 if (args.contains("--no-audio")) audioOutput = false;
Chris@70 242 if (args.contains("--no-osc")) oscSupport = false;
Chris@46 243
Chris@6 244 QApplication::setOrganizationName("sonic-visualiser");
Chris@5 245 QApplication::setOrganizationDomain("sonicvisualiser.org");
mathieu@406 246 QApplication::setApplicationName(QApplication::tr("Sonic Visualiser (Kiosk edition)"));
Chris@141 247
Chris@283 248 QSplashScreen *splash = 0;
Chris@231 249
Chris@231 250 QSettings settings;
Chris@237 251
Chris@237 252 settings.beginGroup("Preferences");
Chris@237 253 if (settings.value("show-splash", true).toBool()) {
Chris@331 254 QPixmap pixmap(":/icons/sv-splash.png");
Chris@331 255 QPainter painter;
Chris@331 256 painter.begin(&pixmap);
Chris@331 257 QString text = QString("v%1").arg(SV_VERSION);
Chris@331 258 painter.drawText
Chris@331 259 (pixmap.width() - painter.fontMetrics().width(text) - 10,
Chris@331 260 10 + painter.fontMetrics().ascent(),
Chris@331 261 text);
Chris@331 262 painter.end();
Chris@283 263 splash = new QSplashScreen(pixmap);
Chris@283 264 splash->show();
Chris@283 265 QTimer::singleShot(5000, splash, SLOT(hide()));
Chris@231 266 application.processEvents();
Chris@231 267 }
Chris@237 268 settings.endGroup();
Chris@231 269
Chris@278 270 settings.beginGroup("RDF");
Chris@278 271 if (!settings.contains("rdf-indices")) {
Chris@278 272 QStringList list;
Chris@278 273 list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
Chris@278 274 settings.setValue("rdf-indices", list);
Chris@278 275 }
Chris@278 276 settings.endGroup();
Chris@278 277
Chris@141 278 QIcon icon;
Chris@141 279 int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
Chris@141 280 for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
Chris@141 281 icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
Chris@141 282 }
Chris@141 283 QApplication::setWindowIcon(icon);
Chris@7 284
Chris@0 285 QString language = QLocale::system().name();
Chris@0 286
Chris@0 287 QTranslator qtTranslator;
Chris@0 288 QString qtTrName = QString("qt_%1").arg(language);
Chris@253 289 std::cerr << "Loading " << qtTrName.toStdString() << "... ";
Chris@165 290 bool success = false;
Chris@165 291 if (!(success = qtTranslator.load(qtTrName))) {
Chris@165 292 QString qtDir = getenv("QTDIR");
Chris@165 293 if (qtDir != "") {
Chris@165 294 success = qtTranslator.load
Chris@165 295 (qtTrName, QDir(qtDir).filePath("translations"));
Chris@165 296 }
Chris@165 297 }
Chris@165 298 if (!success) {
Chris@253 299 std::cerr << "Failed\nFailed to load Qt translation for locale" << std::endl;
Chris@253 300 } else {
Chris@253 301 std::cerr << "Done" << std::endl;
Chris@165 302 }
Chris@0 303 application.installTranslator(&qtTranslator);
Chris@0 304
Chris@0 305 QTranslator svTranslator;
Chris@0 306 QString svTrName = QString("sonic-visualiser_%1").arg(language);
Chris@253 307 std::cerr << "Loading " << svTrName.toStdString() << "... ";
Chris@0 308 svTranslator.load(svTrName, ":i18n");
Chris@253 309 std::cerr << "Done" << std::endl;
Chris@0 310 application.installTranslator(&svTranslator);
Chris@0 311
Chris@187 312 StoreStartupLocale();
Chris@187 313
Chris@0 314 // Permit size_t and PropertyName to be used as args in queued signal calls
Chris@0 315 qRegisterMetaType<size_t>("size_t");
Chris@0 316 qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
Chris@0 317
Chris@222 318 MainWindow *gui = new MainWindow(audioOutput, oscSupport);
Chris@222 319 application.setMainWindow(gui);
Chris@283 320 if (splash) {
Chris@283 321 QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide()));
Chris@283 322 }
Chris@0 323
Chris@0 324 QDesktopWidget *desktop = QApplication::desktop();
Chris@0 325 QRect available = desktop->availableGeometry();
Chris@0 326
Chris@378 327 int width = (available.width() * 2) / 3;
Chris@0 328 int height = available.height() / 2;
Chris@378 329 if (height < 450) height = (available.height() * 2) / 3;
Chris@0 330 if (width > height * 2) width = height * 2;
Chris@0 331
Chris@237 332 settings.beginGroup("MainWindow");
Chris@5 333 QSize size = settings.value("size", QSize(width, height)).toSize();
Chris@319 334 gui->resizeConstrained(size);
Chris@5 335 if (settings.contains("position")) {
Chris@297 336 QRect prevrect(settings.value("position").toPoint(), size);
Chris@297 337 if (!(available & prevrect).isEmpty()) {
Chris@297 338 gui->move(prevrect.topLeft());
Chris@297 339 }
Chris@5 340 }
Chris@5 341 settings.endGroup();
Chris@5 342
Chris@222 343 gui->show();
Chris@64 344
Chris@118 345 // The MainWindow class seems to have trouble dealing with this if
Chris@118 346 // it tries to adapt to this preference before the constructor is
Chris@118 347 // complete. As a lazy hack, apply it explicitly from here
Chris@222 348 gui->preferenceChanged("Property Box Layout");
Chris@118 349
Chris@54 350 bool haveSession = false;
Chris@54 351 bool haveMainModel = false;
Chris@145 352 bool havePriorCommandLineModel = false;
Chris@46 353
Chris@54 354 for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
Chris@54 355
Chris@83 356 MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
Chris@54 357
Chris@54 358 if (i == args.begin()) continue;
Chris@54 359 if (i->startsWith('-')) continue;
Chris@54 360
Chris@54 361 QString path = *i;
Chris@54 362
Chris@54 363 if (path.endsWith("sv")) {
Chris@54 364 if (!haveSession) {
Chris@222 365 status = gui->openSessionFile(path);
Chris@82 366 if (status == MainWindow::FileOpenSucceeded) {
Chris@54 367 haveSession = true;
Chris@54 368 haveMainModel = true;
Chris@54 369 }
Chris@54 370 } else {
Chris@54 371 std::cerr << "WARNING: Ignoring additional session file argument \"" << path.toStdString() << "\"" << std::endl;
Chris@82 372 status = MainWindow::FileOpenSucceeded;
Chris@54 373 }
Chris@54 374 }
Chris@82 375 if (status != MainWindow::FileOpenSucceeded) {
Chris@54 376 if (!haveMainModel) {
Chris@222 377 status = gui->open(path, MainWindow::ReplaceMainModel);
Chris@145 378 if (status == MainWindow::FileOpenSucceeded) {
Chris@145 379 haveMainModel = true;
Chris@145 380 }
Chris@54 381 } else {
Chris@145 382 if (haveSession && !havePriorCommandLineModel) {
Chris@222 383 status = gui->open(path, MainWindow::AskUser);
Chris@145 384 if (status == MainWindow::FileOpenSucceeded) {
Chris@145 385 havePriorCommandLineModel = true;
Chris@145 386 }
Chris@145 387 } else {
Chris@222 388 status = gui->open(path, MainWindow::CreateAdditionalModel);
Chris@145 389 }
Chris@54 390 }
Chris@54 391 }
Chris@82 392 if (status == MainWindow::FileOpenFailed) {
Chris@283 393 if (splash) splash->hide();
Chris@54 394 QMessageBox::critical
Chris@222 395 (gui, QMessageBox::tr("Failed to open file"),
Chris@197 396 QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
Chris@270 397 } else if (status == MainWindow::FileOpenWrongMode) {
Chris@283 398 if (splash) splash->hide();
Chris@270 399 QMessageBox::critical
Chris@270 400 (gui, QMessageBox::tr("Failed to open file"),
Chris@270 401 QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
Chris@54 402 }
Chris@180 403 }
Chris@180 404
Chris@215 405 #ifdef HAVE_FFTW3F
Chris@215 406 settings.beginGroup("FFTWisdom");
Chris@215 407 QString wisdom = settings.value("wisdom").toString();
Chris@215 408 if (wisdom != "") {
Chris@215 409 fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data());
Chris@215 410 }
Chris@267 411 #ifdef HAVE_FFTW3
Chris@267 412 wisdom = settings.value("wisdom_d").toString();
Chris@267 413 if (wisdom != "") {
Chris@267 414 fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
Chris@267 415 }
Chris@267 416 #endif
Chris@215 417 settings.endGroup();
Chris@215 418 #endif
Chris@180 419
Chris@283 420 if (splash) splash->finish(gui);
Chris@283 421 delete splash;
Chris@180 422
Chris@123 423 /*
Chris@120 424 TipDialog tipDialog;
Chris@120 425 if (tipDialog.isOK()) {
Chris@120 426 tipDialog.exec();
Chris@120 427 }
Chris@123 428 */
Chris@0 429 int rv = application.exec();
Chris@0 430
Chris@298 431 gui->hide();
Chris@298 432
Chris@0 433 cleanupMutex.lock();
Chris@332 434
Chris@315 435 TransformFactory::deleteInstance();
Chris@0 436 TempDirectory::getInstance()->cleanup();
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@0 458 return rv;
Chris@0 459 }