annotate widgets/FileFinder.cpp @ 387:f329416bf1a5

* Permit holding Shift while dragging an instant or other edited thing, to override the initial drag resistance (for #1928943 sticky time instants) * better handling of updates during progressive decode load * ready() signal from model (used by vect)
author Chris Cannam
date Mon, 19 May 2008 17:23:11 +0000
parents 22b72f0f6a4e
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