comparison main/MainWindow.cpp @ 88:51be0daa1386

Several changes related to referring to remote URLs for sessions and files: * Pull file dialog wrapper functions out from MainWindow into FileFinder * If a file referred to in a session is not found at its expected location, try a few other alternatives (same location as the session file or same location as the last audio file) before asking the user to locate it * Allow user to give a URL when locating an audio file, not just locate on the filesystem * Make wave file models remember the "original" location (e.g. URL) of the audio file, not just the actual location from which the data was loaded (e.g. local copy of that URL) -- when saving a session, use the original location so as not to refer to a temporary file * Clean up incompletely-downloaded local copies of files
author Chris Cannam
date Thu, 11 Jan 2007 13:29:58 +0000
parents 8944f3005a15
children 51ea003f8f99
comparison
equal deleted inserted replaced
87:8944f3005a15 88:51be0daa1386
63 #include "plugin/api/ladspa.h" 63 #include "plugin/api/ladspa.h"
64 #include "plugin/api/dssi.h" 64 #include "plugin/api/dssi.h"
65 65
66 #include <QApplication> 66 #include <QApplication>
67 #include <QPushButton> 67 #include <QPushButton>
68 #include <QFileDialog>
69 #include <QMessageBox> 68 #include <QMessageBox>
70 #include <QGridLayout> 69 #include <QGridLayout>
71 #include <QLabel> 70 #include <QLabel>
72 #include <QAction> 71 #include <QAction>
73 #include <QMenuBar> 72 #include <QMenuBar>
74 #include <QToolBar> 73 #include <QToolBar>
75 #include <QInputDialog> 74 #include <QInputDialog>
76 #include <QStatusBar> 75 #include <QStatusBar>
77 #include <QTreeView> 76 #include <QTreeView>
78 #include <QFile> 77 #include <QFile>
78 #include <QFileInfo>
79 #include <QDir>
79 #include <QTextStream> 80 #include <QTextStream>
80 #include <QProcess> 81 #include <QProcess>
81 #include <QShortcut> 82 #include <QShortcut>
82 #include <QSettings> 83 #include <QSettings>
83 #include <QDateTime> 84 #include <QDateTime>
262 delete m_oscQueue; 263 delete m_oscQueue;
263 Profiles::getInstance()->dump(); 264 Profiles::getInstance()->dump();
264 } 265 }
265 266
266 QString 267 QString
267 MainWindow::getOpenFileName(FileType type) 268 MainWindow::getOpenFileName(FileFinder::FileType type)
268 { 269 {
269 QString settingsKey; 270 FileFinder *ff = FileFinder::getInstance();
270 QString lastPath;
271
272 QString title = tr("Select file");
273 QString filter = tr("All files (*.*)");
274
275 bool canImportLayer = (getMainModel() != 0 &&
276 m_paneStack != 0 &&
277 m_paneStack->getCurrentPane() != 0);
278
279 switch (type) { 271 switch (type) {
280 272 case FileFinder::SessionFile:
281 case SessionFile: 273 return ff->getOpenFileName(type, m_sessionFile);
282 settingsKey = "sessionpath"; 274 case FileFinder::AudioFile:
283 lastPath = m_sessionFile; 275 return ff->getOpenFileName(type, m_audioFile);
284 title = tr("Select a session file"); 276 case FileFinder::LayerFile:
285 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); 277 return ff->getOpenFileName(type, m_sessionFile);
286 break; 278 case FileFinder::SessionOrAudioFile:
287 279 return ff->getOpenFileName(type, m_sessionFile);
288 case AudioFile: 280 case FileFinder::AnyFile:
289 settingsKey = "audiopath"; 281 if (getMainModel() != 0 &&
290 lastPath = m_audioFile; 282 m_paneStack != 0 &&
291 title = "Select an audio file"; 283 m_paneStack->getCurrentPane() != 0) { // can import a layer
292 filter = tr("Audio files (%1)\nAll files (*.*)") 284 return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile);
293 .arg(AudioFileReaderFactory::getKnownExtensions());
294 break;
295
296 case LayerFile:
297 settingsKey = "layerpath";
298 lastPath = m_sessionFile;
299 filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions());
300 break;
301
302 case AnyFile:
303 settingsKey = "lastpath";
304 lastPath = m_sessionFile;
305 if (canImportLayer) {
306 filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)")
307 .arg(AudioFileReaderFactory::getKnownExtensions())
308 .arg(DataFileReaderFactory::getKnownExtensions());
309 } else { 285 } else {
310 filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)") 286 return ff->getOpenFileName(FileFinder::SessionOrAudioFile,
311 .arg(AudioFileReaderFactory::getKnownExtensions()); 287 m_sessionFile);
312 } 288 }
313 break; 289 }
314 }; 290 return "";
315
316 if (lastPath == "") lastPath = m_audioFile;
317 if (lastPath == "") {
318 char *home = getenv("HOME");
319 if (home) lastPath = home;
320 else lastPath = ".";
321 }
322 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
323
324 QSettings settings;
325 settings.beginGroup("MainWindow");
326 lastPath = settings.value(settingsKey, lastPath).toString();
327
328 QString path = "";
329
330 // Use our own QFileDialog just for symmetry with getSaveFileName below
331
332 QFileDialog dialog(this);
333 dialog.setFilters(filter.split('\n'));
334 dialog.setWindowTitle(title);
335 dialog.setDirectory(lastPath);
336
337 dialog.setAcceptMode(QFileDialog::AcceptOpen);
338 dialog.setFileMode(QFileDialog::ExistingFile);
339
340 if (dialog.exec()) {
341 QStringList files = dialog.selectedFiles();
342 if (!files.empty()) path = *files.begin();
343
344 QFileInfo fi(path);
345
346 if (!fi.exists()) {
347
348 QMessageBox::critical(this, tr("File does not exist"),
349 tr("File \"%1\" does not exist").arg(path));
350 path = "";
351
352 } else if (!fi.isReadable()) {
353
354 QMessageBox::critical(this, tr("File is not readable"),
355 tr("File \"%1\" can not be read").arg(path));
356 path = "";
357
358 } else if (fi.isDir()) {
359
360 QMessageBox::critical(this, tr("Directory selected"),
361 tr("File \"%1\" is a directory").arg(path));
362 path = "";
363
364 } else if (!fi.isFile()) {
365
366 QMessageBox::critical(this, tr("Non-file selected"),
367 tr("Path \"%1\" is not a file").arg(path));
368 path = "";
369
370 } else if (fi.size() == 0) {
371
372 QMessageBox::critical(this, tr("File is empty"),
373 tr("File \"%1\" is empty").arg(path));
374 path = "";
375 }
376 }
377
378 if (path != "") {
379 settings.setValue(settingsKey,
380 QFileInfo(path).absoluteDir().canonicalPath());
381 }
382
383 return path;
384 } 291 }
385 292
386 QString 293 QString
387 MainWindow::getSaveFileName(FileType type) 294 MainWindow::getSaveFileName(FileFinder::FileType type)
388 { 295 {
389 QString settingsKey; 296 FileFinder *ff = FileFinder::getInstance();
390 QString lastPath;
391
392 QString title = tr("Select file");
393 QString filter = tr("All files (*.*)");
394
395 switch (type) { 297 switch (type) {
396 298 case FileFinder::SessionFile:
397 case SessionFile: 299 return ff->getSaveFileName(type, m_sessionFile);
398 settingsKey = "savesessionpath"; 300 case FileFinder::AudioFile:
399 lastPath = m_sessionFile; 301 return ff->getSaveFileName(type, m_audioFile);
400 title = tr("Select a session file"); 302 case FileFinder::LayerFile:
401 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); 303 return ff->getSaveFileName(type, m_sessionFile);
402 break; 304 case FileFinder::SessionOrAudioFile:
403 305 return ff->getSaveFileName(type, m_sessionFile);
404 case AudioFile: 306 case FileFinder::AnyFile:
405 settingsKey = "saveaudiopath"; 307 return ff->getSaveFileName(type, m_sessionFile);
406 lastPath = m_audioFile; 308 }
407 title = "Select an audio file"; 309 return "";
408 title = tr("Select a file to export to"); 310 }
409 filter = tr("WAV audio files (*.wav)\nAll files (*.*)"); 311
410 break; 312 void
411 313 MainWindow::registerLastOpenedFilePath(FileFinder::FileType type, QString path)
412 case LayerFile: 314 {
413 settingsKey = "savelayerpath"; 315 FileFinder *ff = FileFinder::getInstance();
414 lastPath = m_sessionFile; 316 ff->registerLastOpenedFilePath(type, path);
415 title = tr("Select a file to export to"); 317 }
416 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)"); 318
417 break;
418
419 case AnyFile:
420 std::cerr << "ERROR: Internal error: MainWindow::getSaveFileName: AnyFile cannot be used here" << std::endl;
421 abort();
422 };
423
424 if (lastPath == "") lastPath = m_audioFile;
425 if (lastPath == "") {
426 char *home = getenv("HOME");
427 if (home) lastPath = home;
428 else lastPath = ".";
429 }
430 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
431
432 QSettings settings;
433 settings.beginGroup("MainWindow");
434 lastPath = settings.value(settingsKey, lastPath).toString();
435
436 QString path = "";
437
438 // Use our own QFileDialog instead of static functions, as we may
439 // need to adjust the file extension based on the selected filter
440
441 QFileDialog dialog(this);
442 dialog.setFilters(filter.split('\n'));
443 dialog.setWindowTitle(title);
444 dialog.setDirectory(lastPath);
445
446 dialog.setAcceptMode(QFileDialog::AcceptSave);
447 dialog.setFileMode(QFileDialog::AnyFile);
448 dialog.setConfirmOverwrite(false); // we'll do that
449
450 if (type == SessionFile) {
451 dialog.setDefaultSuffix("sv");
452 } else if (type == AudioFile) {
453 dialog.setDefaultSuffix("wav");
454 }
455
456 bool good = false;
457
458 while (!good) {
459
460 path = "";
461
462 if (!dialog.exec()) break;
463
464 QStringList files = dialog.selectedFiles();
465 if (files.empty()) break;
466 path = *files.begin();
467
468 QFileInfo fi(path);
469
470 if (type == LayerFile && fi.suffix() == "") {
471 QString expectedExtension;
472 QString selectedFilter = dialog.selectedFilter();
473 if (selectedFilter.contains(".svl")) {
474 expectedExtension = "svl";
475 } else if (selectedFilter.contains(".txt")) {
476 expectedExtension = "txt";
477 } else if (selectedFilter.contains(".csv")) {
478 expectedExtension = "csv";
479 }
480 if (expectedExtension != "") {
481 path = QString("%1.%2").arg(path).arg(expectedExtension);
482 fi = QFileInfo(path);
483 }
484 }
485
486 if (fi.isDir()) {
487 QMessageBox::critical(this, tr("Directory selected"),
488 tr("File \"%1\" is a directory").arg(path));
489 continue;
490 }
491
492 if (fi.exists()) {
493 if (QMessageBox::question(this, tr("File exists"),
494 tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
495 QMessageBox::Ok,
496 QMessageBox::Cancel) != QMessageBox::Ok) {
497 continue;
498 }
499 }
500
501 good = true;
502 }
503
504 if (path != "") {
505 settings.setValue(settingsKey,
506 QFileInfo(path).absoluteDir().canonicalPath());
507 }
508
509 return path;
510 }
511
512 void
513 MainWindow::registerLastOpenedFilePath(FileType type, QString path)
514 {
515 QString settingsKey;
516
517 switch (type) {
518 case SessionFile:
519 settingsKey = "sessionpath";
520 break;
521
522 case AudioFile:
523 settingsKey = "audiopath";
524 break;
525
526 case LayerFile:
527 settingsKey = "layerpath";
528 break;
529
530 case AnyFile:
531 settingsKey = "lastpath";
532 break;
533 }
534
535 if (path != "") {
536 QSettings settings;
537 settings.beginGroup("MainWindow");
538 path = QFileInfo(path).absoluteDir().canonicalPath();
539 settings.setValue(settingsKey, path);
540 settings.setValue("lastpath", path);
541 }
542 }
543
544 void 319 void
545 MainWindow::setupMenus() 320 MainWindow::setupMenus()
546 { 321 {
547 if (!m_mainMenusCreated) { 322 if (!m_mainMenusCreated) {
548 m_rightButtonMenu = new QMenu(); 323 m_rightButtonMenu = new QMenu();
2055 } 1830 }
2056 1831
2057 void 1832 void
2058 MainWindow::importAudio() 1833 MainWindow::importAudio()
2059 { 1834 {
2060 QString path = getOpenFileName(AudioFile); 1835 QString path = getOpenFileName(FileFinder::AudioFile);
2061 1836
2062 if (path != "") { 1837 if (path != "") {
2063 if (openAudioFile(path, ReplaceMainModel) == FileOpenFailed) { 1838 if (openAudioFile(path, ReplaceMainModel) == FileOpenFailed) {
2064 QMessageBox::critical(this, tr("Failed to open file"), 1839 QMessageBox::critical(this, tr("Failed to open file"),
2065 tr("Audio file \"%1\" could not be opened").arg(path)); 1840 tr("Audio file \"%1\" could not be opened").arg(path));
2068 } 1843 }
2069 1844
2070 void 1845 void
2071 MainWindow::importMoreAudio() 1846 MainWindow::importMoreAudio()
2072 { 1847 {
2073 QString path = getOpenFileName(AudioFile); 1848 QString path = getOpenFileName(FileFinder::AudioFile);
2074 1849
2075 if (path != "") { 1850 if (path != "") {
2076 if (openAudioFile(path, CreateAdditionalModel) == FileOpenFailed) { 1851 if (openAudioFile(path, CreateAdditionalModel) == FileOpenFailed) {
2077 QMessageBox::critical(this, tr("Failed to open file"), 1852 QMessageBox::critical(this, tr("Failed to open file"),
2078 tr("Audio file \"%1\" could not be opened").arg(path)); 1853 tr("Audio file \"%1\" could not be opened").arg(path));
2083 void 1858 void
2084 MainWindow::exportAudio() 1859 MainWindow::exportAudio()
2085 { 1860 {
2086 if (!getMainModel()) return; 1861 if (!getMainModel()) return;
2087 1862
2088 QString path = getSaveFileName(AudioFile); 1863 QString path = getSaveFileName(FileFinder::AudioFile);
2089 1864
2090 if (path == "") return; 1865 if (path == "") return;
2091 1866
2092 bool ok = false; 1867 bool ok = false;
2093 QString error; 1868 QString error;
2202 // shouldn't happen, as the menu action should have been disabled 1977 // shouldn't happen, as the menu action should have been disabled
2203 std::cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << std::endl; 1978 std::cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << std::endl;
2204 return; 1979 return;
2205 } 1980 }
2206 1981
2207 QString path = getOpenFileName(LayerFile); 1982 QString path = getOpenFileName(FileFinder::LayerFile);
2208 1983
2209 if (path != "") { 1984 if (path != "") {
2210 1985
2211 if (openLayerFile(path) == FileOpenFailed) { 1986 if (openLayerFile(path) == FileOpenFailed) {
2212 QMessageBox::critical(this, tr("Failed to open file"), 1987 QMessageBox::critical(this, tr("Failed to open file"),
2268 } 2043 }
2269 2044
2270 m_recentFiles.addFile(location); 2045 m_recentFiles.addFile(location);
2271 2046
2272 if (realFile) { 2047 if (realFile) {
2273 registerLastOpenedFilePath(LayerFile, path); // for file dialog 2048 registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog
2274 } 2049 }
2275 2050
2276 return FileOpenSucceeded; 2051 return FileOpenSucceeded;
2277 2052
2278 } else { 2053 } else {
2287 2062
2288 m_document->addLayerToView(pane, newLayer); 2063 m_document->addLayerToView(pane, newLayer);
2289 m_recentFiles.addFile(location); 2064 m_recentFiles.addFile(location);
2290 2065
2291 if (realFile) { 2066 if (realFile) {
2292 registerLastOpenedFilePath(LayerFile, path); // for file dialog 2067 registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog
2293 } 2068 }
2294 2069
2295 return FileOpenSucceeded; 2070 return FileOpenSucceeded;
2296 } 2071 }
2297 } 2072 }
2310 if (!layer) return; 2085 if (!layer) return;
2311 2086
2312 Model *model = layer->getModel(); 2087 Model *model = layer->getModel();
2313 if (!model) return; 2088 if (!model) return;
2314 2089
2315 QString path = getSaveFileName(LayerFile); 2090 QString path = getSaveFileName(FileFinder::LayerFile);
2316 2091
2317 if (path == "") return; 2092 if (path == "") return;
2318 2093
2319 if (QFileInfo(path).suffix() == "") path += ".svl"; 2094 if (QFileInfo(path).suffix() == "") path += ".svl";
2320 2095
2376 return FileOpenFailed; 2151 return FileOpenFailed;
2377 } 2152 }
2378 2153
2379 m_openingAudioFile = true; 2154 m_openingAudioFile = true;
2380 2155
2381 WaveFileModel *newModel = new WaveFileModel(path); 2156 WaveFileModel *newModel = new WaveFileModel(path, location);
2382 2157
2383 if (!newModel->isOK()) { 2158 if (!newModel->isOK()) {
2384 delete newModel; 2159 delete newModel;
2385 m_openingAudioFile = false; 2160 m_openingAudioFile = false;
2386 return FileOpenFailed; 2161 return FileOpenFailed;
2477 } 2252 }
2478 2253
2479 updateMenuStates(); 2254 updateMenuStates();
2480 m_recentFiles.addFile(location); 2255 m_recentFiles.addFile(location);
2481 if (realFile) { 2256 if (realFile) {
2482 registerLastOpenedFilePath(AudioFile, path); // for file dialog 2257 registerLastOpenedFilePath(FileFinder::AudioFile, path); // for file dialog
2483 } 2258 }
2484 m_openingAudioFile = false; 2259 m_openingAudioFile = false;
2485 2260
2486 return FileOpenSucceeded; 2261 return FileOpenSucceeded;
2487 } 2262 }
2616 2391
2617 QString orig = m_audioFile; 2392 QString orig = m_audioFile;
2618 if (orig == "") orig = "."; 2393 if (orig == "") orig = ".";
2619 else orig = QFileInfo(orig).absoluteDir().canonicalPath(); 2394 else orig = QFileInfo(orig).absoluteDir().canonicalPath();
2620 2395
2621 QString path = getOpenFileName(SessionFile); 2396 QString path = getOpenFileName(FileFinder::SessionFile);
2622 2397
2623 if (path.isEmpty()) return; 2398 if (path.isEmpty()) return;
2624 2399
2625 if (openSessionFile(path) == FileOpenFailed) { 2400 if (openSessionFile(path) == FileOpenFailed) {
2626 QMessageBox::critical(this, tr("Failed to open file"), 2401 QMessageBox::critical(this, tr("Failed to open file"),
2637 2412
2638 bool canImportLayer = (getMainModel() != 0 && 2413 bool canImportLayer = (getMainModel() != 0 &&
2639 m_paneStack != 0 && 2414 m_paneStack != 0 &&
2640 m_paneStack->getCurrentPane() != 0); 2415 m_paneStack->getCurrentPane() != 0);
2641 2416
2642 QString path = getOpenFileName(AnyFile); 2417 QString path = getOpenFileName(FileFinder::AnyFile);
2643 2418
2644 if (path.isEmpty()) return; 2419 if (path.isEmpty()) return;
2645 2420
2646 if (path.endsWith(".sv")) { 2421 if (path.endsWith(".sv")) {
2647 2422
2740 return FileOpenFailed; 2515 return FileOpenFailed;
2741 } else { 2516 } else {
2742 RemoteFile rf(url); 2517 RemoteFile rf(url);
2743 rf.wait(); 2518 rf.wait();
2744 if (!rf.isOK()) { 2519 if (!rf.isOK()) {
2745 //!!! need to clean up any partially downloaded file!
2746 QMessageBox::critical(this, tr("File download failed"), 2520 QMessageBox::critical(this, tr("File download failed"),
2747 tr("Failed to download URL \"%1\": %2") 2521 tr("Failed to download URL \"%1\": %2")
2748 .arg(url.toString()).arg(rf.getErrorString())); 2522 .arg(url.toString()).arg(rf.getErrorString()));
2749 return FileOpenFailed; 2523 return FileOpenFailed;
2750 } 2524 }
2751 //!!! and delete the file if we fail to open it here? 2525 FileOpenStatus status;
2752 return openSomeFile(rf.getLocalFilename(), url.toString()); 2526 if ((status = openSomeFile(rf.getLocalFilename(), url.toString())) !=
2527 FileOpenSucceeded) {
2528 rf.deleteLocalFile();
2529 }
2530 return status;
2753 } 2531 }
2754 } 2532 }
2755 2533
2756 MainWindow::FileOpenStatus 2534 MainWindow::FileOpenStatus
2757 MainWindow::openSomeFile(QString path, AudioFileOpenMode mode) 2535 MainWindow::openSomeFile(QString path, AudioFileOpenMode mode)
2833 updateMenuStates(); 2611 updateMenuStates();
2834 2612
2835 m_recentFiles.addFile(location); 2613 m_recentFiles.addFile(location);
2836 2614
2837 if (realFile) { 2615 if (realFile) {
2838 registerLastOpenedFilePath(SessionFile, path); // for file dialog 2616 registerLastOpenedFilePath(FileFinder::SessionFile, path); // for file dialog
2839 } 2617 }
2840 2618
2841 } else { 2619 } else {
2842 setWindowTitle(tr("Sonic Visualiser")); 2620 setWindowTitle(tr("Sonic Visualiser"));
2843 } 2621 }
2953 { 2731 {
2954 QString orig = m_audioFile; 2732 QString orig = m_audioFile;
2955 if (orig == "") orig = "."; 2733 if (orig == "") orig = ".";
2956 else orig = QFileInfo(orig).absoluteDir().canonicalPath(); 2734 else orig = QFileInfo(orig).absoluteDir().canonicalPath();
2957 2735
2958 QString path = getSaveFileName(SessionFile); 2736 QString path = getSaveFileName(FileFinder::SessionFile);
2959 2737
2960 if (path == "") return; 2738 if (path == "") return;
2961 2739
2962 if (!saveSessionFile(path)) { 2740 if (!saveSessionFile(path)) {
2963 QMessageBox::critical(this, tr("Failed to save file"), 2741 QMessageBox::critical(this, tr("Failed to save file"),