svgui  1.9
InteractiveFileFinder.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2007 QMUL.
00008     
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "InteractiveFileFinder.h"
00017 #include "data/fileio/FileSource.h"
00018 #include "data/fileio/AudioFileReaderFactory.h"
00019 #include "data/fileio/DataFileReaderFactory.h"
00020 #include "rdf/RDFImporter.h"
00021 #include "rdf/RDFExporter.h"
00022 
00023 #include <QFileInfo>
00024 #include <QMessageBox>
00025 #include <QFileDialog>
00026 #include <QInputDialog>
00027 #include <QImageReader>
00028 #include <QSettings>
00029 
00030 #include <iostream>
00031 
00032 InteractiveFileFinder 
00033 InteractiveFileFinder::m_instance;
00034 
00035 InteractiveFileFinder::InteractiveFileFinder() :
00036     m_sessionExtension("sv"),
00037     m_lastLocatedLocation(""),
00038     m_parent(0)
00039 {
00040     SVDEBUG << "Registering interactive file finder" << endl;
00041     FileFinder::registerFileFinder(this);
00042 }
00043 
00044 InteractiveFileFinder::~InteractiveFileFinder()
00045 {
00046 }
00047 
00048 void
00049 InteractiveFileFinder::setParentWidget(QWidget *parent)
00050 {
00051     getInstance()->m_parent = parent;
00052 }
00053 
00054 void
00055 InteractiveFileFinder::setApplicationSessionExtension(QString extension)
00056 {
00057     m_sessionExtension = extension;
00058 }
00059 
00060 QString
00061 InteractiveFileFinder::getOpenFileName(FileType type, QString fallbackLocation)
00062 {
00063     QString settingsKeyStub;
00064     QString lastPath = fallbackLocation;
00065     
00066     QString title = tr("Select file");
00067     QString filter = tr("All files (*.*)");
00068 
00069     switch (type) {
00070 
00071     case SessionFile:
00072         settingsKeyStub = "session";
00073         title = tr("Select a session file");
00074         filter = tr("%1 session files (*.%1)\nRDF files (%3)\nAll files (*.*)")
00075             .arg(QApplication::applicationName())
00076             .arg(m_sessionExtension)
00077             .arg(RDFImporter::getKnownExtensions());
00078         break;
00079 
00080     case AudioFile:
00081         settingsKeyStub = "audio";
00082         title = "Select an audio file";
00083         filter = tr("Audio files (%1)\nAll files (*.*)")
00084             .arg(AudioFileReaderFactory::getKnownExtensions());
00085         break;
00086 
00087     case LayerFile:
00088         settingsKeyStub = "layer";
00089         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 (*.*)")
00090             .arg(DataFileReaderFactory::getKnownExtensions())
00091             .arg(RDFImporter::getKnownExtensions());
00092         break;
00093 
00094     case LayerFileNoMidi:
00095         settingsKeyStub = "layer";
00096         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 (*.*)")
00097             .arg(DataFileReaderFactory::getKnownExtensions())
00098             .arg(RDFImporter::getKnownExtensions());
00099         break;
00100 
00101     case LayerFileNonSV:
00102         settingsKeyStub = "layer";
00103         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 (*.*)")
00104             .arg(DataFileReaderFactory::getKnownExtensions())
00105             .arg(RDFImporter::getKnownExtensions());
00106         break;
00107 
00108     case LayerFileNoMidiNonSV:
00109         settingsKeyStub = "layer";
00110         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 (*.*)")
00111             .arg(DataFileReaderFactory::getKnownExtensions())
00112             .arg(RDFImporter::getKnownExtensions());
00113         break;
00114 
00115     case SessionOrAudioFile:
00116         settingsKeyStub = "last";
00117         filter = tr("All supported files (*.sv %1 %2)\n%3 session files (*.%4)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)")
00118             .arg(RDFImporter::getKnownExtensions())
00119             .arg(AudioFileReaderFactory::getKnownExtensions())
00120             .arg(QApplication::applicationName())
00121             .arg(m_sessionExtension);
00122         break;
00123 
00124     case ImageFile:
00125         settingsKeyStub = "image";
00126         {
00127             QStringList fmts;
00128             QList<QByteArray> formats = QImageReader::supportedImageFormats();
00129             for (QList<QByteArray>::iterator i = formats.begin();
00130                  i != formats.end(); ++i) {
00131                 fmts.push_back(QString("*.%1")
00132                                .arg(QString::fromLocal8Bit(*i).toLower()));
00133             }
00134             filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" "));
00135         }
00136         break;
00137 
00138     case CSVFile:
00139         settingsKeyStub = "layer";
00140         filter = tr("Comma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)");
00141         break;
00142 
00143     case AnyFile:
00144         settingsKeyStub = "last";
00145         filter = tr("All supported files (*.sv %1 %2 %3)\n%4 session files (*.%5)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)")
00146             .arg(AudioFileReaderFactory::getKnownExtensions())
00147             .arg(DataFileReaderFactory::getKnownExtensions())
00148             .arg(RDFImporter::getKnownExtensions())
00149             .arg(QApplication::applicationName())
00150             .arg(m_sessionExtension);
00151         break;
00152     };
00153 
00154     if (lastPath == "") {
00155         char *home = getenv("HOME");
00156         if (home) lastPath = home;
00157         else lastPath = ".";
00158     } else if (QFileInfo(lastPath).isDir()) {
00159         lastPath = QFileInfo(lastPath).canonicalPath();
00160     } else {
00161         lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
00162     }
00163 
00164     QSettings settings;
00165     settings.beginGroup("FileFinder");
00166     lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
00167 
00168     QString path = "";
00169 
00170     // Use our own QFileDialog just for symmetry with getSaveFileName below
00171 
00172     QFileDialog dialog(m_parent);
00173     dialog.setNameFilters(filter.split('\n'));
00174     dialog.setWindowTitle(title);
00175     dialog.setDirectory(lastPath);
00176 
00177     dialog.setAcceptMode(QFileDialog::AcceptOpen);
00178     dialog.setFileMode(QFileDialog::ExistingFile);
00179     
00180     if (dialog.exec()) {
00181         QStringList files = dialog.selectedFiles();
00182         if (!files.empty()) path = *files.begin();
00183         
00184         QFileInfo fi(path);
00185         
00186         if (!fi.exists()) {
00187             
00188             QMessageBox::critical(0, tr("File does not exist"),
00189                                   tr("<b>File not found</b><p>File \"%1\" does not exist").arg(path));
00190             path = "";
00191             
00192         } else if (!fi.isReadable()) {
00193             
00194             QMessageBox::critical(0, tr("File is not readable"),
00195                                   tr("<b>File is not readable</b><p>File \"%1\" can not be read").arg(path));
00196             path = "";
00197             
00198         } else if (fi.isDir()) {
00199             
00200             QMessageBox::critical(0, tr("Directory selected"),
00201                                   tr("<b>Directory selected</b><p>File \"%1\" is a directory").arg(path));
00202             path = "";
00203 
00204         } else if (!fi.isFile()) {
00205             
00206             QMessageBox::critical(0, tr("Non-file selected"),
00207                                   tr("<b>Not a file</b><p>Path \"%1\" is not a file").arg(path));
00208             path = "";
00209             
00210         } else if (fi.size() == 0) {
00211             
00212             QMessageBox::critical(0, tr("File is empty"),
00213                                   tr("<b>File is empty</b><p>File \"%1\" is empty").arg(path));
00214             path = "";
00215         }                
00216     }
00217 
00218     if (path != "") {
00219         settings.setValue(settingsKeyStub + "path",
00220                           QFileInfo(path).absoluteDir().canonicalPath());
00221     }
00222     
00223     return path;
00224 }
00225 
00226 QString
00227 InteractiveFileFinder::getSaveFileName(FileType type, 
00228                                        QString fallbackLocation)
00229 {
00230     QString settingsKeyStub;
00231     QString lastPath = fallbackLocation;
00232     
00233     QString title = tr("Select file");
00234     QString filter = tr("All files (*.*)");
00235 
00236     switch (type) {
00237 
00238     case SessionFile:
00239         settingsKeyStub = "savesession";
00240         title = tr("Select a session file");
00241         filter = tr("%1 session files (*.%2)\nAll files (*.*)")
00242             .arg(QApplication::applicationName()).arg(m_sessionExtension);
00243         break;
00244 
00245     case AudioFile:
00246         settingsKeyStub = "saveaudio";
00247         title = "Select an audio file";
00248         title = tr("Select a file to export to");
00249         filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
00250         break;
00251 
00252     case LayerFile:
00253         settingsKeyStub = "savelayer";
00254         title = tr("Select a file to export to");
00255         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());
00256         break;
00257 
00258     case LayerFileNoMidi:
00259         settingsKeyStub = "savelayer";
00260         title = tr("Select a file to export to");
00261         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());
00262         break;
00263 
00264     case LayerFileNonSV:
00265         settingsKeyStub = "savelayer";
00266         title = tr("Select a file to export to");
00267         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 (*.*)").arg(RDFExporter::getSupportedExtensions());
00268         break;
00269 
00270     case LayerFileNoMidiNonSV:
00271         settingsKeyStub = "savelayer";
00272         title = tr("Select a file to export to");
00273         filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
00274         break;
00275 
00276     case SessionOrAudioFile:
00277         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << endl;
00278         abort();
00279 
00280     case ImageFile:
00281         settingsKeyStub = "saveimage";
00282         title = tr("Select a file to export to");
00283         filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
00284         break;
00285 
00286     case CSVFile:
00287         settingsKeyStub = "savelayer";
00288         title = tr("Select a file to export to");
00289         filter = tr("Comma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
00290         break;
00291 
00292     case AnyFile:
00293         cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: AnyFile cannot be used here" << endl;
00294         abort();
00295     };
00296 
00297     if (lastPath == "") {
00298         char *home = getenv("HOME");
00299         if (home) lastPath = home;
00300         else lastPath = ".";
00301     } else if (QFileInfo(lastPath).isDir()) {
00302         lastPath = QFileInfo(lastPath).canonicalPath();
00303     } else {
00304         lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
00305     }
00306 
00307     QSettings settings;
00308     settings.beginGroup("FileFinder");
00309     lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
00310 
00311     QString path = "";
00312 
00313     // Use our own QFileDialog instead of static functions, as we may
00314     // need to adjust the file extension based on the selected filter
00315 
00316     QFileDialog dialog(m_parent);
00317 
00318     QStringList filters = filter.split('\n');
00319 
00320     dialog.setNameFilters(filters);
00321     dialog.setWindowTitle(title);
00322     dialog.setDirectory(lastPath);
00323     dialog.setAcceptMode(QFileDialog::AcceptSave);
00324     dialog.setFileMode(QFileDialog::AnyFile);
00325     dialog.setConfirmOverwrite(false); // we'll do that
00326     
00327     QString defaultSuffix;
00328     if (type == SessionFile) {
00329         defaultSuffix = m_sessionExtension;
00330     } else if (type == AudioFile) {
00331         defaultSuffix = "wav";
00332     } else if (type == ImageFile) {
00333         defaultSuffix = "png";
00334     } else if (type == CSVFile) {
00335         defaultSuffix = "csv";
00336     }
00337 
00338     defaultSuffix = 
00339         settings.value(settingsKeyStub + "suffix", defaultSuffix).toString();
00340 
00341     dialog.setDefaultSuffix(defaultSuffix);
00342 
00343     foreach (QString f, filters) {
00344         if (f.contains("." + defaultSuffix)) {
00345             dialog.selectNameFilter(f);
00346         }
00347     }
00348 
00349     bool good = false;
00350 
00351     while (!good) {
00352 
00353         path = "";
00354         
00355         if (!dialog.exec()) break;
00356         
00357         QStringList files = dialog.selectedFiles();
00358         if (files.empty()) break;
00359         path = *files.begin();
00360         
00361         QFileInfo fi(path);
00362 
00363         cerr << "type = " << type << ", suffix = " << fi.suffix() << endl;
00364         
00365         if ((type == LayerFile || type == LayerFileNoMidi || 
00366              type == LayerFileNonSV || type == LayerFileNoMidiNonSV)
00367             && fi.suffix() == "") {
00368             QString expectedExtension;
00369             QString selectedFilter = dialog.selectedNameFilter();
00370             if (selectedFilter.contains(".svl")) {
00371                 expectedExtension = "svl";
00372             } else if (selectedFilter.contains(".txt")) {
00373                 expectedExtension = "txt";
00374             } else if (selectedFilter.contains(".csv")) {
00375                 expectedExtension = "csv";
00376             } else if (selectedFilter.contains(".mid")) {
00377                 expectedExtension = "mid";
00378             } else if (selectedFilter.contains(".ttl")) {
00379                 expectedExtension = "ttl";
00380             }
00381             cerr << "expected extension = " << expectedExtension << endl;
00382             if (expectedExtension != "") {
00383                 path = QString("%1.%2").arg(path).arg(expectedExtension);
00384                 fi = QFileInfo(path);
00385             }
00386         }
00387         
00388         if (fi.isDir()) {
00389             QMessageBox::critical(0, tr("Directory selected"),
00390                                   tr("<b>Directory selected</b><p>File \"%1\" is a directory").arg(path));
00391             continue;
00392         }
00393         
00394         if (fi.exists()) {
00395             if (QMessageBox::question(0, tr("File exists"),
00396                                       tr("<b>File exists</b><p>The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
00397                                       QMessageBox::Ok,
00398                                       QMessageBox::Cancel) != QMessageBox::Ok) {
00399                 continue;
00400             }
00401         }
00402         
00403         good = true;
00404     }
00405         
00406     if (path != "") {
00407         settings.setValue(settingsKeyStub + "path",
00408                           QFileInfo(path).absoluteDir().canonicalPath());
00409         settings.setValue(settingsKeyStub + "suffix",
00410                           QFileInfo(path).suffix());
00411     }
00412     
00413     return path;
00414 }
00415 
00416 void
00417 InteractiveFileFinder::registerLastOpenedFilePath(FileType type, QString path)
00418 {
00419     QString settingsKeyStub;
00420 
00421     switch (type) {
00422     case SessionFile:
00423         settingsKeyStub = "session";
00424         break;
00425 
00426     case AudioFile:
00427         settingsKeyStub = "audio";
00428         break;
00429 
00430     case LayerFile:
00431         settingsKeyStub = "layer";
00432         break;
00433 
00434     case LayerFileNoMidi:
00435         settingsKeyStub = "layer";
00436         break;
00437 
00438     case LayerFileNonSV:
00439         settingsKeyStub = "layer";
00440         break;
00441 
00442     case LayerFileNoMidiNonSV:
00443         settingsKeyStub = "layer";
00444         break;
00445 
00446     case SessionOrAudioFile:
00447         settingsKeyStub = "last";
00448         break;
00449 
00450     case ImageFile:
00451         settingsKeyStub = "image";
00452         break;
00453 
00454     case CSVFile:
00455         settingsKeyStub = "layer";
00456         break;
00457 
00458     case AnyFile:
00459         settingsKeyStub = "last";
00460         break;
00461     }
00462 
00463     if (path != "") {
00464         QSettings settings;
00465         settings.beginGroup("FileFinder");
00466         path = QFileInfo(path).absoluteDir().canonicalPath();
00467         QString suffix = QFileInfo(path).suffix();
00468         settings.setValue(settingsKeyStub + "path", path);
00469         settings.setValue(settingsKeyStub + "suffix", suffix);
00470         settings.setValue("lastpath", path);
00471     }
00472 }
00473     
00474 QString
00475 InteractiveFileFinder::find(FileType type, QString location, QString lastKnownLocation)
00476 {
00477     if (FileSource::canHandleScheme(location)) {
00478         if (FileSource(location).isAvailable()) {
00479             SVDEBUG << "InteractiveFileFinder::find: ok, it's available... returning" << endl;
00480             return location;
00481         }
00482     }
00483 
00484     if (QFileInfo(location).exists()) return location;
00485 
00486     QString foundAt = "";
00487 
00488     if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
00489         return foundAt;
00490     }
00491 
00492     if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
00493         return foundAt;
00494     }
00495 
00496     return locateInteractive(type, location);
00497 }
00498 
00499 QString
00500 InteractiveFileFinder::findRelative(QString location, QString relativeTo)
00501 {
00502     if (relativeTo == "") return "";
00503 
00504     SVDEBUG << "Looking for \"" << location << "\" next to \""
00505               << relativeTo << "\"..." << endl;
00506 
00507     QString fileName;
00508     QString resolved;
00509 
00510     if (FileSource::isRemote(location)) {
00511         fileName = QUrl(location).path().section('/', -1, -1,
00512                                                  QString::SectionSkipEmpty);
00513     } else {
00514         if (QUrl(location).scheme() == "file") {
00515             location = QUrl(location).toLocalFile();
00516         }
00517         fileName = QFileInfo(location).fileName();
00518     }
00519 
00520     if (FileSource::isRemote(relativeTo)) {
00521         resolved = QUrl(relativeTo).resolved(fileName).toString();
00522         if (!FileSource(resolved).isAvailable()) resolved = "";
00523         cerr << "resolved: " << resolved << endl;
00524     } else {
00525         if (QUrl(relativeTo).scheme() == "file") {
00526             relativeTo = QUrl(relativeTo).toLocalFile();
00527         }
00528         resolved = QFileInfo(relativeTo).dir().filePath(fileName);
00529         if (!QFileInfo(resolved).exists() ||
00530             !QFileInfo(resolved).isFile() ||
00531             !QFileInfo(resolved).isReadable()) {
00532             resolved = "";
00533         }
00534     }
00535             
00536     return resolved;
00537 }
00538 
00539 QString
00540 InteractiveFileFinder::locateInteractive(FileType type, QString thing)
00541 {
00542     QString question;
00543     if (type == AudioFile) {
00544         question = tr("<b>File not found</b><p>Audio file \"%1\" could not be opened.\nDo you want to locate it?");
00545     } else {
00546         question = tr("<b>File not found</b><p>File \"%1\" could not be opened.\nDo you want to locate it?");
00547     }
00548 
00549     QString path = "";
00550     bool done = false;
00551 
00552     while (!done) {
00553 
00554         int rv = QMessageBox::question
00555             (0, 
00556              tr("Failed to open file"),
00557              question.arg(thing),
00558              tr("Locate file..."),
00559              tr("Use URL..."),
00560              tr("Cancel"),
00561              0, 2);
00562         
00563         switch (rv) {
00564 
00565         case 0: // Locate file
00566 
00567             if (QFileInfo(thing).dir().exists()) {
00568                 path = QFileInfo(thing).dir().canonicalPath();
00569             }
00570             
00571             path = getOpenFileName(type, path);
00572             done = (path != "");
00573             break;
00574 
00575         case 1: // Use URL
00576         {
00577             bool ok = false;
00578             path = QInputDialog::getText
00579                 (0, tr("Use URL"),
00580                  tr("Please enter the URL to use for this file:"),
00581                  QLineEdit::Normal, "", &ok);
00582 
00583             if (ok && path != "") {
00584                 if (FileSource(path).isAvailable()) {
00585                     done = true;
00586                 } else {
00587                     QMessageBox::critical
00588                         (0, tr("Failed to open location"),
00589                          tr("<b>Failed to open location</b><p>URL \"%1\" could not be opened").arg(path));
00590                     path = "";
00591                 }
00592             }
00593             break;
00594         }
00595 
00596         case 2: // Cancel
00597             path = "";
00598             done = true;
00599             break;
00600         }
00601     }
00602 
00603     if (path != "") m_lastLocatedLocation = path;
00604     return path;
00605 }
00606 
00607