annotate data/fileio/FileFinder.cpp @ 301:73537d900d4b

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