comparison main/main.cpp @ 362:a0ea7bcac15b macness

Now can load file when someone chooses from Mac Finder "Open with > Sonic Visualiser" - but currently *only* if the app is already running.
author Dan Stowell <dan.stowell@eecs.qmul.ac.uk>
date Mon, 11 Oct 2010 14:16:54 +0100
parents 3bb7eef5d45c
children 43205eda54bd
comparison
equal deleted inserted replaced
361:101f05420cba 362:a0ea7bcac15b
20 #include "base/TempDirectory.h" 20 #include "base/TempDirectory.h"
21 #include "base/PropertyContainer.h" 21 #include "base/PropertyContainer.h"
22 #include "base/Preferences.h" 22 #include "base/Preferences.h"
23 #include "widgets/TipDialog.h" 23 #include "widgets/TipDialog.h"
24 #include "transform/TransformFactory.h" 24 #include "transform/TransformFactory.h"
25
26 #include <ApplicationServices/ApplicationServices.h>
27 #include <Carbon/Carbon.h>
25 28
26 #include <QMetaType> 29 #include <QMetaType>
27 #include <QApplication> 30 #include <QApplication>
28 #include <QDesktopWidget> 31 #include <QDesktopWidget>
29 #include <QMessageBox> 32 #include <QMessageBox>
169 of the window. 172 of the window.
170 173
171 */ 174 */
172 175
173 static QMutex cleanupMutex; 176 static QMutex cleanupMutex;
177 static MainWindow *gui;
174 178
175 static void 179 static void
176 signalHandler(int /* signal */) 180 signalHandler(int /* signal */)
177 { 181 {
178 // Avoid this happening more than once across threads 182 // Avoid this happening more than once across threads
200 bool success = m_mainWindow->commitData(mayAskUser); 204 bool success = m_mainWindow->commitData(mayAskUser);
201 manager.release(); 205 manager.release();
202 if (!success) manager.cancel(); 206 if (!success) manager.cancel();
203 } 207 }
204 208
205 protected: 209 /** Application-global handler for filepaths passed in, e.g. as command-line arguments or apple events */
206 MainWindow *m_mainWindow; 210 static void handleFilepathArgument(QString path, QSplashScreen *splash){
207 }; 211 static bool haveSession = false;
208 212 static bool haveMainModel = false;
209 int 213 static bool havePriorCommandLineModel = false;
210 main(int argc, char **argv) 214
211 {
212 #ifdef Q_WS_X11
213 #if QT_VERSION >= 0x040500
214 // QApplication::setGraphicsSystem("raster");
215 #endif
216 #endif
217
218 SVApplication application(argc, argv);
219
220 QStringList args = application.arguments();
221
222 signal(SIGINT, signalHandler);
223 signal(SIGTERM, signalHandler);
224
225 #ifndef Q_WS_WIN32
226 signal(SIGHUP, signalHandler);
227 signal(SIGQUIT, signalHandler);
228 #endif
229
230 svSystemSpecificInitialisation();
231
232 bool audioOutput = true;
233 bool oscSupport = true;
234
235 if (args.contains("--help") || args.contains("-h") || args.contains("-?")) {
236 std::cerr << QApplication::tr(
237 "\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;
238 exit(2);
239 }
240
241 if (args.contains("--no-audio")) audioOutput = false;
242 if (args.contains("--no-osc")) oscSupport = false;
243
244 QApplication::setOrganizationName("sonic-visualiser");
245 QApplication::setOrganizationDomain("sonicvisualiser.org");
246 QApplication::setApplicationName(QApplication::tr("Sonic Visualiser"));
247
248 QSplashScreen *splash = 0;
249
250 QSettings settings;
251
252 settings.beginGroup("Preferences");
253 if (settings.value("show-splash", true).toBool()) {
254 QPixmap pixmap(":/icons/sv-splash.png");
255 QPainter painter;
256 painter.begin(&pixmap);
257 QString text = QString("v%1").arg(SV_VERSION);
258 painter.drawText
259 (pixmap.width() - painter.fontMetrics().width(text) - 10,
260 10 + painter.fontMetrics().ascent(),
261 text);
262 painter.end();
263 splash = new QSplashScreen(pixmap);
264 splash->show();
265 QTimer::singleShot(5000, splash, SLOT(hide()));
266 application.processEvents();
267 }
268 settings.endGroup();
269
270 settings.beginGroup("RDF");
271 if (!settings.contains("rdf-indices")) {
272 QStringList list;
273 list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
274 settings.setValue("rdf-indices", list);
275 }
276 settings.endGroup();
277
278 QIcon icon;
279 int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
280 for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
281 icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
282 }
283 QApplication::setWindowIcon(icon);
284
285 QString language = QLocale::system().name();
286
287 QTranslator qtTranslator;
288 QString qtTrName = QString("qt_%1").arg(language);
289 std::cerr << "Loading " << qtTrName.toStdString() << "... ";
290 bool success = false;
291 if (!(success = qtTranslator.load(qtTrName))) {
292 QString qtDir = getenv("QTDIR");
293 if (qtDir != "") {
294 success = qtTranslator.load
295 (qtTrName, QDir(qtDir).filePath("translations"));
296 }
297 }
298 if (!success) {
299 std::cerr << "Failed\nFailed to load Qt translation for locale" << std::endl;
300 } else {
301 std::cerr << "Done" << std::endl;
302 }
303 application.installTranslator(&qtTranslator);
304
305 QTranslator svTranslator;
306 QString svTrName = QString("sonic-visualiser_%1").arg(language);
307 std::cerr << "Loading " << svTrName.toStdString() << "... ";
308 svTranslator.load(svTrName, ":i18n");
309 std::cerr << "Done" << std::endl;
310 application.installTranslator(&svTranslator);
311
312 StoreStartupLocale();
313
314 // Permit size_t and PropertyName to be used as args in queued signal calls
315 qRegisterMetaType<size_t>("size_t");
316 qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
317
318 MainWindow *gui = new MainWindow(audioOutput, oscSupport);
319 application.setMainWindow(gui);
320 if (splash) {
321 QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide()));
322 }
323
324 QDesktopWidget *desktop = QApplication::desktop();
325 QRect available = desktop->availableGeometry();
326
327 int width = available.width() * 2 / 3;
328 int height = available.height() / 2;
329 if (height < 450) height = available.height() * 2 / 3;
330 if (width > height * 2) width = height * 2;
331
332 settings.beginGroup("MainWindow");
333 QSize size = settings.value("size", QSize(width, height)).toSize();
334 gui->resizeConstrained(size);
335 if (settings.contains("position")) {
336 QRect prevrect(settings.value("position").toPoint(), size);
337 if (!(available & prevrect).isEmpty()) {
338 gui->move(prevrect.topLeft());
339 }
340 }
341 settings.endGroup();
342
343 gui->show();
344
345 // The MainWindow class seems to have trouble dealing with this if
346 // it tries to adapt to this preference before the constructor is
347 // complete. As a lazy hack, apply it explicitly from here
348 gui->preferenceChanged("Property Box Layout");
349
350 bool haveSession = false;
351 bool haveMainModel = false;
352 bool havePriorCommandLineModel = false;
353
354 for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
355
356 MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed; 215 MainWindow::FileOpenStatus status = MainWindow::FileOpenFailed;
357 216
358 if (i == args.begin()) continue;
359 if (i->startsWith('-')) continue;
360
361 QString path = *i;
362
363 if (path.endsWith("sv")) { 217 if (path.endsWith("sv")) {
364 if (!haveSession) { 218 if (!haveSession) {
365 status = gui->openSessionFile(path); 219 status = gui->openSessionFile(path);
366 if (status == MainWindow::FileOpenSucceeded) { 220 if (status == MainWindow::FileOpenSucceeded) {
367 haveSession = true; 221 haveSession = true;
389 } 243 }
390 } 244 }
391 } 245 }
392 if (status == MainWindow::FileOpenFailed) { 246 if (status == MainWindow::FileOpenFailed) {
393 if (splash) splash->hide(); 247 if (splash) splash->hide();
394 QMessageBox::critical 248 QMessageBox::critical
395 (gui, QMessageBox::tr("Failed to open file"), 249 (gui, QMessageBox::tr("Failed to open file"),
396 QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path)); 250 QMessageBox::tr("File or URL \"%1\" could not be opened").arg(path));
397 } else if (status == MainWindow::FileOpenWrongMode) { 251 } else if (status == MainWindow::FileOpenWrongMode) {
398 if (splash) splash->hide(); 252 if (splash) splash->hide();
399 QMessageBox::critical 253 QMessageBox::critical
400 (gui, QMessageBox::tr("Failed to open file"), 254 (gui, QMessageBox::tr("Failed to open file"),
401 QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data")); 255 QMessageBox::tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
402 } 256 }
403 } 257 }
404 258
259 #ifdef Q_WS_MAC
260 static pascal OSErr HandleOpenDocAE (const AppleEvent * theAppleEvent, AppleEvent * /* reply */, SInt32 /* handlerRefcon */) {
261 AEDescList docList;
262 FSRef theFSRef;
263 long count = 0;
264 OSErr err;
265
266 err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, &docList);
267 err = AECountItems(&docList, &count);
268 if(count == 0){
269 std::cerr << "SV warning: received apple open-documents event with no documents (count==0)" << std::endl;
270 AEDisposeDesc(&docList);
271 return noErr;
272 }
273 // NB we could be passed a list of files - however, SV on Mac currently can only open one at a time, so we simply open the first.
274
275 err = AEGetNthPtr(&docList, 1, typeFSRef, NULL, NULL, &theFSRef, sizeof(theFSRef), NULL);
276
277 // Call routine to open document with current reference.
278 if(err == noErr){
279 char uPath[1024];
280 FSRefMakePath(&theFSRef, (UInt8*)uPath, 1023);
281 std::cerr << "SV received Apple Event request to open " << uPath << std::endl;
282 handleFilepathArgument(uPath, NULL);
283 }
284
285 AEDisposeDesc(&docList);
286 return noErr;
287 }
288 static OSErr InstallMacOSEventHandlers(void)
289 {
290 OSErr err;
291 err = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerUPP(HandleOpenDocAE), 0, false);
292 std::cerr << "HandleOpenDocAE installed. " << err << std::endl;
293 return err;
294 }
295 #endif // Q_WS_MAC
296
297 protected:
298 MainWindow *m_mainWindow;
299 };
300
301 int
302 main(int argc, char **argv)
303 {
304 #ifdef Q_WS_X11
305 #if QT_VERSION >= 0x040500
306 // QApplication::setGraphicsSystem("raster");
307 #endif
308 #endif
309
310 SVApplication application(argc, argv);
311
312 QStringList args = application.arguments();
313
314 signal(SIGINT, signalHandler);
315 signal(SIGTERM, signalHandler);
316
317 #ifndef Q_WS_WIN32
318 signal(SIGHUP, signalHandler);
319 signal(SIGQUIT, signalHandler);
320 #endif
321
322 svSystemSpecificInitialisation();
323
324 bool audioOutput = true;
325 bool oscSupport = true;
326
327 if (args.contains("--help") || args.contains("-h") || args.contains("-?")) {
328 std::cerr << QApplication::tr(
329 "\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;
330 exit(2);
331 }
332
333 if (args.contains("--no-audio")) audioOutput = false;
334 if (args.contains("--no-osc")) oscSupport = false;
335
336 QApplication::setOrganizationName("sonic-visualiser");
337 QApplication::setOrganizationDomain("sonicvisualiser.org");
338 QApplication::setApplicationName(QApplication::tr("Sonic Visualiser"));
339
340 QSplashScreen *splash = 0;
341
342 QSettings settings;
343
344 settings.beginGroup("Preferences");
345 if (settings.value("show-splash", true).toBool()) {
346 QPixmap pixmap(":/icons/sv-splash.png");
347 QPainter painter;
348 painter.begin(&pixmap);
349 QString text = QString("v%1").arg(SV_VERSION);
350 painter.drawText
351 (pixmap.width() - painter.fontMetrics().width(text) - 10,
352 10 + painter.fontMetrics().ascent(),
353 text);
354 painter.end();
355 splash = new QSplashScreen(pixmap);
356 splash->show();
357 QTimer::singleShot(5000, splash, SLOT(hide()));
358 application.processEvents();
359 }
360 settings.endGroup();
361
362 settings.beginGroup("RDF");
363 if (!settings.contains("rdf-indices")) {
364 QStringList list;
365 list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
366 settings.setValue("rdf-indices", list);
367 }
368 settings.endGroup();
369
370 QIcon icon;
371 int sizes[] = { 16, 22, 24, 32, 48, 64, 128 };
372 for (int i = 0; i < sizeof(sizes)/sizeof(sizes[0]); ++i) {
373 icon.addFile(QString(":icons/sv-%1x%2.png").arg(sizes[i]).arg(sizes[i]));
374 }
375 QApplication::setWindowIcon(icon);
376
377 QString language = QLocale::system().name();
378
379 QTranslator qtTranslator;
380 QString qtTrName = QString("qt_%1").arg(language);
381 std::cerr << "Loading " << qtTrName.toStdString() << "... ";
382 bool success = false;
383 if (!(success = qtTranslator.load(qtTrName))) {
384 QString qtDir = getenv("QTDIR");
385 if (qtDir != "") {
386 success = qtTranslator.load
387 (qtTrName, QDir(qtDir).filePath("translations"));
388 }
389 }
390 if (!success) {
391 std::cerr << "Failed\nFailed to load Qt translation for locale" << std::endl;
392 } else {
393 std::cerr << "Done" << std::endl;
394 }
395 application.installTranslator(&qtTranslator);
396
397 QTranslator svTranslator;
398 QString svTrName = QString("sonic-visualiser_%1").arg(language);
399 std::cerr << "Loading " << svTrName.toStdString() << "... ";
400 svTranslator.load(svTrName, ":i18n");
401 std::cerr << "Done" << std::endl;
402 application.installTranslator(&svTranslator);
403
404 StoreStartupLocale();
405
406 // Permit size_t and PropertyName to be used as args in queued signal calls
407 qRegisterMetaType<size_t>("size_t");
408 qRegisterMetaType<PropertyContainer::PropertyName>("PropertyContainer::PropertyName");
409
410 gui = new MainWindow(audioOutput, oscSupport);
411 application.setMainWindow(gui);
412 if (splash) {
413 QObject::connect(gui, SIGNAL(hideSplash()), splash, SLOT(hide()));
414 }
415
416 QDesktopWidget *desktop = QApplication::desktop();
417 QRect available = desktop->availableGeometry();
418
419 int width = available.width() * 2 / 3;
420 int height = available.height() / 2;
421 if (height < 450) height = available.height() * 2 / 3;
422 if (width > height * 2) width = height * 2;
423
424 settings.beginGroup("MainWindow");
425 QSize size = settings.value("size", QSize(width, height)).toSize();
426 gui->resizeConstrained(size);
427 if (settings.contains("position")) {
428 QRect prevrect(settings.value("position").toPoint(), size);
429 if (!(available & prevrect).isEmpty()) {
430 gui->move(prevrect.topLeft());
431 }
432 }
433 settings.endGroup();
434
435 #ifdef Q_WS_MAC
436 application.InstallMacOSEventHandlers();
437 #endif // Q_WS_MAC
438
439 gui->show();
440
441 // The MainWindow class seems to have trouble dealing with this if
442 // it tries to adapt to this preference before the constructor is
443 // complete. As a lazy hack, apply it explicitly from here
444 gui->preferenceChanged("Property Box Layout");
445
446 for (QStringList::iterator i = args.begin(); i != args.end(); ++i) {
447
448 if (i == args.begin()) continue;
449 if (i->startsWith('-')) continue;
450
451 QString path = *i;
452
453 SVApplication::handleFilepathArgument(path, splash);
454 }
455
405 #ifdef HAVE_FFTW3F 456 #ifdef HAVE_FFTW3F
406 settings.beginGroup("FFTWisdom"); 457 settings.beginGroup("FFTWisdom");
407 QString wisdom = settings.value("wisdom").toString(); 458 QString wisdom = settings.value("wisdom").toString();
408 if (wisdom != "") { 459 if (wisdom != "") {
409 fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data()); 460 fftwf_import_wisdom_from_string(wisdom.toLocal8Bit().data());