annotate widgets/FileFinder.cpp @ 456:035d62c4cddf

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