annotate main/main.cpp @ 197:c08c312b2399

* Make RemoteFile far more pervasive, and use it for local files as well so that we can handle both transparently. Make it shallow copy with reference counting, so it can be used by value without having to worry about the cache file lifetime. Use RemoteFile for MainWindow file-open functions, etc
author Chris Cannam
date Thu, 18 Oct 2007 15:31:20 +0000
parents 77cd75905998
children 1871581e4da9
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@0 24
Chris@0 25 #include <QMetaType>
Chris@0 26 #include <QApplication>
Chris@0 27 #include <QDesktopWidget>
Chris@0 28 #include <QMessageBox>
Chris@0 29 #include <QTranslator>
Chris@0 30 #include <QLocale>
Chris@5 31 #include <QSettings>
Chris@7 32 #include <QIcon>
Chris@11 33 #include <QSessionManager>
Chris@165 34 #include <QDir>
Chris@0 35
Chris@0 36 #include <iostream>
Chris@0 37 #include <signal.h>
Chris@0 38
Chris@127 39 /*! \mainpage Sonic Visualiser
Chris@127 40
Chris@127 41 \section interesting Summary of interesting classes
Chris@127 42
Chris@127 43 - Data models: Model and subclasses, e.g. WaveFileModel
Chris@127 44
Chris@127 45 - Graphical layers: Layer and subclasses, displayed on View and its
Chris@127 46 subclass widgets.
Chris@127 47
Chris@127 48 - Main window class, document class, and file parser: MainWindow,
Chris@127 49 Document, SVFileReader
Chris@127 50
Chris@127 51 - Turning one model (e.g. audio) into another (e.g. more audio, or a
Chris@127 52 curve extracted from it): Transform and subclasses
Chris@127 53
Chris@127 54 - Creating the plugins used by transforms: RealTimePluginFactory,
Chris@129 55 FeatureExtractionPluginFactory. See also the API documentation for
Chris@129 56 Vamp feature extraction plugins at
Chris@129 57 http://www.vamp-plugins.org/code-doc/.
Chris@127 58
Chris@127 59 - File reading and writing code: AudioFileReader and subclasses,
Chris@127 60 WavFileWriter, DataFileReader, SVFileReader
Chris@127 61
Chris@127 62 - FFT calculation and cacheing: FFTModel, FFTDataServer
Chris@127 63
Chris@127 64 - Widgets that show groups of editable properties: PropertyBox for
Chris@127 65 layer properties (contained in a PropertyStack), PluginParameterBox
Chris@127 66 for plugins (contained in a PluginParameterDialog)
Chris@127 67
Chris@127 68 - Audio playback: AudioCallbackPlaySource and subclasses,
Chris@127 69 AudioCallbackPlayTarget and subclasses, AudioGenerator
Chris@127 70
Chris@127 71 \section model Data sources: the Model hierarchy
Chris@127 72
Chris@127 73 A Model is something containing, or knowing how to obtain, data.
Chris@127 74
Chris@127 75 For example, WaveFileModel is a model that knows how to get data
Chris@127 76 from an audio file; SparseTimeValueModel is a model containing
Chris@127 77 editable "curve" data.
Chris@127 78
Chris@127 79 Models typically subclass one of a number of abstract subclasses of
Chris@127 80 Model. For example, WaveFileModel subclasses DenseTimeValueModel,
Chris@127 81 which describes an interface for models that have a value at each
Chris@127 82 time point for a given sampling resolution. (Note that
Chris@127 83 WaveFileModel does not actually read the files itself: it uses
Chris@127 84 AudioFileReader classes for that. It just makes data from the
Chris@127 85 files available in a Model.) SparseTimeValueModel uses the
Chris@127 86 SparseModel template class, which provides most of the
Chris@127 87 implementation for models that contain a series of points of some
Chris@127 88 sort -- also used by NoteModel, TextModel, and
Chris@127 89 SparseOneDimensionalModel.
Chris@127 90
Chris@127 91 Everything that goes on the screen originates from a model, via a
Chris@127 92 layer (see below). The models are contained in a Document object.
Chris@127 93 There is no containment hierarchy or ordering of models in the
Chris@127 94 document. One model is the main model, which defines the sample
Chris@127 95 rate for playback.
Chris@127 96
Chris@127 97 A model may also be marked as a "derived" model, which means it was
Chris@127 98 generated from another model using some transform (feature
Chris@127 99 extraction or effect plugin, etc) -- the idea being that they can
Chris@127 100 be re-generated using the same transform if a new source model is
Chris@127 101 loaded.
Chris@127 102
Chris@127 103 \section layer Things that can display data: the Layer hierarchy
Chris@127 104
Chris@127 105 A Layer is something that knows how to draw parts of a model onto a
Chris@127 106 timeline.
Chris@127 107
Chris@127 108 For example, WaveformLayer is a layer which draws waveforms, based
Chris@127 109 on WaveFileModel; TimeValueLayer draws curves, based on
Chris@127 110 SparseTimeValueModel; SpectrogramLayer draws spectrograms, based on
Chris@127 111 WaveFileModel (via FFTModel).
Chris@127 112
Chris@127 113 The most basic functions of a layer are: to draw itself onto a
Chris@127 114 Pane, against a timeline on the x axis; and to permit user
Chris@127 115 interaction. If you were thinking of adding the capability to
Chris@127 116 display a new sort of something, then you would want to add a new
Chris@127 117 layer type. (You may also need a new model type, depending on
Chris@127 118 whether any existing model can capture the data you need.)
Chris@127 119 Depending on the sort of data in question, there are various
Chris@127 120 existing layers that might be appropriate to start from -- for
Chris@127 121 example, a layer that displays images that the user has imported
Chris@127 122 and associated with particular times might have something in common
Chris@127 123 with the existing TextLayer which displays pieces of text that are
Chris@127 124 associated with particular times.
Chris@127 125
Chris@127 126 Although layers are visual objects, they are contained in the
Chris@127 127 Document in Sonic Visualiser rather than being managed together
Chris@127 128 with display widgets. The Sonic Visualiser file format has
Chris@127 129 separate data and layout sections, and the layers are defined in
Chris@127 130 the data section and then referred to in the layout section which
Chris@127 131 determines which layers may go on which panes (see Pane below).
Chris@127 132
Chris@127 133 Once a layer class is defined, some basic data about it needs to be
Chris@127 134 set up in the LayerFactory class, and then it will appear in the
Chris@127 135 menus and so on on the main window.
Chris@127 136
Chris@127 137 \section view Widgets that are used to show layers: The View hierarchy
Chris@127 138
Chris@127 139 A View is a widget that displays a stack of layers. The most
Chris@127 140 important subclass is Pane, the widget that is used to show most of
Chris@127 141 the data in the main window of Sonic Visualiser.
Chris@127 142
Chris@127 143 All a pane really does is contain a set of layers and get them to
Chris@127 144 render themselves (one on top of the other, with the topmost layer
Chris@127 145 being the one that is currently interacted with), cache the
Chris@127 146 results, negotiate user interaction with them, and so on. This is
Chris@127 147 generally fiddly, if not especially interesting. Panes are
Chris@127 148 strictly layout objects and are not stored in the Document class;
Chris@127 149 instead the MainWindow contains a PaneStack widget (the widget that
Chris@127 150 takes up most of Sonic Visualiser's main window) which contains a
Chris@127 151 set of panes stacked vertically.
Chris@127 152
Chris@127 153 Another View subclass is Overview, which is the widget that
Chris@127 154 contains that green waveform showing the entire file at the bottom
Chris@127 155 of the window.
Chris@127 156
Chris@127 157 */
Chris@127 158
Chris@0 159 static QMutex cleanupMutex;
Chris@0 160
Chris@0 161 static void
Chris@0 162 signalHandler(int /* signal */)
Chris@0 163 {
Chris@0 164 // Avoid this happening more than once across threads
Chris@0 165
Chris@0 166 cleanupMutex.lock();
Chris@0 167 std::cerr << "signalHandler: cleaning up and exiting" << std::endl;
Chris@0 168 TempDirectory::getInstance()->cleanup();
Chris@0 169 exit(0); // without releasing mutex
Chris@0 170 }
Chris@0 171
Chris@11 172 class SVApplication : public QApplication
Chris@11 173 {
Chris@11 174 public:
Chris@11 175 SVApplication(int argc, char **argv) :
Chris@11 176 QApplication(argc, argv),
Chris@11 177 m_mainWindow(0) { }
Chris@11 178 virtual ~SVApplication() { }
Chris@11 179
Chris@11 180 void setMainWindow(MainWindow *mw) { m_mainWindow = mw; }
Chris@11 181 void releaseMainWindow() { m_mainWindow = 0; }
Chris@11 182
Chris@11 183 virtual void commitData(QSessionManager &manager) {
Chris@11 184 if (!m_mainWindow) return;
Chris@11 185 bool mayAskUser = manager.allowsInteraction();
Chris@11 186 bool success = m_mainWindow->commitData(mayAskUser);
Chris@11 187 manager.release();
Chris@11 188 if (!success) manager.cancel();
Chris@11 189 }
Chris@11 190
Chris@11 191 protected:
Chris@11 192 MainWindow *m_mainWindow;
Chris@11 193 };
Chris@11 194
Chris@0 195 int
Chris@0 196 main(int argc, char **argv)
Chris@0 197 {
Chris@11 198 SVApplication application(argc, argv);
Chris@0 199
Chris@46 200 QStringList args = application.arguments();
Chris@46 201
Chris@0 202 signal(SIGINT, signalHandler);
Chris@0 203 signal(SIGTERM, signalHandler);
Chris@0 204
Chris@0 205 #ifndef Q_WS_WIN32
Chris@0 206 signal(SIGHUP, signalHandler);
Chris@0 207 signal(SIGQUIT, signalHandler);
Chris@0 208 #endif
Chris@0 209
Chris@0 210 svSystemSpecificInitialisation();
Chris@0 211
Chris@46 212 bool audioOutput = true;
Chris@70 213 bool oscSupport = true;
Chris@70 214
Chris@133 215 if (args.contains("--help") || args.contains("-h") || args.contains("-?")) {
Chris@70 216 std::cerr << QApplication::tr(
Chris@70 217 "\nSonic Visualiser is a program for viewing and exploring audio data\nfor semantic music analysis and annotation.\n\nUsage:\n\n %1 [--no-audio] [--no-osc] [<file> ...]\n\n --no-audio: Do not attempt to open an audio output device\n --no-osc: Do not provide an Open Sound Control port for remote control\n <file>: One or more Sonic Visualiser (.sv) and audio files may be provided.\n").arg(argv[0]).toStdString() << std::endl;
Chris@70 218 exit(2);
Chris@70 219 }
Chris@70 220
Chris@46 221 if (args.contains("--no-audio")) audioOutput = false;
Chris@70 222 if (args.contains("--no-osc")) oscSupport = false;
Chris@46 223
Chris@6 224 QApplication::setOrganizationName("sonic-visualiser");
Chris@5 225 QApplication::setOrganizationDomain("sonicvisualiser.org");
Chris@6 226 QApplication::setApplicationName("sonic-visualiser");
Chris@141 227
Chris@141 228 QIcon icon;
Chris@141 229 int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
Chris@141 230 for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
Chris@141 231 icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
Chris@141 232 }
Chris@141 233 QApplication::setWindowIcon(icon);
Chris@7 234
Chris@0 235 QString language = QLocale::system().name();
Chris@0 236
Chris@0 237 QTranslator qtTranslator;
Chris@0 238 QString qtTrName = QString("qt_%1").arg(language);
Chris@0 239 std::cerr << "Loading " << qtTrName.toStdString() << "..." << std::endl;
Chris@165 240 bool success = false;
Chris@165 241 if (!(success = qtTranslator.load(qtTrName))) {
Chris@165 242 QString qtDir = getenv("QTDIR");
Chris@165 243 if (qtDir != "") {
Chris@165 244 success = qtTranslator.load
Chris@165 245 (qtTrName, QDir(qtDir).filePath("translations"));
Chris@165 246 }
Chris@165 247 }
Chris@165 248 if (!success) {
Chris@165 249 std::cerr << "Failed to load Qt translation for locale" << std::endl;
Chris@165 250 }
Chris@0 251 application.installTranslator(&qtTranslator);
Chris@0 252
Chris@0 253 QTranslator svTranslator;
Chris@0 254 QString svTrName = QString("sonic-visualiser_%1").arg(language);
Chris@0 255 std::cerr << "Loading " << svTrName.toStdString() << "..." << std::endl;
Chris@0 256 svTranslator.load(svTrName, ":i18n");
Chris@0 257 application.installTranslator(&svTranslator);
Chris@0 258
Chris@187 259 StoreStartupLocale();
Chris@187 260
Chris@0 261 // Permit size_t and PropertyName to be used as args in queued signal calls
Chris@0 262 qRegisterMetaType<size_t>("size_t");
Chris@0 263 qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
Chris@0 264
Chris@70 265 MainWindow gui(audioOutput, oscSupport);
Chris@11 266 application.setMainWindow(&gui);
Chris@0 267
Chris@0 268 QDesktopWidget *desktop = QApplication::desktop();
Chris@0 269 QRect available = desktop->availableGeometry();
Chris@0 270
Chris@0 271 int width = available.width() * 2 / 3;
Chris@0 272 int height = available.height() / 2;
Chris@0 273 if (height < 450) height = available.height() * 2 / 3;
Chris@0 274 if (width > height * 2) width = height * 2;
Chris@0 275
Chris@5 276 QSettings settings;
Chris@5 277 settings.beginGroup("MainWindow");
Chris@5 278 QSize size = settings.value("size", QSize(width, height)).toSize();
Chris@5 279 gui.resize(size);
Chris@5 280 if (settings.contains("position")) {
Chris@5 281 gui.move(settings.value("position").toPoint());
Chris@5 282 }
Chris@5 283 settings.endGroup();
Chris@5 284
Chris@0 285 gui.show();
Chris@64 286
Chris@118 287 // The MainWindow class seems to have trouble dealing with this if
Chris@118 288 // it tries to adapt to this preference before the constructor is
Chris@118 289 // complete. As a lazy hack, apply it explicitly from here
Chris@118 290 gui.preferenceChanged("Property Box Layout");
Chris@118 291
Chris@54 292 bool haveSession = false;
Chris@54 293 bool haveMainModel = false;
Chris@145 294 bool havePriorCommandLineModel = false;
Chris@46 295
Chris@54 296 for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
Chris@54 297
Chris@83 298 MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
Chris@54 299
Chris@54 300 if (i == args.begin()) continue;
Chris@54 301 if (i->startsWith('-')) continue;
Chris@54 302
Chris@54 303 QString path = *i;
Chris@54 304
Chris@54 305 if (path.endsWith("sv")) {
Chris@54 306 if (!haveSession) {
Chris@197 307 status = gui.openSession(path);
Chris@82 308 if (status == MainWindow::FileOpenSucceeded) {
Chris@54 309 haveSession = true;
Chris@54 310 haveMainModel = true;
Chris@54 311 }
Chris@54 312 } else {
Chris@54 313 std::cerr << "WARNING: Ignoring additional session file argument \"" << path.toStdString() << "\"" << std::endl;
Chris@82 314 status = MainWindow::FileOpenSucceeded;
Chris@54 315 }
Chris@54 316 }
Chris@82 317 if (status != MainWindow::FileOpenSucceeded) {
Chris@54 318 if (!haveMainModel) {
Chris@197 319 status = gui.open(path, MainWindow::ReplaceMainModel);
Chris@145 320 if (status == MainWindow::FileOpenSucceeded) {
Chris@145 321 haveMainModel = true;
Chris@145 322 }
Chris@54 323 } else {
Chris@145 324 if (haveSession && !havePriorCommandLineModel) {
Chris@197 325 status = gui.open(path, MainWindow::AskUser);
Chris@145 326 if (status == MainWindow::FileOpenSucceeded) {
Chris@145 327 havePriorCommandLineModel = true;
Chris@145 328 }
Chris@145 329 } else {
Chris@197 330 status = gui.open(path, MainWindow::CreateAdditionalModel);
Chris@145 331 }
Chris@54 332 }
Chris@54 333 }
Chris@82 334 if (status == MainWindow::FileOpenFailed) {
Chris@54 335 QMessageBox::critical
Chris@54 336 (&gui, QMessageBox::tr("Failed to open file"),
Chris@197 337 QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
Chris@54 338 }
Chris@180 339 }
Chris@180 340
Chris@180 341
Chris@180 342
Chris@123 343 /*
Chris@120 344 TipDialog tipDialog;
Chris@120 345 if (tipDialog.isOK()) {
Chris@120 346 tipDialog.exec();
Chris@120 347 }
Chris@123 348 */
Chris@0 349 int rv = application.exec();
Chris@137 350 // std::cerr << "application.exec() returned " << rv << std::endl;
Chris@0 351
Chris@0 352 cleanupMutex.lock();
Chris@0 353 TempDirectory::getInstance()->cleanup();
Chris@11 354 application.releaseMainWindow();
Chris@5 355
Chris@0 356 return rv;
Chris@0 357 }