Chris@529: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@529: 
Chris@529: /*
Chris@529:     Sonic Visualiser
Chris@529:     An audio file viewer and annotation editor.
Chris@529:     Centre for Digital Music, Queen Mary, University of London.
Chris@529:     This file copyright 2007 QMUL.
Chris@529:     
Chris@529:     This program is free software; you can redistribute it and/or
Chris@529:     modify it under the terms of the GNU General Public License as
Chris@529:     published by the Free Software Foundation; either version 2 of the
Chris@529:     License, or (at your option) any later version.  See the file
Chris@529:     COPYING included with this distribution for more information.
Chris@529: */
Chris@529: 
Chris@529: #include "InteractiveFileFinder.h"
Chris@529: #include "data/fileio/FileSource.h"
Chris@529: #include "data/fileio/AudioFileReaderFactory.h"
Chris@529: #include "data/fileio/DataFileReaderFactory.h"
Chris@529: #include "rdf/RDFImporter.h"
Chris@529: #include "rdf/RDFExporter.h"
Chris@1296: #include "system/System.h"
Chris@529: 
Chris@529: #include <QFileInfo>
Chris@529: #include <QMessageBox>
Chris@529: #include <QFileDialog>
Chris@529: #include <QInputDialog>
Chris@529: #include <QImageReader>
Chris@529: #include <QSettings>
Chris@529: 
Chris@529: #include <iostream>
Chris@529: 
Chris@529: InteractiveFileFinder 
Chris@529: InteractiveFileFinder::m_instance;
Chris@529: 
Chris@529: InteractiveFileFinder::InteractiveFileFinder() :
Chris@760:     m_sessionExtension("sv"),
Chris@831:     m_lastLocatedLocation(""),
Chris@1408:     m_parent(nullptr)
Chris@529: {
Chris@529:     FileFinder::registerFileFinder(this);
Chris@529: }
Chris@529: 
Chris@529: InteractiveFileFinder::~InteractiveFileFinder()
Chris@529: {
Chris@529: }
Chris@529: 
Chris@760: void
Chris@831: InteractiveFileFinder::setParentWidget(QWidget *parent)
Chris@831: {
Chris@831:     getInstance()->m_parent = parent;
Chris@831: }
Chris@831: 
Chris@831: void
Chris@760: InteractiveFileFinder::setApplicationSessionExtension(QString extension)
Chris@760: {
Chris@760:     m_sessionExtension = extension;
Chris@760: }
Chris@760: 
Chris@529: QString
Chris@1440: InteractiveFileFinder::getOpenFileName(FileType type,
Chris@1440:                                        QString fallbackLocation)
Chris@1440: {
Chris@1440:     QStringList names = getOpenFileNames(type,
Chris@1440:                                          fallbackLocation,
Chris@1440:                                          false);
Chris@1440:     if (names.empty()) return "";
Chris@1440:     else return names[0];
Chris@1440: }
Chris@1440: 
Chris@1440: QStringList
Chris@1440: InteractiveFileFinder::getOpenFileNames(FileType type,
Chris@1440:                                         QString fallbackLocation)
Chris@1440: {
Chris@1440:     return getOpenFileNames(type,
Chris@1440:                             fallbackLocation,
Chris@1440:                             true);
Chris@1440: }
Chris@1440: 
Chris@1440: QStringList
Chris@1440: InteractiveFileFinder::getOpenFileNames(FileType type,
Chris@1440:                                         QString fallbackLocation,
Chris@1440:                                         bool multiple)
Chris@529: {
Chris@778:     QString settingsKeyStub;
Chris@529:     QString lastPath = fallbackLocation;
Chris@529:     
Chris@1440:     QString title;
Chris@1440:     if (multiple) {
Chris@1440:         title = tr("Select one or more files");
Chris@1440:     } else {
Chris@1440:         title = tr("Select file");
Chris@1440:     }
Chris@529:     QString filter = tr("All files (*.*)");
Chris@529: 
Chris@1440:     QStringList names;
Chris@1440:     
Chris@529:     switch (type) {
Chris@529: 
Chris@529:     case SessionFile:
Chris@778:         settingsKeyStub = "session";
Chris@1440:         if (multiple) {
Chris@1440:             title = tr("Select one or more session files");
Chris@1440:         } else {
Chris@1440:             title = tr("Select a session file");
Chris@1440:         }
Chris@1421:         filter = tr("%1 session files (*.%2)\nRDF files (%3)\nAll files (*.*)")
Chris@760:             .arg(QApplication::applicationName())
Chris@760:             .arg(m_sessionExtension)
Chris@760:             .arg(RDFImporter::getKnownExtensions());
Chris@529:         break;
Chris@529: 
Chris@529:     case AudioFile:
Chris@778:         settingsKeyStub = "audio";
Chris@1440:         if (multiple) {
Chris@1440:             title = tr("Select one or more audio files");
Chris@1440:         } else {
Chris@1440:             title = tr("Select an audio file");
Chris@1440:         }
Chris@529:         filter = tr("Audio files (%1)\nAll files (*.*)")
Chris@529:             .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@529:         break;
Chris@529: 
Chris@529:     case LayerFile:
Chris@778:         settingsKeyStub = "layer";
Chris@529:         filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
Chris@529:             .arg(DataFileReaderFactory::getKnownExtensions())
Chris@529:             .arg(RDFImporter::getKnownExtensions());
Chris@529:         break;
Chris@529: 
Chris@529:     case LayerFileNoMidi:
Chris@778:         settingsKeyStub = "layer";
Chris@529:         filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
Chris@529:             .arg(DataFileReaderFactory::getKnownExtensions())
Chris@529:             .arg(RDFImporter::getKnownExtensions());
Chris@529:         break;
Chris@529: 
Chris@705:     case LayerFileNonSV:
Chris@778:         settingsKeyStub = "layer";
Chris@705:         filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
Chris@705:             .arg(DataFileReaderFactory::getKnownExtensions())
Chris@705:             .arg(RDFImporter::getKnownExtensions());
Chris@705:         break;
Chris@705: 
Chris@705:     case LayerFileNoMidiNonSV:
Chris@778:         settingsKeyStub = "layer";
Chris@705:         filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
Chris@705:             .arg(DataFileReaderFactory::getKnownExtensions())
Chris@705:             .arg(RDFImporter::getKnownExtensions());
Chris@705:         break;
Chris@705: 
Chris@529:     case SessionOrAudioFile:
Chris@778:         settingsKeyStub = "last";
Chris@1421:         filter = tr("All supported files (*.%1 %2 %3)\n%4 session files (*.%1)\nAudio files (%3)\nRDF files (%2)\nAll files (*.*)")
Chris@1421:             .arg(m_sessionExtension)
Chris@529:             .arg(RDFImporter::getKnownExtensions())
Chris@760:             .arg(AudioFileReaderFactory::getKnownExtensions())
Chris@1421:             .arg(QApplication::applicationName());
Chris@529:         break;
Chris@529: 
Chris@529:     case ImageFile:
Chris@778:         settingsKeyStub = "image";
Chris@529:         {
Chris@529:             QStringList fmts;
Chris@529:             QList<QByteArray> formats = QImageReader::supportedImageFormats();
Chris@529:             for (QList<QByteArray>::iterator i = formats.begin();
Chris@529:                  i != formats.end(); ++i) {
Chris@529:                 fmts.push_back(QString("*.%1")
Chris@529:                                .arg(QString::fromLocal8Bit(*i).toLower()));
Chris@529:             }
Chris@529:             filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" "));
Chris@529:         }
Chris@529:         break;
Chris@529: 
Chris@1202:     case SVGFile:
Chris@1202:         settingsKeyStub = "svg";
Chris@1202:         filter = tr("Scalable Vector Graphics files (*.svg)\nAll files (*.*)");
Chris@1202:         break;
Chris@1202: 
Chris@672:     case CSVFile:
Chris@778:         settingsKeyStub = "layer";
Chris@672:         filter = tr("Comma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)");
Chris@672:         break;
Chris@672: 
Chris@529:     case AnyFile:
Chris@778:         settingsKeyStub = "last";
Chris@1421:         filter = tr("All supported files (*.%1 %2 %3 %4)\n%5 session files (*.%1)\nAudio files (%2)\nLayer files (%3)\nRDF files (%4)\nAll files (*.*)")
Chris@1421:             .arg(m_sessionExtension)
Chris@529:             .arg(AudioFileReaderFactory::getKnownExtensions())
Chris@529:             .arg(DataFileReaderFactory::getKnownExtensions())
Chris@760:             .arg(RDFImporter::getKnownExtensions())
Chris@1421:             .arg(QApplication::applicationName());
Chris@529:         break;
Chris@529:     };
Chris@529: 
Chris@529:     if (lastPath == "") {
Chris@1296:         std::string home;
Chris@1296:         if (getEnvUtf8("HOME", home)) {
Chris@1296:             lastPath = QString::fromStdString(home);
Chris@1296:         } else {
Chris@1296:             lastPath = ".";
Chris@1296:         }
Chris@529:     } else if (QFileInfo(lastPath).isDir()) {
Chris@529:         lastPath = QFileInfo(lastPath).canonicalPath();
Chris@529:     } else {
Chris@529:         lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@529:     }
Chris@529: 
Chris@529:     QSettings settings;
Chris@529:     settings.beginGroup("FileFinder");
Chris@778:     lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
Chris@529: 
Chris@529:     // Use our own QFileDialog just for symmetry with getSaveFileName below
Chris@529: 
Chris@831:     QFileDialog dialog(m_parent);
Chris@616:     dialog.setNameFilters(filter.split('\n'));
Chris@529:     dialog.setWindowTitle(title);
Chris@529:     dialog.setDirectory(lastPath);
Chris@529: 
Chris@529:     dialog.setAcceptMode(QFileDialog::AcceptOpen);
Chris@1440: 
Chris@1440:     if (multiple) {
Chris@1440:         dialog.setFileMode(QFileDialog::ExistingFiles);
Chris@1440:     } else {
Chris@1440:         dialog.setFileMode(QFileDialog::ExistingFile);
Chris@1440:     }
Chris@1440: 
Chris@1440:     QString testPath = "";
Chris@1440:     QString pathToRemember = "";
Chris@529:     
Chris@529:     if (dialog.exec()) {
Chris@1440:         names = dialog.selectedFiles();
Chris@529:         
Chris@1440:         if (!multiple && !names.empty()) {
Chris@1440:             testPath = *names.begin();
Chris@1440:             QFileInfo fi(testPath);
Chris@529:         
Chris@1440:             if (!fi.exists()) {
Chris@1440:                 QMessageBox::critical(nullptr, tr("File does not exist"),
Chris@1440:                                       tr("<b>File not found</b><p>File \"%1\" does not exist").arg(testPath));
Chris@529:             
Chris@1440:             } else if (!fi.isReadable()) {
Chris@529:             
Chris@1440:                 QMessageBox::critical(nullptr, tr("File is not readable"),
Chris@1440:                                       tr("<b>File is not readable</b><p>File \"%1\" can not be read").arg(testPath));
Chris@529:             
Chris@1440:             } else if (fi.isDir()) {
Chris@529:             
Chris@1440:                 QMessageBox::critical(nullptr, tr("Directory selected"),
Chris@1440:                                       tr("<b>Directory selected</b><p>File \"%1\" is a directory").arg(testPath));
Chris@1440: 
Chris@1440:             } else if (!fi.isFile()) {
Chris@529:             
Chris@1440:                 QMessageBox::critical(nullptr, tr("Non-file selected"),
Chris@1440:                                       tr("<b>Not a file</b><p>Path \"%1\" is not a file").arg(testPath));
Chris@1440:             
Chris@1440:             } else if (fi.size() == 0) {
Chris@1440:             
Chris@1440:                 QMessageBox::critical(nullptr, tr("File is empty"),
Chris@1440:                                       tr("<b>File is empty</b><p>File \"%1\" is empty").arg(testPath));
Chris@529: 
Chris@1440:             } else {
Chris@1440:                 pathToRemember = testPath;
Chris@1440:             }
Chris@1440:         }
Chris@529:     }
Chris@529: 
Chris@1440:     if (pathToRemember != "") {
Chris@778:         settings.setValue(settingsKeyStub + "path",
Chris@1440:                           QFileInfo(pathToRemember)
Chris@1440:                           .absoluteDir()
Chris@1440:                           .canonicalPath());
Chris@529:     }
Chris@529:     
Chris@1440:     return names;
Chris@529: }
Chris@529: 
Chris@529: QString
Chris@794: InteractiveFileFinder::getSaveFileName(FileType type, 
Chris@794:                                        QString fallbackLocation)
Chris@529: {
Chris@778:     QString settingsKeyStub;
Chris@529:     QString lastPath = fallbackLocation;
Chris@529:     
Chris@529:     QString title = tr("Select file");
Chris@529:     QString filter = tr("All files (*.*)");
Chris@529: 
Chris@529:     switch (type) {
Chris@529: 
Chris@529:     case SessionFile:
Chris@778:         settingsKeyStub = "savesession";
Chris@529:         title = tr("Select a session file");
Chris@760:         filter = tr("%1 session files (*.%2)\nAll files (*.*)")
Chris@1421:             .arg(QApplication::applicationName())
Chris@1421:             .arg(m_sessionExtension);
Chris@529:         break;
Chris@529: 
Chris@529:     case AudioFile:
Chris@778:         settingsKeyStub = "saveaudio";
Chris@529:         title = "Select an audio file";
Chris@529:         title = tr("Select a file to export to");
Chris@529:         filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
Chris@529:         break;
Chris@529: 
Chris@529:     case LayerFile:
Chris@778:         settingsKeyStub = "savelayer";
Chris@529:         title = tr("Select a file to export to");
Chris@1421:         filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
Chris@1421:             .arg(RDFExporter::getSupportedExtensions());
Chris@529:         break;
Chris@529: 
Chris@529:     case LayerFileNoMidi:
Chris@778:         settingsKeyStub = "savelayer";
Chris@529:         title = tr("Select a file to export to");
Chris@1421:         filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)")
Chris@1421:             .arg(RDFExporter::getSupportedExtensions());
Chris@529:         break;
Chris@529: 
Chris@705:     case LayerFileNonSV:
Chris@778:         settingsKeyStub = "savelayer";
Chris@705:         title = tr("Select a file to export to");
Chris@1421:         filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
Chris@1421:             .arg(RDFExporter::getSupportedExtensions());
Chris@705:         break;
Chris@705: 
Chris@705:     case LayerFileNoMidiNonSV:
Chris@778:         settingsKeyStub = "savelayer";
Chris@705:         title = tr("Select a file to export to");
Chris@1421:         filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)")
Chris@1421:             .arg(RDFExporter::getSupportedExtensions());
Chris@705:         break;
Chris@705: 
Chris@529:     case SessionOrAudioFile:
Chris@682:         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << endl;
Chris@529:         abort();
Chris@529: 
Chris@529:     case ImageFile:
Chris@778:         settingsKeyStub = "saveimage";
Chris@529:         title = tr("Select a file to export to");
Chris@529:         filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
Chris@529:         break;
Chris@529: 
Chris@1202:     case SVGFile:
Chris@1202:         settingsKeyStub = "savesvg";
Chris@1202:         title = tr("Select a file to export to");
Chris@1202:         filter = tr("Scalable Vector Graphics files (*.svg)\nAll files (*.*)");
Chris@1202:         break;
Chris@1202: 
Chris@672:     case CSVFile:
Chris@778:         settingsKeyStub = "savelayer";
Chris@672:         title = tr("Select a file to export to");
Chris@672:         filter = tr("Comma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
Chris@672:         break;
Chris@672: 
Chris@529:     case AnyFile:
Chris@682:         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: AnyFile cannot be used here" << endl;
Chris@529:         abort();
Chris@529:     };
Chris@529: 
Chris@529:     if (lastPath == "") {
Chris@1296:         std::string home;
Chris@1296:         if (getEnvUtf8("HOME", home)) {
Chris@1296:             lastPath = QString::fromStdString(home);
Chris@1296:         } else {
Chris@1296:             lastPath = ".";
Chris@1296:         }
Chris@529:     } else if (QFileInfo(lastPath).isDir()) {
Chris@529:         lastPath = QFileInfo(lastPath).canonicalPath();
Chris@529:     } else {
Chris@529:         lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@529:     }
Chris@529: 
Chris@529:     QSettings settings;
Chris@529:     settings.beginGroup("FileFinder");
Chris@778:     lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
Chris@529: 
Chris@529:     QString path = "";
Chris@529: 
Chris@529:     // Use our own QFileDialog instead of static functions, as we may
Chris@529:     // need to adjust the file extension based on the selected filter
Chris@529: 
Chris@831:     QFileDialog dialog(m_parent);
Chris@778: 
Chris@778:     QStringList filters = filter.split('\n');
Chris@778: 
Chris@778:     dialog.setNameFilters(filters);
Chris@529:     dialog.setWindowTitle(title);
Chris@529:     dialog.setDirectory(lastPath);
Chris@529:     dialog.setAcceptMode(QFileDialog::AcceptSave);
Chris@529:     dialog.setFileMode(QFileDialog::AnyFile);
Chris@529:     dialog.setConfirmOverwrite(false); // we'll do that
Chris@778:     
Chris@778:     QString defaultSuffix;
Chris@529:     if (type == SessionFile) {
Chris@778:         defaultSuffix = m_sessionExtension;
Chris@529:     } else if (type == AudioFile) {
Chris@778:         defaultSuffix = "wav";
Chris@529:     } else if (type == ImageFile) {
Chris@778:         defaultSuffix = "png";
Chris@1202:     } else if (type == SVGFile) {
Chris@1202:         defaultSuffix = "svg";
Chris@672:     } else if (type == CSVFile) {
Chris@778:         defaultSuffix = "csv";
Chris@778:     }
Chris@778: 
Chris@778:     defaultSuffix = 
Chris@778:         settings.value(settingsKeyStub + "suffix", defaultSuffix).toString();
Chris@778: 
Chris@778:     dialog.setDefaultSuffix(defaultSuffix);
Chris@778: 
Chris@778:     foreach (QString f, filters) {
Chris@778:         if (f.contains("." + defaultSuffix)) {
Chris@778:             dialog.selectNameFilter(f);
Chris@778:         }
Chris@529:     }
Chris@529: 
Chris@529:     bool good = false;
Chris@529: 
Chris@529:     while (!good) {
Chris@529: 
Chris@529:         path = "";
Chris@529:         
Chris@529:         if (!dialog.exec()) break;
Chris@529:         
Chris@529:         QStringList files = dialog.selectedFiles();
Chris@529:         if (files.empty()) break;
Chris@529:         path = *files.begin();
Chris@529:         
Chris@529:         QFileInfo fi(path);
Chris@529: 
Chris@682:         cerr << "type = " << type << ", suffix = " << fi.suffix() << endl;
Chris@529:         
Chris@705:         if ((type == LayerFile || type == LayerFileNoMidi || 
Chris@705:              type == LayerFileNonSV || type == LayerFileNoMidiNonSV)
Chris@529:             && fi.suffix() == "") {
Chris@529:             QString expectedExtension;
Chris@616:             QString selectedFilter = dialog.selectedNameFilter();
Chris@529:             if (selectedFilter.contains(".svl")) {
Chris@529:                 expectedExtension = "svl";
Chris@529:             } else if (selectedFilter.contains(".txt")) {
Chris@529:                 expectedExtension = "txt";
Chris@529:             } else if (selectedFilter.contains(".csv")) {
Chris@529:                 expectedExtension = "csv";
Chris@529:             } else if (selectedFilter.contains(".mid")) {
Chris@529:                 expectedExtension = "mid";
Chris@529:             } else if (selectedFilter.contains(".ttl")) {
Chris@529:                 expectedExtension = "ttl";
Chris@529:             }
Chris@682:             cerr << "expected extension = " << expectedExtension << endl;
Chris@529:             if (expectedExtension != "") {
Chris@529:                 path = QString("%1.%2").arg(path).arg(expectedExtension);
Chris@529:                 fi = QFileInfo(path);
Chris@529:             }
Chris@529:         }
Chris@529:         
Chris@529:         if (fi.isDir()) {
Chris@1408:             QMessageBox::critical(nullptr, tr("Directory selected"),
Chris@529:                                   tr("<b>Directory selected</b><p>File \"%1\" is a directory").arg(path));
Chris@529:             continue;
Chris@529:         }
Chris@529:         
Chris@529:         if (fi.exists()) {
Chris@1408:             if (QMessageBox::question(nullptr, tr("File exists"),
Chris@529:                                       tr("<b>File exists</b><p>The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
Chris@529:                                       QMessageBox::Ok,
Chris@529:                                       QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@529:                 continue;
Chris@529:             }
Chris@529:         }
Chris@529:         
Chris@529:         good = true;
Chris@529:     }
Chris@529:         
Chris@529:     if (path != "") {
Chris@778:         settings.setValue(settingsKeyStub + "path",
Chris@529:                           QFileInfo(path).absoluteDir().canonicalPath());
Chris@778:         settings.setValue(settingsKeyStub + "suffix",
Chris@778:                           QFileInfo(path).suffix());
Chris@529:     }
Chris@529:     
Chris@529:     return path;
Chris@529: }
Chris@529: 
Chris@529: void
Chris@529: InteractiveFileFinder::registerLastOpenedFilePath(FileType type, QString path)
Chris@529: {
Chris@778:     QString settingsKeyStub;
Chris@529: 
Chris@529:     switch (type) {
Chris@529:     case SessionFile:
Chris@778:         settingsKeyStub = "session";
Chris@529:         break;
Chris@529: 
Chris@529:     case AudioFile:
Chris@778:         settingsKeyStub = "audio";
Chris@529:         break;
Chris@529: 
Chris@529:     case LayerFile:
Chris@778:         settingsKeyStub = "layer";
Chris@529:         break;
Chris@529: 
Chris@529:     case LayerFileNoMidi:
Chris@778:         settingsKeyStub = "layer";
Chris@529:         break;
Chris@529: 
Chris@705:     case LayerFileNonSV:
Chris@778:         settingsKeyStub = "layer";
Chris@705:         break;
Chris@705: 
Chris@705:     case LayerFileNoMidiNonSV:
Chris@778:         settingsKeyStub = "layer";
Chris@705:         break;
Chris@705: 
Chris@529:     case SessionOrAudioFile:
Chris@778:         settingsKeyStub = "last";
Chris@529:         break;
Chris@529: 
Chris@529:     case ImageFile:
Chris@778:         settingsKeyStub = "image";
Chris@529:         break;
Chris@529: 
Chris@1202:     case SVGFile:
Chris@1202:         settingsKeyStub = "svg";
Chris@1202:         break;
Chris@1202: 
Chris@672:     case CSVFile:
Chris@778:         settingsKeyStub = "layer";
Chris@672:         break;
Chris@672: 
Chris@529:     case AnyFile:
Chris@778:         settingsKeyStub = "last";
Chris@529:         break;
Chris@529:     }
Chris@529: 
Chris@529:     if (path != "") {
Chris@529:         QSettings settings;
Chris@529:         settings.beginGroup("FileFinder");
Chris@529:         path = QFileInfo(path).absoluteDir().canonicalPath();
Chris@778:         QString suffix = QFileInfo(path).suffix();
Chris@778:         settings.setValue(settingsKeyStub + "path", path);
Chris@778:         settings.setValue(settingsKeyStub + "suffix", suffix);
Chris@529:         settings.setValue("lastpath", path);
Chris@529:     }
Chris@529: }
Chris@529:     
Chris@529: QString
Chris@529: InteractiveFileFinder::find(FileType type, QString location, QString lastKnownLocation)
Chris@529: {
Chris@529:     if (FileSource::canHandleScheme(location)) {
Chris@529:         if (FileSource(location).isAvailable()) {
Chris@587:             SVDEBUG << "InteractiveFileFinder::find: ok, it's available... returning" << endl;
Chris@529:             return location;
Chris@529:         }
Chris@529:     }
Chris@529: 
Chris@529:     if (QFileInfo(location).exists()) return location;
Chris@529: 
Chris@529:     QString foundAt = "";
Chris@529: 
Chris@529:     if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
Chris@529:         return foundAt;
Chris@529:     }
Chris@529: 
Chris@529:     if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
Chris@529:         return foundAt;
Chris@529:     }
Chris@529: 
Chris@529:     return locateInteractive(type, location);
Chris@529: }
Chris@529: 
Chris@529: QString
Chris@529: InteractiveFileFinder::findRelative(QString location, QString relativeTo)
Chris@529: {
Chris@529:     if (relativeTo == "") return "";
Chris@529: 
Chris@587:     SVDEBUG << "Looking for \"" << location << "\" next to \""
Chris@585:               << relativeTo << "\"..." << endl;
Chris@529: 
Chris@529:     QString fileName;
Chris@529:     QString resolved;
Chris@529: 
Chris@529:     if (FileSource::isRemote(location)) {
Chris@529:         fileName = QUrl(location).path().section('/', -1, -1,
Chris@529:                                                  QString::SectionSkipEmpty);
Chris@529:     } else {
Chris@529:         if (QUrl(location).scheme() == "file") {
Chris@529:             location = QUrl(location).toLocalFile();
Chris@529:         }
Chris@529:         fileName = QFileInfo(location).fileName();
Chris@529:     }
Chris@529: 
Chris@529:     if (FileSource::isRemote(relativeTo)) {
Chris@529:         resolved = QUrl(relativeTo).resolved(fileName).toString();
Chris@529:         if (!FileSource(resolved).isAvailable()) resolved = "";
Chris@682:         cerr << "resolved: " << resolved << endl;
Chris@529:     } else {
Chris@529:         if (QUrl(relativeTo).scheme() == "file") {
Chris@529:             relativeTo = QUrl(relativeTo).toLocalFile();
Chris@529:         }
Chris@529:         resolved = QFileInfo(relativeTo).dir().filePath(fileName);
Chris@529:         if (!QFileInfo(resolved).exists() ||
Chris@529:             !QFileInfo(resolved).isFile() ||
Chris@529:             !QFileInfo(resolved).isReadable()) {
Chris@529:             resolved = "";
Chris@529:         }
Chris@529:     }
Chris@529:             
Chris@529:     return resolved;
Chris@529: }
Chris@529: 
Chris@529: QString
Chris@529: InteractiveFileFinder::locateInteractive(FileType type, QString thing)
Chris@529: {
Chris@529:     QString question;
Chris@529:     if (type == AudioFile) {
Chris@529:         question = tr("<b>File not found</b><p>Audio file \"%1\" could not be opened.\nDo you want to locate it?");
Chris@529:     } else {
Chris@529:         question = tr("<b>File not found</b><p>File \"%1\" could not be opened.\nDo you want to locate it?");
Chris@529:     }
Chris@529: 
Chris@529:     QString path = "";
Chris@529:     bool done = false;
Chris@529: 
Chris@529:     while (!done) {
Chris@529: 
Chris@529:         int rv = QMessageBox::question
Chris@1408:             (nullptr, 
Chris@529:              tr("Failed to open file"),
Chris@529:              question.arg(thing),
Chris@529:              tr("Locate file..."),
Chris@529:              tr("Use URL..."),
Chris@529:              tr("Cancel"),
Chris@529:              0, 2);
Chris@529:         
Chris@529:         switch (rv) {
Chris@529: 
Chris@529:         case 0: // Locate file
Chris@529: 
Chris@529:             if (QFileInfo(thing).dir().exists()) {
Chris@529:                 path = QFileInfo(thing).dir().canonicalPath();
Chris@529:             }
Chris@529:             
Chris@529:             path = getOpenFileName(type, path);
Chris@529:             done = (path != "");
Chris@529:             break;
Chris@529: 
Chris@529:         case 1: // Use URL
Chris@529:         {
Chris@529:             bool ok = false;
Chris@529:             path = QInputDialog::getText
Chris@1408:                 (nullptr, tr("Use URL"),
Chris@529:                  tr("Please enter the URL to use for this file:"),
Chris@529:                  QLineEdit::Normal, "", &ok);
Chris@529: 
Chris@529:             if (ok && path != "") {
Chris@529:                 if (FileSource(path).isAvailable()) {
Chris@529:                     done = true;
Chris@529:                 } else {
Chris@529:                     QMessageBox::critical
Chris@1408:                         (nullptr, tr("Failed to open location"),
Chris@529:                          tr("<b>Failed to open location</b><p>URL \"%1\" could not be opened").arg(path));
Chris@529:                     path = "";
Chris@529:                 }
Chris@529:             }
Chris@529:             break;
Chris@529:         }
Chris@529: 
Chris@529:         case 2: // Cancel
Chris@529:             path = "";
Chris@529:             done = true;
Chris@529:             break;
Chris@529:         }
Chris@529:     }
Chris@529: 
Chris@529:     if (path != "") m_lastLocatedLocation = path;
Chris@529:     return path;
Chris@529: }
Chris@529: 
Chris@529: