annotate widgets/FileFinder.cpp @ 378:22b72f0f6a4e

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