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@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@529: m_lastLocatedLocation("") Chris@529: { Chris@585: DEBUG << "Registering interactive file finder" << endl; Chris@529: FileFinder::registerFileFinder(this); Chris@529: } Chris@529: Chris@529: InteractiveFileFinder::~InteractiveFileFinder() Chris@529: { Chris@529: } Chris@529: Chris@529: QString Chris@529: InteractiveFileFinder::getOpenFileName(FileType type, QString fallbackLocation) Chris@529: { Chris@529: QString settingsKey; 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@529: settingsKey = "sessionpath"; Chris@529: title = tr("Select a session file"); Chris@529: filter = tr("Sonic Visualiser session files (*.sv)\nRDF files (%1)\nAll files (*.*)").arg(RDFImporter::getKnownExtensions()); Chris@529: break; Chris@529: Chris@529: case AudioFile: Chris@529: settingsKey = "audiopath"; Chris@529: title = "Select an audio file"; Chris@529: filter = tr("Audio files (%1)\nAll files (*.*)") Chris@529: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@529: break; Chris@529: Chris@529: case LayerFile: Chris@529: settingsKey = "layerpath"; 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@529: settingsKey = "layerpath"; 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@529: case SessionOrAudioFile: Chris@529: settingsKey = "lastpath"; Chris@529: filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)") Chris@529: .arg(RDFImporter::getKnownExtensions()) Chris@529: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@529: break; Chris@529: Chris@529: case ImageFile: Chris@529: settingsKey = "imagepath"; 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@529: case AnyFile: Chris@529: settingsKey = "lastpath"; Chris@529: 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@529: .arg(AudioFileReaderFactory::getKnownExtensions()) Chris@529: .arg(DataFileReaderFactory::getKnownExtensions()) Chris@529: .arg(RDFImporter::getKnownExtensions()); Chris@529: break; Chris@529: }; Chris@529: Chris@529: if (lastPath == "") { Chris@529: char *home = getenv("HOME"); Chris@529: if (home) lastPath = home; Chris@529: else lastPath = "."; 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@529: lastPath = settings.value(settingsKey, lastPath).toString(); Chris@529: Chris@529: QString path = ""; Chris@529: Chris@529: // Use our own QFileDialog just for symmetry with getSaveFileName below Chris@529: Chris@529: QFileDialog dialog; Chris@529: dialog.setFilters(filter.split('\n')); Chris@529: dialog.setWindowTitle(title); Chris@529: dialog.setDirectory(lastPath); Chris@529: Chris@529: dialog.setAcceptMode(QFileDialog::AcceptOpen); Chris@529: dialog.setFileMode(QFileDialog::ExistingFile); Chris@529: Chris@529: if (dialog.exec()) { Chris@529: QStringList files = dialog.selectedFiles(); Chris@529: if (!files.empty()) path = *files.begin(); Chris@529: Chris@529: QFileInfo fi(path); Chris@529: Chris@529: if (!fi.exists()) { Chris@529: Chris@529: QMessageBox::critical(0, tr("File does not exist"), Chris@529: tr("File not found

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

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

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

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

File \"%1\" is empty").arg(path)); Chris@529: path = ""; Chris@529: } Chris@529: } Chris@529: Chris@529: if (path != "") { Chris@529: settings.setValue(settingsKey, Chris@529: QFileInfo(path).absoluteDir().canonicalPath()); Chris@529: } Chris@529: Chris@529: return path; Chris@529: } Chris@529: Chris@529: QString Chris@529: InteractiveFileFinder::getSaveFileName(FileType type, QString fallbackLocation) Chris@529: { Chris@529: QString settingsKey; 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@529: settingsKey = "savesessionpath"; Chris@529: title = tr("Select a session file"); Chris@529: filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); Chris@529: break; Chris@529: Chris@529: case AudioFile: Chris@529: settingsKey = "saveaudiopath"; 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@529: settingsKey = "savelayerpath"; Chris@529: title = tr("Select a file to export to"); Chris@529: 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@529: break; Chris@529: Chris@529: case LayerFileNoMidi: Chris@529: settingsKey = "savelayerpath"; Chris@529: title = tr("Select a file to export to"); Chris@529: 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@529: break; Chris@529: Chris@529: case SessionOrAudioFile: Chris@529: std::cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl; Chris@529: abort(); Chris@529: Chris@529: case ImageFile: Chris@529: settingsKey = "saveimagepath"; 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@529: case AnyFile: Chris@529: std::cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl; Chris@529: abort(); Chris@529: }; Chris@529: Chris@529: if (lastPath == "") { Chris@529: char *home = getenv("HOME"); Chris@529: if (home) lastPath = home; Chris@529: else lastPath = "."; 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@529: lastPath = settings.value(settingsKey, 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@529: QFileDialog dialog; Chris@529: dialog.setFilters(filter.split('\n')); Chris@529: dialog.setWindowTitle(title); Chris@529: dialog.setDirectory(lastPath); Chris@529: Chris@529: dialog.setAcceptMode(QFileDialog::AcceptSave); Chris@529: dialog.setFileMode(QFileDialog::AnyFile); Chris@529: dialog.setConfirmOverwrite(false); // we'll do that Chris@529: Chris@529: if (type == SessionFile) { Chris@529: dialog.setDefaultSuffix("sv"); Chris@529: } else if (type == AudioFile) { Chris@529: dialog.setDefaultSuffix("wav"); Chris@529: } else if (type == ImageFile) { Chris@529: dialog.setDefaultSuffix("png"); 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@584: std::cerr << "type = " << type << ", suffix = " << fi.suffix() << std::endl; Chris@529: Chris@529: if ((type == LayerFile || type == LayerFileNoMidi) Chris@529: && fi.suffix() == "") { Chris@529: QString expectedExtension; Chris@529: QString selectedFilter = dialog.selectedFilter(); 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@584: std::cerr << "expected extension = " << expectedExtension << std::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@529: QMessageBox::critical(0, 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@529: if (QMessageBox::question(0, 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@529: settings.setValue(settingsKey, Chris@529: QFileInfo(path).absoluteDir().canonicalPath()); 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@529: QString settingsKey; Chris@529: Chris@529: switch (type) { Chris@529: case SessionFile: Chris@529: settingsKey = "sessionpath"; Chris@529: break; Chris@529: Chris@529: case AudioFile: Chris@529: settingsKey = "audiopath"; Chris@529: break; Chris@529: Chris@529: case LayerFile: Chris@529: settingsKey = "layerpath"; Chris@529: break; Chris@529: Chris@529: case LayerFileNoMidi: Chris@529: settingsKey = "layerpath"; Chris@529: break; Chris@529: Chris@529: case SessionOrAudioFile: Chris@529: settingsKey = "lastpath"; Chris@529: break; Chris@529: Chris@529: case ImageFile: Chris@529: settingsKey = "imagepath"; Chris@529: break; Chris@529: Chris@529: case AnyFile: Chris@529: settingsKey = "lastpath"; 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@529: settings.setValue(settingsKey, path); 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@585: DEBUG << "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@585: DEBUG << "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@584: std::cerr << "resolved: " << resolved << std::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@529: (0, 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@529: (0, 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@529: (0, 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: