annotate data/fileio/FileFinder.cpp @ 302:726b32522e3f

* Phase 1 of an image layer.
author Chris Cannam
date Thu, 04 Oct 2007 16:34:11 +0000
parents 73537d900d4b
children fc656505c573
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@210 17 #include "RemoteFile.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@211 285
Chris@211 286 if (type == LayerFile && fi.suffix() == "") {
Chris@211 287 QString expectedExtension;
Chris@211 288 QString selectedFilter = dialog.selectedFilter();
Chris@211 289 if (selectedFilter.contains(".svl")) {
Chris@211 290 expectedExtension = "svl";
Chris@211 291 } else if (selectedFilter.contains(".txt")) {
Chris@211 292 expectedExtension = "txt";
Chris@211 293 } else if (selectedFilter.contains(".csv")) {
Chris@211 294 expectedExtension = "csv";
Chris@301 295 } else if (selectedFilter.contains(".mid")) {
Chris@301 296 expectedExtension = "mid";
Chris@211 297 }
Chris@211 298 if (expectedExtension != "") {
Chris@211 299 path = QString("%1.%2").arg(path).arg(expectedExtension);
Chris@211 300 fi = QFileInfo(path);
Chris@211 301 }
Chris@211 302 }
Chris@211 303
Chris@211 304 if (fi.isDir()) {
Chris@211 305 QMessageBox::critical(0, tr("Directory selected"),
Chris@211 306 tr("File \"%1\" is a directory").arg(path));
Chris@211 307 continue;
Chris@211 308 }
Chris@211 309
Chris@211 310 if (fi.exists()) {
Chris@211 311 if (QMessageBox::question(0, tr("File exists"),
Chris@211 312 tr("The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
Chris@211 313 QMessageBox::Ok,
Chris@211 314 QMessageBox::Cancel) != QMessageBox::Ok) {
Chris@211 315 continue;
Chris@211 316 }
Chris@211 317 }
Chris@211 318
Chris@211 319 good = true;
Chris@211 320 }
Chris@211 321
Chris@211 322 if (path != "") {
Chris@211 323 settings.setValue(settingsKey,
Chris@211 324 QFileInfo(path).absoluteDir().canonicalPath());
Chris@211 325 }
Chris@211 326
Chris@211 327 return path;
Chris@211 328 }
Chris@211 329
Chris@211 330 void
Chris@211 331 FileFinder::registerLastOpenedFilePath(FileType type, QString path)
Chris@211 332 {
Chris@211 333 QString settingsKey;
Chris@211 334
Chris@211 335 switch (type) {
Chris@211 336 case SessionFile:
Chris@211 337 settingsKey = "sessionpath";
Chris@211 338 break;
Chris@211 339
Chris@211 340 case AudioFile:
Chris@211 341 settingsKey = "audiopath";
Chris@211 342 break;
Chris@211 343
Chris@211 344 case LayerFile:
Chris@211 345 settingsKey = "layerpath";
Chris@211 346 break;
Chris@211 347
Chris@301 348 case LayerFileNoMidi:
Chris@301 349 settingsKey = "layerpath";
Chris@301 350 break;
Chris@301 351
Chris@211 352 case SessionOrAudioFile:
Chris@211 353 settingsKey = "lastpath";
Chris@211 354 break;
Chris@211 355
Chris@250 356 case ImageFile:
Chris@250 357 settingsKey = "imagepath";
Chris@250 358 break;
Chris@250 359
Chris@211 360 case AnyFile:
Chris@211 361 settingsKey = "lastpath";
Chris@211 362 break;
Chris@211 363 }
Chris@211 364
Chris@211 365 if (path != "") {
Chris@211 366 QSettings settings;
Chris@211 367 settings.beginGroup("FileFinder");
Chris@211 368 path = QFileInfo(path).absoluteDir().canonicalPath();
Chris@211 369 settings.setValue(settingsKey, path);
Chris@211 370 settings.setValue("lastpath", path);
Chris@211 371 }
Chris@211 372 }
Chris@211 373
Chris@211 374 QString
Chris@211 375 FileFinder::find(FileType type, QString location, QString lastKnownLocation)
Chris@211 376 {
Chris@211 377 if (QFileInfo(location).exists()) return location;
Chris@211 378
Chris@211 379 if (RemoteFile::canHandleScheme(QUrl(location))) {
Chris@211 380 RemoteFile rf(location);
Chris@211 381 bool available = rf.isAvailable();
Chris@211 382 rf.deleteLocalFile();
Chris@211 383 if (available) return location;
Chris@211 384 }
Chris@211 385
Chris@211 386 QString foundAt = "";
Chris@211 387
Chris@211 388 if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
Chris@211 389 return foundAt;
Chris@211 390 }
Chris@211 391
Chris@211 392 if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
Chris@211 393 return foundAt;
Chris@211 394 }
Chris@211 395
Chris@211 396 return locateInteractive(type, location);
Chris@211 397 }
Chris@211 398
Chris@211 399 QString
Chris@211 400 FileFinder::findRelative(QString location, QString relativeTo)
Chris@211 401 {
Chris@211 402 if (relativeTo == "") return "";
Chris@211 403
Chris@211 404 std::cerr << "Looking for \"" << location.toStdString() << "\" next to \""
Chris@211 405 << relativeTo.toStdString() << "\"..." << std::endl;
Chris@211 406
Chris@211 407 QString fileName;
Chris@211 408 QString resolved;
Chris@211 409
Chris@211 410 if (RemoteFile::canHandleScheme(QUrl(location))) {
Chris@211 411 fileName = QUrl(location).path().section('/', -1, -1,
Chris@211 412 QString::SectionSkipEmpty);
Chris@211 413 } else {
Chris@211 414 fileName = QFileInfo(location).fileName();
Chris@211 415 }
Chris@211 416
Chris@211 417 if (RemoteFile::canHandleScheme(QUrl(relativeTo))) {
Chris@211 418 resolved = QUrl(relativeTo).resolved(fileName).toString();
Chris@211 419 RemoteFile rf(resolved);
Chris@211 420 if (!rf.isAvailable()) resolved = "";
Chris@211 421 std::cerr << "resolved: " << resolved.toStdString() << std::endl;
Chris@211 422 rf.deleteLocalFile();
Chris@211 423 } else {
Chris@211 424 resolved = QFileInfo(relativeTo).dir().filePath(fileName);
Chris@211 425 if (!QFileInfo(resolved).exists() ||
Chris@211 426 !QFileInfo(resolved).isFile() ||
Chris@211 427 !QFileInfo(resolved).isReadable()) {
Chris@211 428 resolved = "";
Chris@211 429 }
Chris@211 430 }
Chris@211 431
Chris@211 432 return resolved;
Chris@211 433 }
Chris@211 434
Chris@211 435 QString
Chris@211 436 FileFinder::locateInteractive(FileType type, QString thing)
Chris@211 437 {
Chris@211 438 QString question;
Chris@211 439 if (type == AudioFile) {
Chris@211 440 question = tr("Audio file \"%1\" could not be opened.\nDo you want to locate it?");
Chris@211 441 } else {
Chris@211 442 question = tr("File \"%1\" could not be opened.\nDo you want to locate it?");
Chris@211 443 }
Chris@211 444
Chris@211 445 QString path = "";
Chris@211 446 bool done = false;
Chris@211 447
Chris@211 448 while (!done) {
Chris@211 449
Chris@211 450 int rv = QMessageBox::question
Chris@211 451 (0,
Chris@211 452 tr("Failed to open file"),
Chris@211 453 question.arg(thing),
Chris@211 454 tr("Locate file..."),
Chris@211 455 tr("Use URL..."),
Chris@211 456 tr("Cancel"),
Chris@211 457 0, 2);
Chris@211 458
Chris@211 459 switch (rv) {
Chris@211 460
Chris@211 461 case 0: // Locate file
Chris@211 462
Chris@211 463 if (QFileInfo(thing).dir().exists()) {
Chris@211 464 path = QFileInfo(thing).dir().canonicalPath();
Chris@211 465 }
Chris@211 466
Chris@211 467 path = getOpenFileName(type, path);
Chris@211 468 done = (path != "");
Chris@211 469 break;
Chris@211 470
Chris@211 471 case 1: // Use URL
Chris@211 472 {
Chris@211 473 bool ok = false;
Chris@211 474 path = QInputDialog::getText
Chris@211 475 (0, tr("Use URL"),
Chris@211 476 tr("Please enter the URL to use for this file:"),
Chris@211 477 QLineEdit::Normal, "", &ok);
Chris@211 478
Chris@211 479 if (ok && path != "") {
Chris@211 480 RemoteFile rf(path);
Chris@211 481 if (rf.isAvailable()) {
Chris@211 482 done = true;
Chris@211 483 } else {
Chris@211 484 QMessageBox::critical
Chris@211 485 (0, tr("Failed to open location"),
Chris@211 486 tr("URL \"%1\" could not be opened").arg(path));
Chris@211 487 path = "";
Chris@211 488 }
Chris@211 489 rf.deleteLocalFile();
Chris@211 490 }
Chris@211 491 break;
Chris@211 492 }
Chris@211 493
Chris@211 494 case 2: // Cancel
Chris@211 495 path = "";
Chris@211 496 done = true;
Chris@211 497 break;
Chris@210 498 }
Chris@210 499 }
Chris@210 500
Chris@211 501 if (path != "") m_lastLocatedLocation = path;
Chris@211 502 return path;
Chris@210 503 }
Chris@210 504
Chris@210 505