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