svgui  1.9
InteractiveFileFinder.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2007 QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "InteractiveFileFinder.h"
17 #include "data/fileio/FileSource.h"
18 #include "data/fileio/AudioFileReaderFactory.h"
19 #include "data/fileio/DataFileReaderFactory.h"
20 #include "rdf/RDFImporter.h"
21 #include "rdf/RDFExporter.h"
22 
23 #include <QFileInfo>
24 #include <QMessageBox>
25 #include <QFileDialog>
26 #include <QInputDialog>
27 #include <QImageReader>
28 #include <QSettings>
29 
30 #include <iostream>
31 
34 
36  m_sessionExtension("sv"),
37  m_lastLocatedLocation(""),
38  m_parent(0)
39 {
40  SVDEBUG << "Registering interactive file finder" << endl;
41  FileFinder::registerFileFinder(this);
42 }
43 
45 {
46 }
47 
48 void
50 {
51  getInstance()->m_parent = parent;
52 }
53 
54 void
56 {
57  m_sessionExtension = extension;
58 }
59 
60 QString
61 InteractiveFileFinder::getOpenFileName(FileType type, QString fallbackLocation)
62 {
63  QString settingsKeyStub;
64  QString lastPath = fallbackLocation;
65 
66  QString title = tr("Select file");
67  QString filter = tr("All files (*.*)");
68 
69  switch (type) {
70 
71  case SessionFile:
72  settingsKeyStub = "session";
73  title = tr("Select a session file");
74  filter = tr("%1 session files (*.%1)\nRDF files (%3)\nAll files (*.*)")
75  .arg(QApplication::applicationName())
76  .arg(m_sessionExtension)
77  .arg(RDFImporter::getKnownExtensions());
78  break;
79 
80  case AudioFile:
81  settingsKeyStub = "audio";
82  title = "Select an audio file";
83  filter = tr("Audio files (%1)\nAll files (*.*)")
84  .arg(AudioFileReaderFactory::getKnownExtensions());
85  break;
86 
87  case LayerFile:
88  settingsKeyStub = "layer";
89  filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
90  .arg(DataFileReaderFactory::getKnownExtensions())
91  .arg(RDFImporter::getKnownExtensions());
92  break;
93 
94  case LayerFileNoMidi:
95  settingsKeyStub = "layer";
96  filter = tr("All supported files (%1 %2)\nSonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
97  .arg(DataFileReaderFactory::getKnownExtensions())
98  .arg(RDFImporter::getKnownExtensions());
99  break;
100 
101  case LayerFileNonSV:
102  settingsKeyStub = "layer";
103  filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)")
104  .arg(DataFileReaderFactory::getKnownExtensions())
105  .arg(RDFImporter::getKnownExtensions());
106  break;
107 
108  case LayerFileNoMidiNonSV:
109  settingsKeyStub = "layer";
110  filter = tr("All supported files (%1 %2)\nComma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nSpace-separated .lab files (*.lab)\nRDF files (%2)\nText files (*.txt)\nAll files (*.*)")
111  .arg(DataFileReaderFactory::getKnownExtensions())
112  .arg(RDFImporter::getKnownExtensions());
113  break;
114 
115  case SessionOrAudioFile:
116  settingsKeyStub = "last";
117  filter = tr("All supported files (*.sv %1 %2)\n%3 session files (*.%4)\nAudio files (%2)\nRDF files (%1)\nAll files (*.*)")
118  .arg(RDFImporter::getKnownExtensions())
119  .arg(AudioFileReaderFactory::getKnownExtensions())
120  .arg(QApplication::applicationName())
121  .arg(m_sessionExtension);
122  break;
123 
124  case ImageFile:
125  settingsKeyStub = "image";
126  {
127  QStringList fmts;
128  QList<QByteArray> formats = QImageReader::supportedImageFormats();
129  for (QList<QByteArray>::iterator i = formats.begin();
130  i != formats.end(); ++i) {
131  fmts.push_back(QString("*.%1")
132  .arg(QString::fromLocal8Bit(*i).toLower()));
133  }
134  filter = tr("Image files (%1)\nAll files (*.*)").arg(fmts.join(" "));
135  }
136  break;
137 
138  case CSVFile:
139  settingsKeyStub = "layer";
140  filter = tr("Comma-separated data files (*.csv)\nSpace-separated .lab files (*.lab)\nText files (*.txt)\nAll files (*.*)");
141  break;
142 
143  case AnyFile:
144  settingsKeyStub = "last";
145  filter = tr("All supported files (*.sv %1 %2 %3)\n%4 session files (*.%5)\nAudio files (%1)\nLayer files (%2)\nRDF files (%3)\nAll files (*.*)")
146  .arg(AudioFileReaderFactory::getKnownExtensions())
147  .arg(DataFileReaderFactory::getKnownExtensions())
148  .arg(RDFImporter::getKnownExtensions())
149  .arg(QApplication::applicationName())
150  .arg(m_sessionExtension);
151  break;
152  };
153 
154  if (lastPath == "") {
155  char *home = getenv("HOME");
156  if (home) lastPath = home;
157  else lastPath = ".";
158  } else if (QFileInfo(lastPath).isDir()) {
159  lastPath = QFileInfo(lastPath).canonicalPath();
160  } else {
161  lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
162  }
163 
164  QSettings settings;
165  settings.beginGroup("FileFinder");
166  lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
167 
168  QString path = "";
169 
170  // Use our own QFileDialog just for symmetry with getSaveFileName below
171 
172  QFileDialog dialog(m_parent);
173  dialog.setNameFilters(filter.split('\n'));
174  dialog.setWindowTitle(title);
175  dialog.setDirectory(lastPath);
176 
177  dialog.setAcceptMode(QFileDialog::AcceptOpen);
178  dialog.setFileMode(QFileDialog::ExistingFile);
179 
180  if (dialog.exec()) {
181  QStringList files = dialog.selectedFiles();
182  if (!files.empty()) path = *files.begin();
183 
184  QFileInfo fi(path);
185 
186  if (!fi.exists()) {
187 
188  QMessageBox::critical(0, tr("File does not exist"),
189  tr("<b>File not found</b><p>File \"%1\" does not exist").arg(path));
190  path = "";
191 
192  } else if (!fi.isReadable()) {
193 
194  QMessageBox::critical(0, tr("File is not readable"),
195  tr("<b>File is not readable</b><p>File \"%1\" can not be read").arg(path));
196  path = "";
197 
198  } else if (fi.isDir()) {
199 
200  QMessageBox::critical(0, tr("Directory selected"),
201  tr("<b>Directory selected</b><p>File \"%1\" is a directory").arg(path));
202  path = "";
203 
204  } else if (!fi.isFile()) {
205 
206  QMessageBox::critical(0, tr("Non-file selected"),
207  tr("<b>Not a file</b><p>Path \"%1\" is not a file").arg(path));
208  path = "";
209 
210  } else if (fi.size() == 0) {
211 
212  QMessageBox::critical(0, tr("File is empty"),
213  tr("<b>File is empty</b><p>File \"%1\" is empty").arg(path));
214  path = "";
215  }
216  }
217 
218  if (path != "") {
219  settings.setValue(settingsKeyStub + "path",
220  QFileInfo(path).absoluteDir().canonicalPath());
221  }
222 
223  return path;
224 }
225 
226 QString
228  QString fallbackLocation)
229 {
230  QString settingsKeyStub;
231  QString lastPath = fallbackLocation;
232 
233  QString title = tr("Select file");
234  QString filter = tr("All files (*.*)");
235 
236  switch (type) {
237 
238  case SessionFile:
239  settingsKeyStub = "savesession";
240  title = tr("Select a session file");
241  filter = tr("%1 session files (*.%2)\nAll files (*.*)")
242  .arg(QApplication::applicationName()).arg(m_sessionExtension);
243  break;
244 
245  case AudioFile:
246  settingsKeyStub = "saveaudio";
247  title = "Select an audio file";
248  title = tr("Select a file to export to");
249  filter = tr("WAV audio files (*.wav)\nAll files (*.*)");
250  break;
251 
252  case LayerFile:
253  settingsKeyStub = "savelayer";
254  title = tr("Select a file to export to");
255  filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
256  break;
257 
258  case LayerFileNoMidi:
259  settingsKeyStub = "savelayer";
260  title = tr("Select a file to export to");
261  filter = tr("Sonic Visualiser Layer XML files (*.svl)\nComma-separated data files (*.csv)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
262  break;
263 
264  case LayerFileNonSV:
265  settingsKeyStub = "savelayer";
266  title = tr("Select a file to export to");
267  filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nMIDI files (*.mid)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
268  break;
269 
270  case LayerFileNoMidiNonSV:
271  settingsKeyStub = "savelayer";
272  title = tr("Select a file to export to");
273  filter = tr("Comma-separated data files (*.csv)\nSonic Visualiser Layer XML files (*.svl)\nRDF/Turtle files (%1)\nText files (*.txt)\nAll files (*.*)").arg(RDFExporter::getSupportedExtensions());
274  break;
275 
276  case SessionOrAudioFile:
277  cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: SessionOrAudioFile cannot be used here" << endl;
278  abort();
279 
280  case ImageFile:
281  settingsKeyStub = "saveimage";
282  title = tr("Select a file to export to");
283  filter = tr("Portable Network Graphics files (*.png)\nAll files (*.*)");
284  break;
285 
286  case CSVFile:
287  settingsKeyStub = "savelayer";
288  title = tr("Select a file to export to");
289  filter = tr("Comma-separated data files (*.csv)\nText files (*.txt)\nAll files (*.*)");
290  break;
291 
292  case AnyFile:
293  cerr << "ERROR: Internal error: InteractiveFileFinder::getSaveFileName: AnyFile cannot be used here" << endl;
294  abort();
295  };
296 
297  if (lastPath == "") {
298  char *home = getenv("HOME");
299  if (home) lastPath = home;
300  else lastPath = ".";
301  } else if (QFileInfo(lastPath).isDir()) {
302  lastPath = QFileInfo(lastPath).canonicalPath();
303  } else {
304  lastPath = QFileInfo(lastPath).absoluteDir().canonicalPath();
305  }
306 
307  QSettings settings;
308  settings.beginGroup("FileFinder");
309  lastPath = settings.value(settingsKeyStub + "path", lastPath).toString();
310 
311  QString path = "";
312 
313  // Use our own QFileDialog instead of static functions, as we may
314  // need to adjust the file extension based on the selected filter
315 
316  QFileDialog dialog(m_parent);
317 
318  QStringList filters = filter.split('\n');
319 
320  dialog.setNameFilters(filters);
321  dialog.setWindowTitle(title);
322  dialog.setDirectory(lastPath);
323  dialog.setAcceptMode(QFileDialog::AcceptSave);
324  dialog.setFileMode(QFileDialog::AnyFile);
325  dialog.setConfirmOverwrite(false); // we'll do that
326 
327  QString defaultSuffix;
328  if (type == SessionFile) {
329  defaultSuffix = m_sessionExtension;
330  } else if (type == AudioFile) {
331  defaultSuffix = "wav";
332  } else if (type == ImageFile) {
333  defaultSuffix = "png";
334  } else if (type == CSVFile) {
335  defaultSuffix = "csv";
336  }
337 
338  defaultSuffix =
339  settings.value(settingsKeyStub + "suffix", defaultSuffix).toString();
340 
341  dialog.setDefaultSuffix(defaultSuffix);
342 
343  foreach (QString f, filters) {
344  if (f.contains("." + defaultSuffix)) {
345  dialog.selectNameFilter(f);
346  }
347  }
348 
349  bool good = false;
350 
351  while (!good) {
352 
353  path = "";
354 
355  if (!dialog.exec()) break;
356 
357  QStringList files = dialog.selectedFiles();
358  if (files.empty()) break;
359  path = *files.begin();
360 
361  QFileInfo fi(path);
362 
363  cerr << "type = " << type << ", suffix = " << fi.suffix() << endl;
364 
365  if ((type == LayerFile || type == LayerFileNoMidi ||
366  type == LayerFileNonSV || type == LayerFileNoMidiNonSV)
367  && fi.suffix() == "") {
368  QString expectedExtension;
369  QString selectedFilter = dialog.selectedNameFilter();
370  if (selectedFilter.contains(".svl")) {
371  expectedExtension = "svl";
372  } else if (selectedFilter.contains(".txt")) {
373  expectedExtension = "txt";
374  } else if (selectedFilter.contains(".csv")) {
375  expectedExtension = "csv";
376  } else if (selectedFilter.contains(".mid")) {
377  expectedExtension = "mid";
378  } else if (selectedFilter.contains(".ttl")) {
379  expectedExtension = "ttl";
380  }
381  cerr << "expected extension = " << expectedExtension << endl;
382  if (expectedExtension != "") {
383  path = QString("%1.%2").arg(path).arg(expectedExtension);
384  fi = QFileInfo(path);
385  }
386  }
387 
388  if (fi.isDir()) {
389  QMessageBox::critical(0, tr("Directory selected"),
390  tr("<b>Directory selected</b><p>File \"%1\" is a directory").arg(path));
391  continue;
392  }
393 
394  if (fi.exists()) {
395  if (QMessageBox::question(0, tr("File exists"),
396  tr("<b>File exists</b><p>The file \"%1\" already exists.\nDo you want to overwrite it?").arg(path),
397  QMessageBox::Ok,
398  QMessageBox::Cancel) != QMessageBox::Ok) {
399  continue;
400  }
401  }
402 
403  good = true;
404  }
405 
406  if (path != "") {
407  settings.setValue(settingsKeyStub + "path",
408  QFileInfo(path).absoluteDir().canonicalPath());
409  settings.setValue(settingsKeyStub + "suffix",
410  QFileInfo(path).suffix());
411  }
412 
413  return path;
414 }
415 
416 void
418 {
419  QString settingsKeyStub;
420 
421  switch (type) {
422  case SessionFile:
423  settingsKeyStub = "session";
424  break;
425 
426  case AudioFile:
427  settingsKeyStub = "audio";
428  break;
429 
430  case LayerFile:
431  settingsKeyStub = "layer";
432  break;
433 
434  case LayerFileNoMidi:
435  settingsKeyStub = "layer";
436  break;
437 
438  case LayerFileNonSV:
439  settingsKeyStub = "layer";
440  break;
441 
442  case LayerFileNoMidiNonSV:
443  settingsKeyStub = "layer";
444  break;
445 
446  case SessionOrAudioFile:
447  settingsKeyStub = "last";
448  break;
449 
450  case ImageFile:
451  settingsKeyStub = "image";
452  break;
453 
454  case CSVFile:
455  settingsKeyStub = "layer";
456  break;
457 
458  case AnyFile:
459  settingsKeyStub = "last";
460  break;
461  }
462 
463  if (path != "") {
464  QSettings settings;
465  settings.beginGroup("FileFinder");
466  path = QFileInfo(path).absoluteDir().canonicalPath();
467  QString suffix = QFileInfo(path).suffix();
468  settings.setValue(settingsKeyStub + "path", path);
469  settings.setValue(settingsKeyStub + "suffix", suffix);
470  settings.setValue("lastpath", path);
471  }
472 }
473 
474 QString
475 InteractiveFileFinder::find(FileType type, QString location, QString lastKnownLocation)
476 {
477  if (FileSource::canHandleScheme(location)) {
478  if (FileSource(location).isAvailable()) {
479  SVDEBUG << "InteractiveFileFinder::find: ok, it's available... returning" << endl;
480  return location;
481  }
482  }
483 
484  if (QFileInfo(location).exists()) return location;
485 
486  QString foundAt = "";
487 
488  if ((foundAt = findRelative(location, lastKnownLocation)) != "") {
489  return foundAt;
490  }
491 
492  if ((foundAt = findRelative(location, m_lastLocatedLocation)) != "") {
493  return foundAt;
494  }
495 
496  return locateInteractive(type, location);
497 }
498 
499 QString
500 InteractiveFileFinder::findRelative(QString location, QString relativeTo)
501 {
502  if (relativeTo == "") return "";
503 
504  SVDEBUG << "Looking for \"" << location << "\" next to \""
505  << relativeTo << "\"..." << endl;
506 
507  QString fileName;
508  QString resolved;
509 
510  if (FileSource::isRemote(location)) {
511  fileName = QUrl(location).path().section('/', -1, -1,
512  QString::SectionSkipEmpty);
513  } else {
514  if (QUrl(location).scheme() == "file") {
515  location = QUrl(location).toLocalFile();
516  }
517  fileName = QFileInfo(location).fileName();
518  }
519 
520  if (FileSource::isRemote(relativeTo)) {
521  resolved = QUrl(relativeTo).resolved(fileName).toString();
522  if (!FileSource(resolved).isAvailable()) resolved = "";
523  cerr << "resolved: " << resolved << endl;
524  } else {
525  if (QUrl(relativeTo).scheme() == "file") {
526  relativeTo = QUrl(relativeTo).toLocalFile();
527  }
528  resolved = QFileInfo(relativeTo).dir().filePath(fileName);
529  if (!QFileInfo(resolved).exists() ||
530  !QFileInfo(resolved).isFile() ||
531  !QFileInfo(resolved).isReadable()) {
532  resolved = "";
533  }
534  }
535 
536  return resolved;
537 }
538 
539 QString
540 InteractiveFileFinder::locateInteractive(FileType type, QString thing)
541 {
542  QString question;
543  if (type == AudioFile) {
544  question = tr("<b>File not found</b><p>Audio file \"%1\" could not be opened.\nDo you want to locate it?");
545  } else {
546  question = tr("<b>File not found</b><p>File \"%1\" could not be opened.\nDo you want to locate it?");
547  }
548 
549  QString path = "";
550  bool done = false;
551 
552  while (!done) {
553 
554  int rv = QMessageBox::question
555  (0,
556  tr("Failed to open file"),
557  question.arg(thing),
558  tr("Locate file..."),
559  tr("Use URL..."),
560  tr("Cancel"),
561  0, 2);
562 
563  switch (rv) {
564 
565  case 0: // Locate file
566 
567  if (QFileInfo(thing).dir().exists()) {
568  path = QFileInfo(thing).dir().canonicalPath();
569  }
570 
571  path = getOpenFileName(type, path);
572  done = (path != "");
573  break;
574 
575  case 1: // Use URL
576  {
577  bool ok = false;
578  path = QInputDialog::getText
579  (0, tr("Use URL"),
580  tr("Please enter the URL to use for this file:"),
581  QLineEdit::Normal, "", &ok);
582 
583  if (ok && path != "") {
584  if (FileSource(path).isAvailable()) {
585  done = true;
586  } else {
587  QMessageBox::critical
588  (0, tr("Failed to open location"),
589  tr("<b>Failed to open location</b><p>URL \"%1\" could not be opened").arg(path));
590  path = "";
591  }
592  }
593  break;
594  }
595 
596  case 2: // Cancel
597  path = "";
598  done = true;
599  break;
600  }
601  }
602 
603  if (path != "") m_lastLocatedLocation = path;
604  return path;
605 }
606 
607 
static void setParentWidget(QWidget *)
QString find(FileType type, QString location, QString lastKnownLocation="")
QString getOpenFileName(FileType type, QString fallbackLocation="")
QString locateInteractive(FileType type, QString thing)
void setApplicationSessionExtension(QString extension)
Specify the extension for this application's session files (without the dot)
void registerLastOpenedFilePath(FileType type, QString path)
QString getSaveFileName(FileType type, QString fallbackLocation="")
QString findRelative(QString location, QString relativeTo)
static InteractiveFileFinder m_instance
static InteractiveFileFinder * getInstance()