annotate widgets/InteractiveFileFinder.cpp @ 607:5b72899d692b

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