annotate main/main.cpp @ 490:bc04d651f8a4

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