annotate widgets/FileFinder.cpp @ 422:ea9e41027b93

...
author Chris Cannam
date Fri, 26 Sep 2008 16:34:31 +0000
parents 33b7f5e54d60
children 035d62c4cddf
rev   line source
Chris@378 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@378 2
Chris@378 3 /*
Chris@378 4 Sonic Visualiser
Chris@378 5 An audio file viewer and annotation editor.
Chris@378 6 Centre for Digital Music, Queen Mary, University of London.
Chris@378 7 This file copyright 2007 QMUL.
Chris@378 8
Chris@378 9 This program is free software; you can redistribute it and/or
Chris@378 10 modify it under the terms of the GNU General Public License as
Chris@378 11 published by the Free Software Foundation; either version 2 of the
Chris@378 12 License, or (at your option) any later version. See the file
Chris@378 13 COPYING included with this distribution for more information.
Chris@378 14 */
Chris@378 15
Chris@378 16 #include "FileFinder.h"
Chris@378 17 #include "data/fileio/FileSource.h"
Chris@378 18 #include "data/fileio/AudioFileReaderFactory.h"
Chris@378 19 #include "data/fileio/DataFileReaderFactory.h"
Chris@410 20 #include "rdf/RDFImporter.h"
Chris@378 21
Chris@378 22 #include <QFileInfo>
Chris@378 23 #include <QMessageBox>
Chris@378 24 #include <QFileDialog>
Chris@378 25 #include <QInputDialog>
Chris@378 26 #include <QImageReader>
Chris@378 27 #include <QSettings>
Chris@378 28
Chris@378 29 #include <iostream>
Chris@378 30
Chris@378 31 FileFinder *
Chris@378 32 FileFinder::m_instance = 0;
Chris@378 33
Chris@378 34 FileFinder::FileFinder() :
Chris@378 35 m_lastLocatedLocation("")
Chris@378 36 {
Chris@378 37 }
Chris@378 38
Chris@378 39 FileFinder::~FileFinder()
Chris@378 40 {
Chris@378 41 }
Chris@378 42
Chris@378 43 FileFinder *
Chris@378 44 FileFinder::getInstance()
Chris@378 45 {
Chris@378 46 if (m_instance == 0) {
Chris@378 47 m_instance = new FileFinder();
Chris@378 48 }
Chris@378 49 return m_instance;
Chris@378 50 }
Chris@378 51
Chris@378 52 QString
Chris@378 53 FileFinder::getOpenFileName(FileType type, QString fallbackLocation)
Chris@378 54 {
Chris@378 55 QString settingsKey;
Chris@378 56 QString lastPath = fallbackLocation;
Chris@378 57
Chris@378 58 QString title = tr("Select file");
Chris@378 59 QString filter = tr("All files (*.*)");
Chris@378 60
Chris@378 61 switch (type) {
Chris@378 62
Chris@378 63 case SessionFile:
Chris@378 64 settingsKey = "sessionpath";
Chris@378 65 title = tr("Select a session file");
Chris@378 66 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@378 67 break;
Chris@378 68
Chris@378 69 case AudioFile:
Chris@378 70 settingsKey = "audiopath";
Chris@378 71 title = "Select an audio file";
Chris@378 72 filter = tr("Audio files (%1)\nAll files (*.*)")
Chris@378 73 .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@378 74 break;
Chris@378 75
Chris@378 76 case LayerFile:
Chris@378 77 settingsKey = "layerpath";
Chris@410 78 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 (%3)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()).arg(RDFImporter::getKnownExtensions()).arg(RDFImporter::getKnownExtensions());
Chris@378 79 break;
Chris@378 80
Chris@378 81 case LayerFileNoMidi:
Chris@378 82 settingsKey = "layerpath";
Chris@410 83 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 (%3)\nText files (*.txt)\nAll files (*.*)").arg(DataFileReaderFactory::getKnownExtensions()).arg(RDFImporter::getKnownExtensions()).arg(RDFImporter::getKnownExtensions());
Chris@378 84 break;
Chris@378 85
Chris@378 86 case SessionOrAudioFile:
Chris@378 87 settingsKey = "lastpath";
Chris@378 88 filter = tr("All supported files (*.sv %1)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nAll files (*.*)")
Chris@378 89 .arg(AudioFileReaderFactory::getKnownExtensions());
Chris@378 90 break;
Chris@378 91
Chris@378 92 case ImageFile:
Chris@378 93 settingsKey = "imagepath";
Chris@378 94 {
Chris@378 95 QStringList fmts;
Chris@378 96 QList<QByteArray> formats = QImageReader::supportedImageFormats();
Chris@378 97 for (QList<QByteArray>::iterator i = formats.begin();
Chris@378 98 i != formats.end(); ++i) {
Chris@378 99 fmts.push_back(QString("*.%1")
Chris@378 100 .arg(QString::fromLocal8Bit(*i).toLower()));
Chris@378 101 }
Chris@378 102 filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" "));
Chris@378 103 }
Chris@378 104 break;
Chris@378 105
Chris@378 106 case AnyFile:
Chris@378 107 settingsKey = "lastpath";
Chris@410 108 filter = tr("All supported files (*.sv %1 %2 %3)\nSonic Visualiser session files (*.sv)\nAudio files (%1)\nLayer files (%2)\nAll files (*.*)")
Chris@378 109 .arg(AudioFileReaderFactory::getKnownExtensions())
Chris@410 110 .arg(DataFileReaderFactory::getKnownExtensions())
Chris@410 111 .arg(RDFImporter::getKnownExtensions());
Chris@378 112 break;
Chris@378 113 };
Chris@378 114
Chris@378 115 if (lastPath == "") {
Chris@378 116 char *home = getenv("HOME");
Chris@378 117 if (home) lastPath = home;
Chris@378 118 else lastPath = ".";
Chris@378 119 } else if (QFileInfo(lastPath).isDir()) {
Chris@378 120 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@378 121 } else {
Chris@378 122 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@378 123 }
Chris@378 124
Chris@378 125 QSettings settings;
Chris@378 126 settings.beginGroup("FileFinder");
Chris@378 127 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@378 128
Chris@378 129 QString path = "";
Chris@378 130
Chris@378 131 // Use our own QFileDialog just for symmetry with getSaveFileName below
Chris@378 132
Chris@378 133 QFileDialog dialog;
Chris@378 134 dialog.setFilters(filter.split('\n'));
Chris@378 135 dialog.setWindowTitle(title);
Chris@378 136 dialog.setDirectory(lastPath);
Chris@378 137
Chris@378 138 dialog.setAcceptMode(QFileDialog::AcceptOpen);
Chris@378 139 dialog.setFileMode(QFileDialog::ExistingFile);
Chris@378 140
Chris@378 141 if (dialog.exec()) {
Chris@378 142 QStringList files = dialog.selectedFiles();
Chris@378 143 if (!files.empty()) path = *files.begin();
Chris@378 144
Chris@378 145 QFileInfo fi(path);
Chris@378 146
Chris@378 147 if (!fi.exists()) {
Chris@378 148
Chris@378 149 QMessageBox::critical(0, tr("File does not exist"),
Chris@378 150 tr("File \"%1\" does not exist").arg(path));
Chris@378 151 path = "";
Chris@378 152
Chris@378 153 } else if (!fi.isReadable()) {
Chris@378 154
Chris@378 155 QMessageBox::critical(0, tr("File is not readable"),
Chris@378 156 tr("File \"%1\" can not be read").arg(path));
Chris@378 157 path = "";
Chris@378 158
Chris@378 159 } else if (fi.isDir()) {
Chris@378 160
Chris@378 161 QMessageBox::critical(0, tr("Directory selected"),
Chris@378 162 tr("File \"%1\" is a directory").arg(path));
Chris@378 163 path = "";
Chris@378 164
Chris@378 165 } else if (!fi.isFile()) {
Chris@378 166
Chris@378 167 QMessageBox::critical(0, tr("Non-file selected"),
Chris@378 168 tr("Path \"%1\" is not a file").arg(path));
Chris@378 169 path = "";
Chris@378 170
Chris@378 171 } else if (fi.size() == 0) {
Chris@378 172
Chris@378 173 QMessageBox::critical(0, tr("File is empty"),
Chris@378 174 tr("File \"%1\" is empty").arg(path));
Chris@378 175 path = "";
Chris@378 176 }
Chris@378 177 }
Chris@378 178
Chris@378 179 if (path != "") {
Chris@378 180 settings.setValue(settingsKey,
Chris@378 181 QFileInfo(path).absoluteDir().canonicalPath());
Chris@378 182 }
Chris@378 183
Chris@378 184 return path;
Chris@378 185 }
Chris@378 186
Chris@378 187 QString
Chris@378 188 FileFinder::getSaveFileName(FileType type, QString fallbackLocation)
Chris@378 189 {
Chris@378 190 QString settingsKey;
Chris@378 191 QString lastPath = fallbackLocation;
Chris@378 192
Chris@378 193 QString title = tr("Select file");
Chris@378 194 QString filter = tr("All files (*.*)");
Chris@378 195
Chris@378 196 switch (type) {
Chris@378 197
Chris@378 198 case SessionFile:
Chris@378 199 settingsKey = "savesessionpath";
Chris@378 200 title = tr("Select a session file");
Chris@378 201 filter = tr("Sonic Visualiser session files (*.sv)\nAll files (*.*)");
Chris@378 202 break;
Chris@378 203
Chris@378 204 case AudioFile:
Chris@378 205 settingsKey = "saveaudiopath";
Chris@378 206 title = "Select an audio file";
Chris@378 207 title = tr("Select a file to export to");
Chris@378 208 filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
Chris@378 209 break;
Chris@378 210
Chris@378 211 case LayerFile:
Chris@378 212 settingsKey = "savelayerpath";
Chris@378 213 title = tr("Select a file to export to");
Chris@378 214 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)");
Chris@378 215 break;
Chris@378 216
Chris@378 217 case LayerFileNoMidi:
Chris@378 218 settingsKey = "savelayerpath";
Chris@378 219 title = tr("Select a file to export to");
Chris@378 220 filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
Chris@378 221 break;
Chris@378 222
Chris@378 223 case SessionOrAudioFile:
Chris@378 224 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << std::endl;
Chris@378 225 abort();
Chris@378 226
Chris@378 227 case ImageFile:
Chris@378 228 settingsKey = "saveimagepath";
Chris@378 229 title = tr("Select a file to export to");
Chris@378 230 filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
Chris@378 231 break;
Chris@378 232
Chris@378 233 case AnyFile:
Chris@378 234 std::cerr << "ERROR: Internal error: FileFinder::getSaveFileName: AnyFile cannot be used here" << std::endl;
Chris@378 235 abort();
Chris@378 236 };
Chris@378 237
Chris@378 238 if (lastPath == "") {
Chris@378 239 char *home = getenv("HOME");
Chris@378 240 if (home) lastPath = home;
Chris@378 241 else lastPath = ".";
Chris@378 242 } else if (QFileInfo(lastPath).isDir()) {
Chris@378 243 lastPath = QFileInfo(lastPath).canonicalPath();
Chris@378 244 } else {
Chris@378 245 lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
Chris@378 246 }
Chris@378 247
Chris@378 248 QSettings settings;
Chris@378 249 settings.beginGroup("FileFinder");
Chris@378 250 lastPath = settings.value(settingsKey, lastPath).toString();
Chris@378 251
Chris@378 252 QString path = "";
Chris@378 253
Chris@378 254 // Use our own QFileDialog instead of static functions, as we may
Chris@378 255 // need to adjust the file extension based on the selected filter
Chris@378 256
Chris@378 257 QFileDialog dialog;
Chris@378 258 dialog.setFilters(filter.split('\n'));
Chris@378 259 dialog.setWindowTitle(title);
Chris@378 260 dialog.setDirectory(lastPath);
Chris@378 261
Chris@378 262 dialog.setAcceptMode(QFileDialog::AcceptSave);
Chris@378 263 dialog.setFileMode(QFileDialog::AnyFile);
Chris@378 264 dialog.setConfirmOverwrite(false); // we'll do that
Chris@378 265
Chris@378 266 if (type == SessionFile) {
Chris@378 267 dialog.setDefaultSuffix("sv");
Chris@378 268 } else if (type == AudioFile) {
Chris@378 269 dialog.setDefaultSuffix("wav");
Chris@378 270 } else if (type == ImageFile) {
Chris@378 271 dialog.setDefaultSuffix("png");
Chris@378 272 }
Chris@378 273
Chris@378 274 bool good = false;
Chris@378 275
Chris@378 276 while (!good) {
Chris@378 277
Chris@378 278 path = "";
Chris@378 279
Chris@378 280 if (!dialog.exec()) break;
Chris@378 281
Chris@378 282 QStringList files = dialog.selectedFiles();
Chris@378 283 if (files.empty()) break;
Chris@378 284 path = *files.begin();
Chris@378 285
Chris@378 286 QFileInfo fi(path);
Chris@378 287
Chris@378 288 std::cerr << "type = " << type << ", suffix = " << fi.suffix().toStdString() << std::endl;
Chris@378 289
Chris@378 290 if ((type == LayerFile || type == LayerFileNoMidi)
Chris@378 291 && fi.suffix() == "") {
Chris@378 292 QString expectedExtension;
Chris@378 293 QString selectedFilter = dialog.selectedFilter();
Chris@378 294 if (selectedFilter.contains(".svl")) {
Chris@378 295 expectedExtension = "svl";
Chris@378 296 } else if (selectedFilter.contains(".txt")) {
Chris@378 297 expectedExtension = "txt";
Chris@378 298 } else if (selectedFilter.contains(".csv")) {
Chris@378 299 expectedExtension = "csv";
Chris@378 300 } else if (selectedFilter.contains(".mid")) {
Chris@378 301 expectedExtension = "mid";
Chris@378 302 }
Chris@378 303 std::cerr << "expected extension = " << expectedExtension.toStdString() << std::endl;
Chris@378 304 if (expectedExtension != "") {
Chris@378 305 path = QString("%1.%2").arg(path).arg(expectedExtension);
Chris@378 306 fi = QFileInfo(path);
Chris@378 307 }
Chris@378 308 }
Chris@378 309
Chris@378 310 if (fi.isDir()) {
Chris@378 311 QMessageBox::critical(0, tr("Directory selected"),
Chris@378 312 tr("File \"%1\" is a directory").arg(path));
Chris@378 313 continue;
Chris@378 314 }
Chris@378 315
Chris@378 316 if (fi.exists()) {
Chris@378 317 if (QMessageBox::question(0, tr("File exists"),
Chris@378 318 tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
Chris@378 319 QMessageBox::Ok,
Chris@378 320 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@378 321 continue;
Chris@378 322 }
Chris@378 323 }
Chris@378 324
Chris@378 325 good = true;
Chris@378 326 }
Chris@378 327
Chris@378 328 if (path != "") {
Chris@378 329 settings.setValue(settingsKey,
Chris@378 330 QFileInfo(path).absoluteDir().canonicalPath());
Chris@378 331 }
Chris@378 332
Chris@378 333 return path;
Chris@378 334 }
Chris@378 335
Chris@378 336 void
Chris@378 337 FileFinder::registerLastOpenedFilePath(FileType type, QString path)
Chris@378 338 {
Chris@378 339 QString settingsKey;
Chris@378 340
Chris@378 341 switch (type) {
Chris@378 342 case SessionFile:
Chris@378 343 settingsKey = "sessionpath";
Chris@378 344 break;
Chris@378 345
Chris@378 346 case AudioFile:
Chris@378 347 settingsKey = "audiopath";
Chris@378 348 break;
Chris@378 349
Chris@378 350 case LayerFile:
Chris@378 351 settingsKey = "layerpath";
Chris@378 352 break;
Chris@378 353
Chris@378 354 case LayerFileNoMidi:
Chris@378 355 settingsKey = "layerpath";
Chris@378 356 break;
Chris@378 357
Chris@378 358 case SessionOrAudioFile:
Chris@378 359 settingsKey = "lastpath";
Chris@378 360 break;
Chris@378 361
Chris@378 362 case ImageFile:
Chris@378 363 settingsKey = "imagepath";
Chris@378 364 break;
Chris@378 365
Chris@378 366 case AnyFile:
Chris@378 367 settingsKey = "lastpath";
Chris@378 368 break;
Chris@378 369 }
Chris@378 370
Chris@378 371 if (path != "") {
Chris@378 372 QSettings settings;
Chris@378 373 settings.beginGroup("FileFinder");
Chris@378 374 path = QFileInfo(path).absoluteDir().canonicalPath();
Chris@378 375 settings.setValue(settingsKey, path);
Chris@378 376 settings.setValue("lastpath", path);
Chris@378 377 }
Chris@378 378 }
Chris@378 379
Chris@378 380 QString
Chris@378 381 FileFinder::find(FileType type, QString location, QString lastKnownLocation)
Chris@378 382 {
Chris@378 383 if (FileSource::canHandleScheme(location)) {
Chris@378 384 if (FileSource(location).isAvailable()) {
Chris@378 385 std::cerr << "FileFinder::find: ok, it's available... returning" << std::endl;
Chris@378 386 return location;
Chris@378 387 }
Chris@378 388 }
Chris@378 389
Chris@378 390 if (QFileInfo(location).exists()) return location;
Chris@378 391
Chris@378 392 QString foundAt = "";
Chris@378 393
Chris@378 394 if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
Chris@378 395 return foundAt;
Chris@378 396 }
Chris@378 397
Chris@378 398 if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
Chris@378 399 return foundAt;
Chris@378 400 }
Chris@378 401
Chris@378 402 return locateInteractive(type, location);
Chris@378 403 }
Chris@378 404
Chris@378 405 QString
Chris@378 406 FileFinder::findRelative(QString location, QString relativeTo)
Chris@378 407 {
Chris@378 408 if (relativeTo == "") return "";
Chris@378 409
Chris@378 410 std::cerr << "Looking for \"" << location.toStdString() << "\" next to \""
Chris@378 411 << relativeTo.toStdString() << "\"..." << std::endl;
Chris@378 412
Chris@378 413 QString fileName;
Chris@378 414 QString resolved;
Chris@378 415
Chris@378 416 if (FileSource::isRemote(location)) {
Chris@378 417 fileName = QUrl(location).path().section('/', -1, -1,
Chris@378 418 QString::SectionSkipEmpty);
Chris@378 419 } else {
Chris@378 420 if (QUrl(location).scheme() == "file") {
Chris@378 421 location = QUrl(location).toLocalFile();
Chris@378 422 }
Chris@378 423 fileName = QFileInfo(location).fileName();
Chris@378 424 }
Chris@378 425
Chris@378 426 if (FileSource::isRemote(relativeTo)) {
Chris@378 427 resolved = QUrl(relativeTo).resolved(fileName).toString();
Chris@378 428 if (!FileSource(resolved).isAvailable()) resolved = "";
Chris@378 429 std::cerr << "resolved: " << resolved.toStdString() << std::endl;
Chris@378 430 } else {
Chris@378 431 if (QUrl(relativeTo).scheme() == "file") {
Chris@378 432 relativeTo = QUrl(relativeTo).toLocalFile();
Chris@378 433 }
Chris@378 434 resolved = QFileInfo(relativeTo).dir().filePath(fileName);
Chris@378 435 if (!QFileInfo(resolved).exists() ||
Chris@378 436 !QFileInfo(resolved).isFile() ||
Chris@378 437 !QFileInfo(resolved).isReadable()) {
Chris@378 438 resolved = "";
Chris@378 439 }
Chris@378 440 }
Chris@378 441
Chris@378 442 return resolved;
Chris@378 443 }
Chris@378 444
Chris@378 445 QString
Chris@378 446 FileFinder::locateInteractive(FileType type, QString thing)
Chris@378 447 {
Chris@378 448 QString question;
Chris@378 449 if (type == AudioFile) {
Chris@378 450 question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?");
Chris@378 451 } else {
Chris@378 452 question = tr("File \"%1\" could not be opened.\nDo you want to locate it?");
Chris@378 453 }
Chris@378 454
Chris@378 455 QString path = "";
Chris@378 456 bool done = false;
Chris@378 457
Chris@378 458 while (!done) {
Chris@378 459
Chris@378 460 int rv = QMessageBox::question
Chris@378 461 (0,
Chris@378 462 tr("Failed to open file"),
Chris@378 463 question.arg(thing),
Chris@378 464 tr("Locate file..."),
Chris@378 465 tr("Use URL..."),
Chris@378 466 tr("Cancel"),
Chris@378 467 0, 2);
Chris@378 468
Chris@378 469 switch (rv) {
Chris@378 470
Chris@378 471 case 0: // Locate file
Chris@378 472
Chris@378 473 if (QFileInfo(thing).dir().exists()) {
Chris@378 474 path = QFileInfo(thing).dir().canonicalPath();
Chris@378 475 }
Chris@378 476
Chris@378 477 path = getOpenFileName(type, path);
Chris@378 478 done = (path != "");
Chris@378 479 break;
Chris@378 480
Chris@378 481 case 1: // Use URL
Chris@378 482 {
Chris@378 483 bool ok = false;
Chris@378 484 path = QInputDialog::getText
Chris@378 485 (0, tr("Use URL"),
Chris@378 486 tr("Please enter the URL to use for this file:"),
Chris@378 487 QLineEdit::Normal, "", &ok);
Chris@378 488
Chris@378 489 if (ok && path != "") {
Chris@378 490 if (FileSource(path).isAvailable()) {
Chris@378 491 done = true;
Chris@378 492 } else {
Chris@378 493 QMessageBox::critical
Chris@378 494 (0, tr("Failed to open location"),
Chris@378 495 tr("URL \"%1\" could not be opened").arg(path));
Chris@378 496 path = "";
Chris@378 497 }
Chris@378 498 }
Chris@378 499 break;
Chris@378 500 }
Chris@378 501
Chris@378 502 case 2: // Cancel
Chris@378 503 path = "";
Chris@378 504 done = true;
Chris@378 505 break;
Chris@378 506 }
Chris@378 507 }
Chris@378 508
Chris@378 509 if (path != "") m_lastLocatedLocation = path;
Chris@378 510 return path;
Chris@378 511 }
Chris@378 512
Chris@378 513