This allows paths that are prefixed with environment variables to behave as expected. This is useful when scanning the default LV2 locations in the AudioPluginHost on Windows.v7.0.9
@@ -472,9 +472,9 @@ private: | |||||
{ | { | ||||
for (int i = 0; i < pathList.getPath().getNumPaths(); ++i) | for (int i = 0; i < pathList.getPath().getNumPaths(); ++i) | ||||
{ | { | ||||
auto f = pathList.getPath()[i]; | |||||
auto f = pathList.getPath().getRawString (i); | |||||
if (isStupidPath (f)) | |||||
if (File::isAbsolutePath (f) && isStupidPath (File (f))) | |||||
{ | { | ||||
AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon, | AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon, | ||||
TRANS("Plugin Scanning"), | TRANS("Plugin Scanning"), | ||||
@@ -483,7 +483,7 @@ private: | |||||
"attempting to load unsuitable files.") | "attempting to load unsuitable files.") | ||||
+ newLine | + newLine | ||||
+ TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | ||||
.replace ("XYZ", f.getFullPathName()), | |||||
.replace ("XYZ", f), | |||||
TRANS ("Scan"), | TRANS ("Scan"), | ||||
String(), | String(), | ||||
nullptr, | nullptr, | ||||
@@ -63,7 +63,12 @@ int FileSearchPath::getNumPaths() const | |||||
File FileSearchPath::operator[] (int index) const | File FileSearchPath::operator[] (int index) const | ||||
{ | { | ||||
return File (directories[index]); | |||||
return File (getRawString (index)); | |||||
} | |||||
String FileSearchPath::getRawString (int index) const | |||||
{ | |||||
return directories[index]; | |||||
} | } | ||||
String FileSearchPath::toString() const | String FileSearchPath::toString() const | ||||
@@ -110,21 +115,30 @@ void FileSearchPath::addPath (const FileSearchPath& other) | |||||
void FileSearchPath::removeRedundantPaths() | void FileSearchPath::removeRedundantPaths() | ||||
{ | { | ||||
for (int i = directories.size(); --i >= 0;) | |||||
std::vector<String> reduced; | |||||
for (const auto& directory : directories) | |||||
{ | { | ||||
const File d1 (directories[i]); | |||||
const auto checkedIsChildOf = [&] (const auto& a, const auto& b) | |||||
{ | |||||
return File::isAbsolutePath (a) && File::isAbsolutePath (b) && File (a).isAChildOf (b); | |||||
}; | |||||
for (int j = directories.size(); --j >= 0;) | |||||
const auto fContainsDirectory = [&] (const auto& f) | |||||
{ | { | ||||
const File d2 (directories[j]); | |||||
return f == directory || checkedIsChildOf (directory, f); | |||||
}; | |||||
if (i != j && (d1.isAChildOf (d2) || d1 == d2)) | |||||
{ | |||||
directories.remove (i); | |||||
break; | |||||
} | |||||
} | |||||
if (std::find_if (reduced.begin(), reduced.end(), fContainsDirectory) != reduced.end()) | |||||
continue; | |||||
const auto directoryContainsF = [&] (const auto& f) { return checkedIsChildOf (f, directory); }; | |||||
reduced.erase (std::remove_if (reduced.begin(), reduced.end(), directoryContainsF), reduced.end()); | |||||
reduced.push_back (directory); | |||||
} | } | ||||
directories = StringArray (reduced.data(), (int) reduced.size()); | |||||
} | } | ||||
void FileSearchPath::removeNonExistentPaths() | void FileSearchPath::removeNonExistentPaths() | ||||
@@ -172,4 +186,54 @@ bool FileSearchPath::isFileInPath (const File& fileToCheck, | |||||
return false; | return false; | ||||
} | } | ||||
//============================================================================== | |||||
//============================================================================== | |||||
#if JUCE_UNIT_TESTS | |||||
class FileSearchPathTests : public UnitTest | |||||
{ | |||||
public: | |||||
FileSearchPathTests() : UnitTest ("FileSearchPath", UnitTestCategories::files) {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("removeRedundantPaths"); | |||||
{ | |||||
#if JUCE_WINDOWS | |||||
const String prefix = "C:"; | |||||
#else | |||||
const String prefix = ""; | |||||
#endif | |||||
{ | |||||
FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c/e;" + prefix + "/a/b/c" }; | |||||
fsp.removeRedundantPaths(); | |||||
expectEquals (fsp.toString(), prefix + "/a/b/c"); | |||||
} | |||||
{ | |||||
FileSearchPath fsp { prefix + "/a/b/c;" + prefix + "/a/b/c/d;" + prefix + "/a/b/c/e" }; | |||||
fsp.removeRedundantPaths(); | |||||
expectEquals (fsp.toString(), prefix + "/a/b/c"); | |||||
} | |||||
{ | |||||
FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c;" + prefix + "/a/b/c/e" }; | |||||
fsp.removeRedundantPaths(); | |||||
expectEquals (fsp.toString(), prefix + "/a/b/c"); | |||||
} | |||||
{ | |||||
FileSearchPath fsp { "%FOO%;" + prefix + "/a/b/c;%FOO%;" + prefix + "/a/b/c/d" }; | |||||
fsp.removeRedundantPaths(); | |||||
expectEquals (fsp.toString(), "%FOO%;" + prefix + "/a/b/c"); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
static FileSearchPathTests fileSearchPathTests; | |||||
#endif | |||||
} // namespace juce | } // namespace juce |
@@ -71,10 +71,21 @@ public: | |||||
/** Returns one of the folders in this search path. | /** Returns one of the folders in this search path. | ||||
The file returned isn't guaranteed to actually be a valid directory. | The file returned isn't guaranteed to actually be a valid directory. | ||||
@see getNumPaths | |||||
@see getNumPaths, getRawString | |||||
*/ | */ | ||||
File operator[] (int index) const; | File operator[] (int index) const; | ||||
/** Returns the unaltered text of the folder at the specified index. | |||||
Unlike operator[], this function returns the exact text that was entered. It does not | |||||
attempt to convert the path into an absolute path. | |||||
This may be useful if the directory string is expected to understand environment variables | |||||
or other placeholders that the File constructor doesn't necessarily understand. | |||||
@see operator[] | |||||
*/ | |||||
String getRawString (int index) const; | |||||
/** Returns the search path as a semicolon-separated list of directories. */ | /** Returns the search path as a semicolon-separated list of directories. */ | ||||
String toString() const; | String toString() const; | ||||
@@ -129,7 +129,7 @@ void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, | |||||
f.setHorizontalScale (0.9f); | f.setHorizontalScale (0.9f); | ||||
g.setFont (f); | g.setFont (f); | ||||
g.drawText (path[rowNumber].getFullPathName(), | |||||
g.drawText (path.getRawString (rowNumber), | |||||
4, 0, width - 6, height, | 4, 0, width - 6, height, | ||||
Justification::centredLeft, true); | Justification::centredLeft, true); | ||||
} | } | ||||
@@ -145,7 +145,7 @@ void FileSearchPathListComponent::deleteKeyPressed (int row) | |||||
void FileSearchPathListComponent::returnKeyPressed (int row) | void FileSearchPathListComponent::returnKeyPressed (int row) | ||||
{ | { | ||||
chooser = std::make_unique<FileChooser> (TRANS("Change folder..."), path[row], "*"); | |||||
chooser = std::make_unique<FileChooser> (TRANS("Change folder..."), path.getRawString (row), "*"); | |||||
auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; | auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; | ||||
chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc) | chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc) | ||||
@@ -258,7 +258,7 @@ void FileSearchPathListComponent::moveSelection (int delta) | |||||
if (currentRow != newRow) | if (currentRow != newRow) | ||||
{ | { | ||||
auto f = path[currentRow]; | |||||
const auto f = File::createFileWithoutCheckingPath (path.getRawString (currentRow)); | |||||
path.remove (currentRow); | path.remove (currentRow); | ||||
path.add (f, newRow); | path.add (f, newRow); | ||||
listBox.selectRow (newRow); | listBox.selectRow (newRow); | ||||