annotate data/fileio/FileFinder.cpp @ 346:a9ccd644f3bf

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