Chris@378: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@378: Chris@378: /* Chris@378: Sonic Visualiser Chris@378: An audio file viewer and annotation editor. Chris@378: Centre for Digital Music, Queen Mary, University of London. Chris@378: This file copyright 2007 QMUL. Chris@378: Chris@378: This program is free software; you can redistribute it and/or Chris@378: modify it under the terms of the GNU General Public License as Chris@378: published by the Free Software Foundation; either version 2 of the Chris@378: License, or (at your option) any later version. See the file Chris@378: COPYING included with this distribution for more information. Chris@378: */ Chris@378: Chris@378: #include "FileFinder.h" Chris@378: #include "data/fileio/FileSource.h" Chris@378: #include "data/fileio/AudioFileReaderFactory.h" Chris@378: #include "data/fileio/DataFileReaderFactory.h" Chris@410: #include "rdf/RDFImporter.h" Chris@456: #include "rdf/RDFExporter.h" Chris@378: Chris@378: #include Chris@378: #include Chris@378: #include Chris@378: #include Chris@378: #include Chris@378: #include Chris@378: Chris@378: #include Chris@378: Chris@378: FileFinder * Chris@378: FileFinder::m_instance = 0; Chris@378: Chris@378: FileFinder::FileFinder() : Chris@378: m_lastLocatedLocation("") Chris@378: { Chris@378: } Chris@378: Chris@378: FileFinder::~FileFinder() Chris@378: { Chris@378: } Chris@378: Chris@378: FileFinder * Chris@378: FileFinder::getInstance() Chris@378: { Chris@378: if (m_instance == 0) { Chris@378: m_instance = new FileFinder(); Chris@378: } Chris@378: return m_instance; Chris@378: } Chris@378: Chris@378: QString Chris@378: FileFinder::getOpenFileName(FileType type, QString fallbackLocation) Chris@378: { Chris@378: QString settingsKey; Chris@378: QString lastPath = fallbackLocation; Chris@378: Chris@378: QString title = tr("Select file"); Chris@378: QString filter = tr("All files (*.*)"); Chris@378: Chris@378: switch (type) { Chris@378: Chris@378: case SessionFile: Chris@378: settingsKey = "sessionpath"; Chris@378: title = tr("Select a session file"); Chris@456: filter = tr("Sonic Visualiser session files (*.sv)\nRDF files (%1)\nAll files (*.*)").arg(RDFImporter::getKnownExtensions()); Chris@378: break; Chris@378: Chris@378: case AudioFile: Chris@378: settingsKey = "audiopath"; Chris@378: title = "Select an audio file"; Chris@378: filter = tr("Audio files (%1)\nAll files (*.*)") Chris@378: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@378: break; Chris@378: Chris@378: case LayerFile: Chris@378: settingsKey = "layerpath"; Chris@456: 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@456: .arg(DataFileReaderFactory::getKnownExtensions()) Chris@456: .arg(RDFImporter::getKnownExtensions()); Chris@378: break; Chris@378: Chris@378: case LayerFileNoMidi: Chris@378: settingsKey = "layerpath"; Chris@456: 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@456: .arg(DataFileReaderFactory::getKnownExtensions()) Chris@456: .arg(RDFImporter::getKnownExtensions()); Chris@378: break; Chris@378: Chris@378: case SessionOrAudioFile: Chris@378: settingsKey = "lastpath"; Chris@457: filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)") Chris@456: .arg(RDFImporter::getKnownExtensions()) Chris@378: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@378: break; Chris@378: Chris@378: case ImageFile: Chris@378: settingsKey = "imagepath"; Chris@378: { Chris@378: QStringList fmts; Chris@378: QList formats = QImageReader::supportedImageFormats(); Chris@378: for (QList::iterator i = formats.begin(); Chris@378: i != formats.end(); ++i) { Chris@378: fmts.push_back(QString("*.%1") Chris@378: .arg(QString::fromLocal8Bit(*i).toLower())); Chris@378: } Chris@378: filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" ")); Chris@378: } Chris@378: break; Chris@378: Chris@378: case AnyFile: Chris@378: settingsKey = "lastpath"; Chris@456: filter = tr("All supported files (*.sv %1 %2 %3)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)") Chris@378: .arg(AudioFileReaderFactory::getKnownExtensions()) Chris@410: .arg(DataFileReaderFactory::getKnownExtensions()) Chris@410: .arg(RDFImporter::getKnownExtensions()); Chris@378: break; Chris@378: }; Chris@378: Chris@378: if (lastPath == "") { Chris@378: char *home = getenv("HOME"); Chris@378: if (home) lastPath = home; Chris@378: else lastPath = "."; Chris@378: } else if (QFileInfo(lastPath).isDir()) { Chris@378: lastPath = QFileInfo(lastPath).canonicalPath(); Chris@378: } else { Chris@378: lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); Chris@378: } Chris@378: Chris@378: QSettings settings; Chris@378: settings.beginGroup("FileFinder"); Chris@378: lastPath = settings.value(settingsKey, lastPath).toString(); Chris@378: Chris@378: QString path = ""; Chris@378: Chris@378: // Use our own QFileDialog just for symmetry with getSaveFileName below Chris@378: Chris@378: QFileDialog dialog; Chris@378: dialog.setFilters(filter.split('\n')); Chris@378: dialog.setWindowTitle(title); Chris@378: dialog.setDirectory(lastPath); Chris@378: Chris@378: dialog.setAcceptMode(QFileDialog::AcceptOpen); Chris@378: dialog.setFileMode(QFileDialog::ExistingFile); Chris@378: Chris@378: if (dialog.exec()) { Chris@378: QStringList files = dialog.selectedFiles(); Chris@378: if (!files.empty()) path = *files.begin(); Chris@378: Chris@378: QFileInfo fi(path); Chris@378: Chris@378: if (!fi.exists()) { Chris@378: Chris@378: QMessageBox::critical(0, tr("File does not exist"), Chris@460: tr("File not found

File \"%1\" does not exist").arg(path)); Chris@378: path = ""; Chris@378: Chris@378: } else if (!fi.isReadable()) { Chris@378: Chris@378: QMessageBox::critical(0, tr("File is not readable"), Chris@460: tr("File is not readable

File \"%1\" can not be read").arg(path)); Chris@378: path = ""; Chris@378: Chris@378: } else if (fi.isDir()) { Chris@378: Chris@378: QMessageBox::critical(0, tr("Directory selected"), Chris@460: tr("Directory selected

File \"%1\" is a directory").arg(path)); Chris@378: path = ""; Chris@378: Chris@378: } else if (!fi.isFile()) { Chris@378: Chris@378: QMessageBox::critical(0, tr("Non-file selected"), Chris@460: tr("Not a file

Path \"%1\" is not a file").arg(path)); Chris@378: path = ""; Chris@378: Chris@378: } else if (fi.size() == 0) { Chris@378: Chris@378: QMessageBox::critical(0, tr("File is empty"), Chris@460: tr("File is empty

File \"%1\" is empty").arg(path)); Chris@378: path = ""; Chris@378: } Chris@378: } Chris@378: Chris@378: if (path != "") { Chris@378: settings.setValue(settingsKey, Chris@378: QFileInfo(path).absoluteDir().canonicalPath()); Chris@378: } Chris@378: Chris@378: return path; Chris@378: } Chris@378: Chris@378: QString Chris@378: FileFinder::getSaveFileName(FileType type, QString fallbackLocation) Chris@378: { Chris@378: QString settingsKey; Chris@378: QString lastPath = fallbackLocation; Chris@378: Chris@378: QString title = tr("Select file"); Chris@378: QString filter = tr("All files (*.*)"); Chris@378: Chris@378: switch (type) { Chris@378: Chris@378: case SessionFile: Chris@378: settingsKey = "savesessionpath"; Chris@378: title = tr("Select a session file"); Chris@378: filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); Chris@378: break; Chris@378: Chris@378: case AudioFile: Chris@378: settingsKey = "saveaudiopath"; Chris@378: title = "Select an audio file"; Chris@378: title = tr("Select a file to export to"); Chris@378: filter = tr("WAV audio files (*.wav)\nAll files (*.*)"); Chris@378: break; Chris@378: Chris@378: case LayerFile: Chris@378: settingsKey = "savelayerpath"; Chris@378: title = tr("Select a file to export to"); Chris@456: 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 (*.*)").arg(RDFExporter::getSupportedExtensions()); Chris@378: break; Chris@378: Chris@378: case LayerFileNoMidi: Chris@378: settingsKey = "savelayerpath"; Chris@378: title = tr("Select a file to export to"); Chris@456: filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions()); Chris@378: break; Chris@378: Chris@378: case SessionOrAudioFile: Chris@378: std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl; Chris@378: abort(); Chris@378: Chris@378: case ImageFile: Chris@378: settingsKey = "saveimagepath"; Chris@378: title = tr("Select a file to export to"); Chris@378: filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); Chris@378: break; Chris@378: Chris@378: case AnyFile: Chris@378: std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl; Chris@378: abort(); Chris@378: }; Chris@378: Chris@378: if (lastPath == "") { Chris@378: char *home = getenv("HOME"); Chris@378: if (home) lastPath = home; Chris@378: else lastPath = "."; Chris@378: } else if (QFileInfo(lastPath).isDir()) { Chris@378: lastPath = QFileInfo(lastPath).canonicalPath(); Chris@378: } else { Chris@378: lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); Chris@378: } Chris@378: Chris@378: QSettings settings; Chris@378: settings.beginGroup("FileFinder"); Chris@378: lastPath = settings.value(settingsKey, lastPath).toString(); Chris@378: Chris@378: QString path = ""; Chris@378: Chris@378: // Use our own QFileDialog instead of static functions, as we may Chris@378: // need to adjust the file extension based on the selected filter Chris@378: Chris@378: QFileDialog dialog; Chris@378: dialog.setFilters(filter.split('\n')); Chris@378: dialog.setWindowTitle(title); Chris@378: dialog.setDirectory(lastPath); Chris@378: Chris@378: dialog.setAcceptMode(QFileDialog::AcceptSave); Chris@378: dialog.setFileMode(QFileDialog::AnyFile); Chris@378: dialog.setConfirmOverwrite(false); // we'll do that Chris@378: Chris@378: if (type == SessionFile) { Chris@378: dialog.setDefaultSuffix("sv"); Chris@378: } else if (type == AudioFile) { Chris@378: dialog.setDefaultSuffix("wav"); Chris@378: } else if (type == ImageFile) { Chris@378: dialog.setDefaultSuffix("png"); Chris@378: } Chris@378: Chris@378: bool good = false; Chris@378: Chris@378: while (!good) { Chris@378: Chris@378: path = ""; Chris@378: Chris@378: if (!dialog.exec()) break; Chris@378: Chris@378: QStringList files = dialog.selectedFiles(); Chris@378: if (files.empty()) break; Chris@378: path = *files.begin(); Chris@378: Chris@378: QFileInfo fi(path); Chris@378: Chris@378: std::cerr << "type = " << type << ", suffix = " << fi.suffix().toStdString() << std::endl; Chris@378: Chris@378: if ((type == LayerFile || type == LayerFileNoMidi) Chris@378: && fi.suffix() == "") { Chris@378: QString expectedExtension; Chris@378: QString selectedFilter = dialog.selectedFilter(); Chris@378: if (selectedFilter.contains(".svl")) { Chris@378: expectedExtension = "svl"; Chris@378: } else if (selectedFilter.contains(".txt")) { Chris@378: expectedExtension = "txt"; Chris@378: } else if (selectedFilter.contains(".csv")) { Chris@378: expectedExtension = "csv"; Chris@378: } else if (selectedFilter.contains(".mid")) { Chris@378: expectedExtension = "mid"; Chris@456: } else if (selectedFilter.contains(".ttl")) { Chris@456: expectedExtension = "ttl"; Chris@378: } Chris@378: std::cerr << "expected extension = " << expectedExtension.toStdString() << std::endl; Chris@378: if (expectedExtension != "") { Chris@378: path = QString("%1.%2").arg(path).arg(expectedExtension); Chris@378: fi = QFileInfo(path); Chris@378: } Chris@378: } Chris@378: Chris@378: if (fi.isDir()) { Chris@378: QMessageBox::critical(0, tr("Directory selected"), Chris@460: tr("Directory selected

File \"%1\" is a directory").arg(path)); Chris@378: continue; Chris@378: } Chris@378: Chris@378: if (fi.exists()) { Chris@378: if (QMessageBox::question(0, tr("File exists"), Chris@460: tr("File exists

The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), Chris@378: QMessageBox::Ok, Chris@378: QMessageBox::Cancel) != QMessageBox::Ok) { Chris@378: continue; Chris@378: } Chris@378: } Chris@378: Chris@378: good = true; Chris@378: } Chris@378: Chris@378: if (path != "") { Chris@378: settings.setValue(settingsKey, Chris@378: QFileInfo(path).absoluteDir().canonicalPath()); Chris@378: } Chris@378: Chris@378: return path; Chris@378: } Chris@378: Chris@378: void Chris@378: FileFinder::registerLastOpenedFilePath(FileType type, QString path) Chris@378: { Chris@378: QString settingsKey; Chris@378: Chris@378: switch (type) { Chris@378: case SessionFile: Chris@378: settingsKey = "sessionpath"; Chris@378: break; Chris@378: Chris@378: case AudioFile: Chris@378: settingsKey = "audiopath"; Chris@378: break; Chris@378: Chris@378: case LayerFile: Chris@378: settingsKey = "layerpath"; Chris@378: break; Chris@378: Chris@378: case LayerFileNoMidi: Chris@378: settingsKey = "layerpath"; Chris@378: break; Chris@378: Chris@378: case SessionOrAudioFile: Chris@378: settingsKey = "lastpath"; Chris@378: break; Chris@378: Chris@378: case ImageFile: Chris@378: settingsKey = "imagepath"; Chris@378: break; Chris@378: Chris@378: case AnyFile: Chris@378: settingsKey = "lastpath"; Chris@378: break; Chris@378: } Chris@378: Chris@378: if (path != "") { Chris@378: QSettings settings; Chris@378: settings.beginGroup("FileFinder"); Chris@378: path = QFileInfo(path).absoluteDir().canonicalPath(); Chris@378: settings.setValue(settingsKey, path); Chris@378: settings.setValue("lastpath", path); Chris@378: } Chris@378: } Chris@378: Chris@378: QString Chris@378: FileFinder::find(FileType type, QString location, QString lastKnownLocation) Chris@378: { Chris@378: if (FileSource::canHandleScheme(location)) { Chris@378: if (FileSource(location).isAvailable()) { Chris@378: std::cerr << "FileFinder::find: ok, it's available... returning" << std::endl; Chris@378: return location; Chris@378: } Chris@378: } Chris@378: Chris@378: if (QFileInfo(location).exists()) return location; Chris@378: Chris@378: QString foundAt = ""; Chris@378: Chris@378: if ((foundAt = findRelative(location, lastKnownLocation)) != "") { Chris@378: return foundAt; Chris@378: } Chris@378: Chris@378: if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") { Chris@378: return foundAt; Chris@378: } Chris@378: Chris@378: return locateInteractive(type, location); Chris@378: } Chris@378: Chris@378: QString Chris@378: FileFinder::findRelative(QString location, QString relativeTo) Chris@378: { Chris@378: if (relativeTo == "") return ""; Chris@378: Chris@378: std::cerr << "Looking for \"" << location.toStdString() << "\" next to \"" Chris@378: << relativeTo.toStdString() << "\"..." << std::endl; Chris@378: Chris@378: QString fileName; Chris@378: QString resolved; Chris@378: Chris@378: if (FileSource::isRemote(location)) { Chris@378: fileName = QUrl(location).path().section('/', -1, -1, Chris@378: QString::SectionSkipEmpty); Chris@378: } else { Chris@378: if (QUrl(location).scheme() == "file") { Chris@378: location = QUrl(location).toLocalFile(); Chris@378: } Chris@378: fileName = QFileInfo(location).fileName(); Chris@378: } Chris@378: Chris@378: if (FileSource::isRemote(relativeTo)) { Chris@378: resolved = QUrl(relativeTo).resolved(fileName).toString(); Chris@378: if (!FileSource(resolved).isAvailable()) resolved = ""; Chris@378: std::cerr << "resolved: " << resolved.toStdString() << std::endl; Chris@378: } else { Chris@378: if (QUrl(relativeTo).scheme() == "file") { Chris@378: relativeTo = QUrl(relativeTo).toLocalFile(); Chris@378: } Chris@378: resolved = QFileInfo(relativeTo).dir().filePath(fileName); Chris@378: if (!QFileInfo(resolved).exists() || Chris@378: !QFileInfo(resolved).isFile() || Chris@378: !QFileInfo(resolved).isReadable()) { Chris@378: resolved = ""; Chris@378: } Chris@378: } Chris@378: Chris@378: return resolved; Chris@378: } Chris@378: Chris@378: QString Chris@378: FileFinder::locateInteractive(FileType type, QString thing) Chris@378: { Chris@378: QString question; Chris@378: if (type == AudioFile) { Chris@460: question = tr("File not found

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

File \"%1\" could not be opened.\nDo you want to locate it?"); Chris@378: } Chris@378: Chris@378: QString path = ""; Chris@378: bool done = false; Chris@378: Chris@378: while (!done) { Chris@378: Chris@378: int rv = QMessageBox::question Chris@378: (0, Chris@378: tr("Failed to open file"), Chris@378: question.arg(thing), Chris@378: tr("Locate file..."), Chris@378: tr("Use URL..."), Chris@378: tr("Cancel"), Chris@378: 0, 2); Chris@378: Chris@378: switch (rv) { Chris@378: Chris@378: case 0: // Locate file Chris@378: Chris@378: if (QFileInfo(thing).dir().exists()) { Chris@378: path = QFileInfo(thing).dir().canonicalPath(); Chris@378: } Chris@378: Chris@378: path = getOpenFileName(type, path); Chris@378: done = (path != ""); Chris@378: break; Chris@378: Chris@378: case 1: // Use URL Chris@378: { Chris@378: bool ok = false; Chris@378: path = QInputDialog::getText Chris@378: (0, tr("Use URL"), Chris@378: tr("Please enter the URL to use for this file:"), Chris@378: QLineEdit::Normal, "", &ok); Chris@378: Chris@378: if (ok && path != "") { Chris@378: if (FileSource(path).isAvailable()) { Chris@378: done = true; Chris@378: } else { Chris@378: QMessageBox::critical Chris@378: (0, tr("Failed to open location"), Chris@460: tr("Failed to open location

URL \"%1\" could not be opened").arg(path)); Chris@378: path = ""; Chris@378: } Chris@378: } Chris@378: break; Chris@378: } Chris@378: Chris@378: case 2: // Cancel Chris@378: path = ""; Chris@378: done = true; Chris@378: break; Chris@378: } Chris@378: } Chris@378: Chris@378: if (path != "") m_lastLocatedLocation = path; Chris@378: return path; Chris@378: } Chris@378: Chris@378: