Chris@210: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@210: Chris@210: /* Chris@210: Sonic Visualiser Chris@210: An audio file viewer and annotation editor. Chris@210: Centre for Digital Music, Queen Mary, University of London. Chris@210: This file copyright 2007 QMUL. Chris@210: Chris@210: This program is free software; you can redistribute it and/or Chris@210: modify it under the terms of the GNU General Public License as Chris@210: published by the Free Software Foundation; either version 2 of the Chris@210: License, or (at your option) any later version. See the file Chris@210: COPYING included with this distribution for more information. Chris@210: */ Chris@210: Chris@210: #include "FileFinder.h" Chris@317: #include "FileSource.h" Chris@211: #include "AudioFileReaderFactory.h" Chris@211: #include "DataFileReaderFactory.h" Chris@210: Chris@210: #include Chris@210: #include Chris@210: #include Chris@211: #include Chris@302: #include Chris@211: #include Chris@210: Chris@211: #include Chris@210: Chris@211: FileFinder * Chris@211: FileFinder::m_instance = 0; Chris@211: Chris@211: FileFinder::FileFinder() : Chris@210: m_lastLocatedLocation("") Chris@210: { Chris@210: } Chris@210: Chris@210: FileFinder::~FileFinder() Chris@210: { Chris@210: } Chris@210: Chris@211: FileFinder * Chris@211: FileFinder::getInstance() Chris@211: { Chris@211: if (m_instance == 0) { Chris@211: m_instance = new FileFinder(); Chris@211: } Chris@211: return m_instance; Chris@211: } Chris@211: Chris@210: QString Chris@211: FileFinder::getOpenFileName(FileType type, QString fallbackLocation) Chris@210: { Chris@211: QString settingsKey; Chris@211: QString lastPath = fallbackLocation; Chris@211: Chris@211: QString title = tr("Select file"); Chris@211: QString filter = tr("All files (*.*)"); Chris@210: Chris@211: switch (type) { Chris@210: Chris@211: case SessionFile: Chris@211: settingsKey = "sessionpath"; Chris@211: title = tr("Select a session file"); Chris@211: filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); Chris@211: break; Chris@210: Chris@211: case AudioFile: Chris@211: settingsKey = "audiopath"; Chris@211: title = "Select an audio file"; Chris@211: filter = tr("Audio files (%1)\nAll files (*.*)") Chris@290: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@211: break; Chris@211: Chris@211: case LayerFile: Chris@211: settingsKey = "layerpath"; Chris@211: 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()); Chris@211: break; Chris@211: Chris@301: case LayerFileNoMidi: Chris@301: settingsKey = "layerpath"; Chris@301: filter = tr("All supported files (%1)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()); Chris@301: break; Chris@301: Chris@211: case SessionOrAudioFile: Chris@211: settingsKey = "lastpath"; Chris@211: filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)") Chris@290: .arg(AudioFileReaderFactory::getKnownExtensions()); Chris@211: break; Chris@211: Chris@250: case ImageFile: Chris@250: settingsKey = "imagepath"; Chris@302: { Chris@302: QStringList fmts; Chris@302: QList formats = QImageReader::supportedImageFormats(); Chris@302: for (QList::iterator i = formats.begin(); Chris@302: i != formats.end(); ++i) { Chris@302: fmts.push_back(QString("*.%1") Chris@302: .arg(QString::fromLocal8Bit(*i).toLower())); Chris@302: } Chris@302: filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" ")); Chris@302: } Chris@250: break; Chris@250: Chris@211: case AnyFile: Chris@211: settingsKey = "lastpath"; Chris@211: filter = tr("All supported files (*.sv %1 %2)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)") Chris@290: .arg(AudioFileReaderFactory::getKnownExtensions()) Chris@211: .arg(DataFileReaderFactory::getKnownExtensions()); Chris@211: break; Chris@211: }; Chris@211: Chris@211: if (lastPath == "") { Chris@211: char *home = getenv("HOME"); Chris@211: if (home) lastPath = home; Chris@211: else lastPath = "."; Chris@211: } else if (QFileInfo(lastPath).isDir()) { Chris@211: lastPath = QFileInfo(lastPath).canonicalPath(); Chris@211: } else { Chris@211: lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); Chris@211: } Chris@211: Chris@211: QSettings settings; Chris@211: settings.beginGroup("FileFinder"); Chris@211: lastPath = settings.value(settingsKey, lastPath).toString(); Chris@211: Chris@211: QString path = ""; Chris@211: Chris@211: // Use our own QFileDialog just for symmetry with getSaveFileName below Chris@211: Chris@211: QFileDialog dialog; Chris@211: dialog.setFilters(filter.split('\n')); Chris@211: dialog.setWindowTitle(title); Chris@211: dialog.setDirectory(lastPath); Chris@211: Chris@211: dialog.setAcceptMode(QFileDialog::AcceptOpen); Chris@211: dialog.setFileMode(QFileDialog::ExistingFile); Chris@211: Chris@211: if (dialog.exec()) { Chris@211: QStringList files = dialog.selectedFiles(); Chris@211: if (!files.empty()) path = *files.begin(); Chris@211: Chris@211: QFileInfo fi(path); Chris@211: Chris@211: if (!fi.exists()) { Chris@211: Chris@211: QMessageBox::critical(0, tr("File does not exist"), Chris@211: tr("File \"%1\" does not exist").arg(path)); Chris@211: path = ""; Chris@211: Chris@211: } else if (!fi.isReadable()) { Chris@211: Chris@211: QMessageBox::critical(0, tr("File is not readable"), Chris@211: tr("File \"%1\" can not be read").arg(path)); Chris@211: path = ""; Chris@211: Chris@211: } else if (fi.isDir()) { Chris@211: Chris@211: QMessageBox::critical(0, tr("Directory selected"), Chris@211: tr("File \"%1\" is a directory").arg(path)); Chris@211: path = ""; Chris@211: Chris@211: } else if (!fi.isFile()) { Chris@211: Chris@211: QMessageBox::critical(0, tr("Non-file selected"), Chris@211: tr("Path \"%1\" is not a file").arg(path)); Chris@211: path = ""; Chris@211: Chris@211: } else if (fi.size() == 0) { Chris@211: Chris@211: QMessageBox::critical(0, tr("File is empty"), Chris@211: tr("File \"%1\" is empty").arg(path)); Chris@211: path = ""; Chris@211: } Chris@211: } Chris@211: Chris@211: if (path != "") { Chris@211: settings.setValue(settingsKey, Chris@211: QFileInfo(path).absoluteDir().canonicalPath()); Chris@211: } Chris@211: Chris@211: return path; Chris@211: } Chris@211: Chris@211: QString Chris@211: FileFinder::getSaveFileName(FileType type, QString fallbackLocation) Chris@211: { Chris@211: QString settingsKey; Chris@211: QString lastPath = fallbackLocation; Chris@211: Chris@211: QString title = tr("Select file"); Chris@211: QString filter = tr("All files (*.*)"); Chris@211: Chris@211: switch (type) { Chris@211: Chris@211: case SessionFile: Chris@211: settingsKey = "savesessionpath"; Chris@211: title = tr("Select a session file"); Chris@211: filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)"); Chris@211: break; Chris@211: Chris@211: case AudioFile: Chris@211: settingsKey = "saveaudiopath"; Chris@211: title = "Select an audio file"; Chris@211: title = tr("Select a file to export to"); Chris@211: filter = tr("WAV audio files (*.wav)\nAll files (*.*)"); Chris@211: break; Chris@211: Chris@211: case LayerFile: Chris@211: settingsKey = "savelayerpath"; Chris@211: title = tr("Select a file to export to"); Chris@301: filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)"); Chris@301: break; Chris@301: Chris@301: case LayerFileNoMidi: Chris@301: settingsKey = "savelayerpath"; Chris@301: title = tr("Select a file to export to"); Chris@211: filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)"); Chris@211: break; Chris@211: Chris@211: case SessionOrAudioFile: Chris@211: std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl; Chris@211: abort(); Chris@211: Chris@250: case ImageFile: Chris@250: settingsKey = "saveimagepath"; Chris@250: title = tr("Select a file to export to"); Chris@250: filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)"); Chris@250: break; Chris@250: Chris@211: case AnyFile: Chris@211: std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl; Chris@211: abort(); Chris@211: }; Chris@211: Chris@211: if (lastPath == "") { Chris@211: char *home = getenv("HOME"); Chris@211: if (home) lastPath = home; Chris@211: else lastPath = "."; Chris@211: } else if (QFileInfo(lastPath).isDir()) { Chris@211: lastPath = QFileInfo(lastPath).canonicalPath(); Chris@211: } else { Chris@211: lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath(); Chris@211: } Chris@211: Chris@211: QSettings settings; Chris@211: settings.beginGroup("FileFinder"); Chris@211: lastPath = settings.value(settingsKey, lastPath).toString(); Chris@211: Chris@211: QString path = ""; Chris@211: Chris@211: // Use our own QFileDialog instead of static functions, as we may Chris@211: // need to adjust the file extension based on the selected filter Chris@211: Chris@211: QFileDialog dialog; Chris@211: dialog.setFilters(filter.split('\n')); Chris@211: dialog.setWindowTitle(title); Chris@211: dialog.setDirectory(lastPath); Chris@211: Chris@211: dialog.setAcceptMode(QFileDialog::AcceptSave); Chris@211: dialog.setFileMode(QFileDialog::AnyFile); Chris@211: dialog.setConfirmOverwrite(false); // we'll do that Chris@211: Chris@211: if (type == SessionFile) { Chris@211: dialog.setDefaultSuffix("sv"); Chris@211: } else if (type == AudioFile) { Chris@211: dialog.setDefaultSuffix("wav"); Chris@250: } else if (type == ImageFile) { Chris@250: dialog.setDefaultSuffix("png"); Chris@211: } Chris@211: Chris@211: bool good = false; Chris@211: Chris@211: while (!good) { Chris@211: Chris@211: path = ""; Chris@211: Chris@211: if (!dialog.exec()) break; Chris@211: Chris@211: QStringList files = dialog.selectedFiles(); Chris@211: if (files.empty()) break; Chris@211: path = *files.begin(); Chris@211: Chris@211: QFileInfo fi(path); Chris@305: Chris@305: std::cerr << "type = " << type << ", suffix = " << fi.suffix().toStdString() << std::endl; Chris@211: Chris@305: if ((type == LayerFile || type == LayerFileNoMidi) Chris@305: && fi.suffix() == "") { Chris@211: QString expectedExtension; Chris@211: QString selectedFilter = dialog.selectedFilter(); Chris@211: if (selectedFilter.contains(".svl")) { Chris@211: expectedExtension = "svl"; Chris@211: } else if (selectedFilter.contains(".txt")) { Chris@211: expectedExtension = "txt"; Chris@211: } else if (selectedFilter.contains(".csv")) { Chris@211: expectedExtension = "csv"; Chris@301: } else if (selectedFilter.contains(".mid")) { Chris@301: expectedExtension = "mid"; Chris@211: } Chris@305: std::cerr << "expected extension = " << expectedExtension.toStdString() << std::endl; Chris@211: if (expectedExtension != "") { Chris@211: path = QString("%1.%2").arg(path).arg(expectedExtension); Chris@211: fi = QFileInfo(path); Chris@211: } Chris@211: } Chris@211: Chris@211: if (fi.isDir()) { Chris@211: QMessageBox::critical(0, tr("Directory selected"), Chris@211: tr("File \"%1\" is a directory").arg(path)); Chris@211: continue; Chris@211: } Chris@211: Chris@211: if (fi.exists()) { Chris@211: if (QMessageBox::question(0, tr("File exists"), Chris@211: tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path), Chris@211: QMessageBox::Ok, Chris@211: QMessageBox::Cancel) != QMessageBox::Ok) { Chris@211: continue; Chris@211: } Chris@211: } Chris@211: Chris@211: good = true; Chris@211: } Chris@211: Chris@211: if (path != "") { Chris@211: settings.setValue(settingsKey, Chris@211: QFileInfo(path).absoluteDir().canonicalPath()); Chris@211: } Chris@211: Chris@211: return path; Chris@211: } Chris@211: Chris@211: void Chris@211: FileFinder::registerLastOpenedFilePath(FileType type, QString path) Chris@211: { Chris@211: QString settingsKey; Chris@211: Chris@211: switch (type) { Chris@211: case SessionFile: Chris@211: settingsKey = "sessionpath"; Chris@211: break; Chris@211: Chris@211: case AudioFile: Chris@211: settingsKey = "audiopath"; Chris@211: break; Chris@211: Chris@211: case LayerFile: Chris@211: settingsKey = "layerpath"; Chris@211: break; Chris@211: Chris@301: case LayerFileNoMidi: Chris@301: settingsKey = "layerpath"; Chris@301: break; Chris@301: Chris@211: case SessionOrAudioFile: Chris@211: settingsKey = "lastpath"; Chris@211: break; Chris@211: Chris@250: case ImageFile: Chris@250: settingsKey = "imagepath"; Chris@250: break; Chris@250: Chris@211: case AnyFile: Chris@211: settingsKey = "lastpath"; Chris@211: break; Chris@211: } Chris@211: Chris@211: if (path != "") { Chris@211: QSettings settings; Chris@211: settings.beginGroup("FileFinder"); Chris@211: path = QFileInfo(path).absoluteDir().canonicalPath(); Chris@211: settings.setValue(settingsKey, path); Chris@211: settings.setValue("lastpath", path); Chris@211: } Chris@211: } Chris@211: Chris@211: QString Chris@211: FileFinder::find(FileType type, QString location, QString lastKnownLocation) Chris@211: { Chris@384: if (FileSource::canHandleScheme(location)) { Chris@317: if (FileSource(location).isAvailable()) { Chris@316: std::cerr << "FileFinder::find: ok, it's available... returning" << std::endl; Chris@316: return location; Chris@316: } Chris@211: } Chris@211: Chris@384: if (QFileInfo(location).exists()) return location; Chris@384: Chris@211: QString foundAt = ""; Chris@211: Chris@211: if ((foundAt = findRelative(location, lastKnownLocation)) != "") { Chris@211: return foundAt; Chris@211: } Chris@211: Chris@211: if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") { Chris@211: return foundAt; Chris@211: } Chris@211: Chris@211: return locateInteractive(type, location); Chris@211: } Chris@211: Chris@211: QString Chris@211: FileFinder::findRelative(QString location, QString relativeTo) Chris@211: { Chris@211: if (relativeTo == "") return ""; Chris@211: Chris@211: std::cerr << "Looking for \"" << location.toStdString() << "\" next to \"" Chris@211: << relativeTo.toStdString() << "\"..." << std::endl; Chris@211: Chris@211: QString fileName; Chris@211: QString resolved; Chris@211: Chris@317: if (FileSource::isRemote(location)) { Chris@211: fileName = QUrl(location).path().section('/', -1, -1, Chris@211: QString::SectionSkipEmpty); Chris@211: } else { Chris@384: if (QUrl(location).scheme() == "file") { Chris@384: location = QUrl(location).toLocalFile(); Chris@384: } Chris@211: fileName = QFileInfo(location).fileName(); Chris@211: } Chris@211: Chris@317: if (FileSource::isRemote(relativeTo)) { Chris@211: resolved = QUrl(relativeTo).resolved(fileName).toString(); Chris@317: if (!FileSource(resolved).isAvailable()) resolved = ""; Chris@211: std::cerr << "resolved: " << resolved.toStdString() << std::endl; Chris@211: } else { Chris@384: if (QUrl(relativeTo).scheme() == "file") { Chris@384: relativeTo = QUrl(relativeTo).toLocalFile(); Chris@384: } Chris@211: resolved = QFileInfo(relativeTo).dir().filePath(fileName); Chris@211: if (!QFileInfo(resolved).exists() || Chris@211: !QFileInfo(resolved).isFile() || Chris@211: !QFileInfo(resolved).isReadable()) { Chris@211: resolved = ""; Chris@211: } Chris@211: } Chris@211: Chris@211: return resolved; Chris@211: } Chris@211: Chris@211: QString Chris@211: FileFinder::locateInteractive(FileType type, QString thing) Chris@211: { Chris@211: QString question; Chris@211: if (type == AudioFile) { Chris@211: question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?"); Chris@211: } else { Chris@211: question = tr("File \"%1\" could not be opened.\nDo you want to locate it?"); Chris@211: } Chris@211: Chris@211: QString path = ""; Chris@211: bool done = false; Chris@211: Chris@211: while (!done) { Chris@211: Chris@211: int rv = QMessageBox::question Chris@211: (0, Chris@211: tr("Failed to open file"), Chris@211: question.arg(thing), Chris@211: tr("Locate file..."), Chris@211: tr("Use URL..."), Chris@211: tr("Cancel"), Chris@211: 0, 2); Chris@211: Chris@211: switch (rv) { Chris@211: Chris@211: case 0: // Locate file Chris@211: Chris@211: if (QFileInfo(thing).dir().exists()) { Chris@211: path = QFileInfo(thing).dir().canonicalPath(); Chris@211: } Chris@211: Chris@211: path = getOpenFileName(type, path); Chris@211: done = (path != ""); Chris@211: break; Chris@211: Chris@211: case 1: // Use URL Chris@211: { Chris@211: bool ok = false; Chris@211: path = QInputDialog::getText Chris@211: (0, tr("Use URL"), Chris@211: tr("Please enter the URL to use for this file:"), Chris@211: QLineEdit::Normal, "", &ok); Chris@211: Chris@211: if (ok && path != "") { Chris@317: if (FileSource(path).isAvailable()) { Chris@211: done = true; Chris@211: } else { Chris@211: QMessageBox::critical Chris@211: (0, tr("Failed to open location"), Chris@211: tr("URL \"%1\" could not be opened").arg(path)); Chris@211: path = ""; Chris@211: } Chris@211: } Chris@211: break; Chris@211: } Chris@211: Chris@211: case 2: // Cancel Chris@211: path = ""; Chris@211: done = true; Chris@211: break; Chris@210: } Chris@210: } Chris@210: Chris@211: if (path != "") m_lastLocatedLocation = path; Chris@211: return path; Chris@210: } Chris@210: Chris@210: