Mercurial > hg > svgui
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 |