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 Chris@529: #include Chris@529: #include Chris@529: #include Chris@529: #include Chris@529: #include Chris@529: Chris@529: #include 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 formats = QImageReader::supportedImageFormats(); Chris@529: for (QList::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("File not found

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("File is not readable

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("Directory selected

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("Not a file

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("File is empty

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@1476: dialog.setOption(QFileDialog::DontConfirmOverwrite, true); // 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("Directory selected

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("File exists

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("File not found

Audio file \"%1\" could not be opened.\nDo you want to locate it?"); Chris@529: } else { Chris@529: question = tr("File not found

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("Failed to open location

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: