diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 369386aa13..3670537dec 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -3911,6 +3911,27 @@ DynamicObject* var::getObject() const throw() return type == objectType ? value.objectValue : 0; } +bool var::operator== (const var& other) const throw() +{ + switch (type) + { + case voidType: return other.isVoid(); + case intType: return value.intValue == (int) other; + case boolType: return value.boolValue == (bool) other; + case doubleType: return value.doubleValue == (double) other; + case stringType: return (*(value.stringValue)) == other.toString(); + case objectType: return value.objectValue == other.getObject(); + default: jassertfalse; break; + } + + return false; +} + +bool var::operator!= (const var& other) const throw() +{ + return ! operator== (other); +} + const var var::operator[] (const var::identifier& propertyName) const throw() { if (type == objectType && value.objectValue != 0) @@ -15311,7 +15332,7 @@ const String XmlElement::getText() const throw() { jassert (isTextElement()); // you're trying to get the text from an element that // isn't actually a text element.. If this contains text sub-nodes, you - // can use getAllSubText instead to + // probably want to use getAllSubText instead. return getStringAttribute (juce_xmltextContentAttributeName); } @@ -45460,14 +45481,14 @@ namespace CppTokeniser static bool isIdentifierStart (const tchar c) throw() { return CharacterFunctions::isLetter (c) - || c == T('_'); + || c == T('_') || c == T('@'); } static bool isIdentifierBody (const tchar c) throw() { return CharacterFunctions::isLetter (c) || CharacterFunctions::isDigit (c) - || c == T('_'); + || c == T('_') || c == T('@'); } static int parseIdentifier (CodeDocument::Iterator& source) throw() @@ -45494,7 +45515,9 @@ static int parseIdentifier (CodeDocument::Iterator& source) throw() static const tchar* keywordsOther[] = { T("const_cast"), T("continue"), T("default"), T("explicit"), T("mutable"), T("namespace"), T("operator"), T("private"), T("protected"), T("register"), T("reinterpret_cast"), T("static_cast"), - T("template"), T("typedef"), T("typename"), T("unsigned"), T("virtual"), T("volatile"), 0 }; + T("template"), T("typedef"), T("typename"), T("unsigned"), T("virtual"), T("volatile"), + T("@implementation"), T("@interface"), T("@end"), T("@synthesize"), T("@dynamic"), T("@public"), + T("@private"), T("@property"), T("@protected"), T("@class"), 0 }; int tokenLength = 0; tchar possibleIdentifier [19]; @@ -56413,27 +56436,24 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE -class DirectoriesOnlyFilter : public FileFilter -{ -public: - DirectoriesOnlyFilter() : FileFilter (String::empty) {} - - bool isFileSuitable (const File&) const { return false; } - bool isDirectorySuitable (const File&) const { return true; } -}; - -FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, +FileBrowserComponent::FileBrowserComponent (int flags_, const File& initialFileOrDirectory, - const FileFilter* fileFilter, - FilePreviewComponent* previewComp_, - const bool useTreeView, - const bool filenameTextBoxIsReadOnly) - : directoriesOnlyFilter (0), - mode (mode_), + const FileFilter* fileFilter_, + FilePreviewComponent* previewComp_) + : FileFilter (String::empty), + fileFilter (fileFilter_), + flags (flags_), listeners (2), previewComp (previewComp_), thread ("Juce FileBrowser") { + // You need to specify one or other of the open/save flags.. + jassert ((flags & (saveMode | openMode)) != 0); + jassert ((flags & (saveMode | openMode)) != (saveMode | openMode)); + + // You need to specify at least one of these flags.. + jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0); + String filename; if (initialFileOrDirectory == File::nonexistent) @@ -56446,18 +56466,20 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, } else { + chosenFiles.add (new File (initialFileOrDirectory)); currentRoot = initialFileOrDirectory.getParentDirectory(); filename = initialFileOrDirectory.getFileName(); } - if (mode_ == chooseDirectoryMode) - fileFilter = directoriesOnlyFilter = new DirectoriesOnlyFilter(); + fileList = new DirectoryContentsList (this, thread); - fileList = new DirectoryContentsList (fileFilter, thread); - - if (useTreeView) + if ((flags & useTreeView) != 0) { FileTreeComponent* const tree = new FileTreeComponent (*fileList); + + if ((flags & canSelectMultipleItems) != 0) + tree->setMultiSelectEnabled (true); + addAndMakeVisible (tree); fileListComponent = tree; } @@ -56465,6 +56487,10 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, { FileListComponent* const list = new FileListComponent (*fileList); list->setOutlineThickness (1); + + if ((flags & canSelectMultipleItems) != 0) + list->setMultipleSelectionEnabled (true); + addAndMakeVisible (list); fileListComponent = list; } @@ -56492,11 +56518,11 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, filenameBox->setMultiLine (false); filenameBox->setSelectAllWhenFocused (true); filenameBox->setText (filename, false); - filenameBox->addListener (this); - filenameBox->setReadOnly (filenameTextBoxIsReadOnly); - Label* label = new Label ("f", (mode == chooseDirectoryMode) ? TRANS("folder:") - : TRANS("file:")); + filenameBox->addListener (this); + filenameBox->setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0); + + Label* label = new Label ("f", TRANS("file:")); addAndMakeVisible (label); label->attachToComponent (filenameBox, true); @@ -56519,10 +56545,7 @@ FileBrowserComponent::~FileBrowserComponent() removeChildComponent (previewComp); deleteAllChildren(); - deleteAndZero (fileList); - delete directoriesOnlyFilter; - thread.stopThread (10000); } @@ -56539,19 +56562,33 @@ void FileBrowserComponent::removeListener (FileBrowserListener* const listener) listeners.removeValue (listener); } -const File FileBrowserComponent::getCurrentFile() const throw() +bool FileBrowserComponent::isSaveMode() const throw() { - return currentRoot.getChildFile (filenameBox->getText()); + return (flags & saveMode) != 0; +} + +int FileBrowserComponent::getNumSelectedFiles() const throw() +{ + if (chosenFiles.size() == 0 && currentFileIsValid()) + return 1; + + return chosenFiles.size(); +} + +const File FileBrowserComponent::getSelectedFile (int index) const throw() +{ + if (! filenameBox->isReadOnly()) + return currentRoot.getChildFile (filenameBox->getText()); + else + return chosenFiles[index] != 0 ? *chosenFiles[index] : File::nonexistent; } bool FileBrowserComponent::currentFileIsValid() const { - if (mode == saveFileMode) - return ! getCurrentFile().isDirectory(); - else if (mode == loadFileMode) - return getCurrentFile().existsAsFile(); - else if (mode == chooseDirectoryMode) - return getCurrentFile().isDirectory(); + if (isSaveMode()) + return ! getSelectedFile (0).isDirectory(); + else + return getSelectedFile (0).exists(); jassertfalse return false; @@ -56559,7 +56596,27 @@ bool FileBrowserComponent::currentFileIsValid() const const File FileBrowserComponent::getHighlightedFile() const throw() { - return fileListComponent->getSelectedFile(); + return fileListComponent->getSelectedFile (0); +} + +bool FileBrowserComponent::isFileSuitable (const File& file) const +{ + return (flags & canSelectFiles) != 0 ? (fileFilter == 0 || fileFilter->isFileSuitable (file)) + : false; +} + +bool FileBrowserComponent::isDirectorySuitable (const File&) const +{ + return true; +} + +bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const +{ + if (f.isDirectory()) + return (flags & canSelectDirectories) != 0 && (fileFilter == 0 || fileFilter->isDirectorySuitable (f)); + + return (flags & canSelectFiles) != 0 && f.exists() + && (fileFilter == 0 || fileFilter->isFileSuitable (f)); } const File FileBrowserComponent::getRoot() const @@ -56573,9 +56630,6 @@ void FileBrowserComponent::setRoot (const File& newRootDirectory) { fileListComponent->scrollToTop(); - if (mode == chooseDirectoryMode) - filenameBox->setText (String::empty, false); - String path (newRootDirectory.getFullPathName()); if (path.isEmpty()) @@ -56627,8 +56681,7 @@ void FileBrowserComponent::refresh() const String FileBrowserComponent::getActionVerb() const { - return (mode == chooseDirectoryMode) ? TRANS("Choose") - : ((mode == saveFileMode) ? TRANS("Save") : TRANS("Open")); + return isSaveMode() ? TRANS("Save") : TRANS("Open"); } FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const throw() @@ -56649,7 +56702,7 @@ void FileBrowserComponent::sendListenerChangeMessage() ComponentDeletionWatcher deletionWatcher (this); if (previewComp != 0) - previewComp->selectedFileChanged (getCurrentFile()); + previewComp->selectedFileChanged (getSelectedFile (0)); jassert (! deletionWatcher.hasBeenDeleted()); @@ -56666,14 +56719,29 @@ void FileBrowserComponent::sendListenerChangeMessage() void FileBrowserComponent::selectionChanged() { - const File selected (fileListComponent->getSelectedFile()); + StringArray newFilenames; + bool resetChosenFiles = true; - if ((mode == chooseDirectoryMode && selected.isDirectory()) - || selected.existsAsFile()) + for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i) { - filenameBox->setText (selected.getRelativePathFrom (getRoot()), false); + const File f (fileListComponent->getSelectedFile (i)); + + if (isFileOrDirSuitable (f)) + { + if (resetChosenFiles) + { + chosenFiles.clear(); + resetChosenFiles = false; + } + + chosenFiles.add (new File (f)); + newFilenames.add (f.getRelativePathFrom (getRoot())); + } } + if (newFilenames.size() > 0) + filenameBox->setText (newFilenames.joinIntoString (T(", ")), false); + sendListenerChangeMessage(); } @@ -56743,17 +56811,20 @@ void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&) if (f.isDirectory()) { setRoot (f); + chosenFiles.clear(); filenameBox->setText (String::empty); } else { setRoot (f.getParentDirectory()); + chosenFiles.clear(); + chosenFiles.add (new File (f)); filenameBox->setText (f.getFileName()); } } else { - fileDoubleClicked (getCurrentFile()); + fileDoubleClicked (getSelectedFile (0)); } } @@ -56763,7 +56834,7 @@ void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&) void FileBrowserComponent::textEditorFocusLost (TextEditor&) { - if (mode != saveFileMode) + if (! isSaveMode()) selectionChanged(); } @@ -56917,22 +56988,27 @@ FileChooser::~FileChooser() bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComponent) { - return showDialog (false, false, false, false, previewComponent); + return showDialog (false, true, false, false, false, previewComponent); } bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComponent) { - return showDialog (false, false, false, true, previewComponent); + return showDialog (false, true, false, false, true, previewComponent); +} + +bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent) +{ + return showDialog (true, true, false, false, true, previewComponent); } bool FileChooser::browseForFileToSave (const bool warnAboutOverwritingExistingFiles) { - return showDialog (false, true, warnAboutOverwritingExistingFiles, false, 0); + return showDialog (false, true, true, warnAboutOverwritingExistingFiles, false, 0); } bool FileChooser::browseForDirectory() { - return showDialog (true, false, false, false, 0); + return showDialog (true, false, false, false, false, 0); } const File FileChooser::getResult() const @@ -56954,7 +57030,8 @@ const OwnedArray & FileChooser::getResults() const return results; } -bool FileChooser::showDialog (const bool isDirectory, +bool FileChooser::showDialog (const bool selectsDirectories, + const bool selectsFiles, const bool isSave, const bool warnAboutOverwritingExistingFiles, const bool selectMultipleFiles, @@ -56973,27 +57050,38 @@ bool FileChooser::showDialog (const bool isDirectory, && previewComponent->getHeight() > 10)); #if JUCE_WINDOWS - if (useNativeDialogBox) -#else + if (useNativeDialogBox && ! (selectsFiles && selectsDirectories)) +#elif JUCE_MAC if (useNativeDialogBox && (previewComponent == 0)) +#else + if (false) #endif { showPlatformDialog (results, title, startingFile, filters, - isDirectory, isSave, + selectsDirectories, selectsFiles, isSave, warnAboutOverwritingExistingFiles, selectMultipleFiles, previewComponent); } else { - jassert (! selectMultipleFiles); // not yet implemented for juce dialogs! + WildcardFileFilter wildcard (selectsFiles ? filters : String::empty, + selectsDirectories ? "*" : String::empty, + String::empty); - WildcardFileFilter wildcard (filters, String::empty); + int flags = isSave ? FileBrowserComponent::saveMode + : FileBrowserComponent::openMode; - FileBrowserComponent browserComponent (isDirectory ? FileBrowserComponent::chooseDirectoryMode - : (isSave ? FileBrowserComponent::saveFileMode - : FileBrowserComponent::loadFileMode), - startingFile, &wildcard, previewComponent); + if (selectsFiles) + flags |= FileBrowserComponent::canSelectFiles; + + if (selectsDirectories) + flags |= FileBrowserComponent::canSelectDirectories; + + if (selectMultipleFiles) + flags |= FileBrowserComponent::canSelectMultipleItems; + + FileBrowserComponent browserComponent (flags, startingFile, &wildcard, previewComponent); FileChooserDialogBox box (title, String::empty, browserComponent, @@ -57001,7 +57089,10 @@ bool FileChooser::showDialog (const bool isDirectory, browserComponent.findColour (AlertWindow::backgroundColourId)); if (box.show()) - results.add (new File (browserComponent.getCurrentFile())); + { + for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i) + results.add (new File (browserComponent.getSelectedFile (i))); + } } if (currentlyFocused != 0 && ! currentlyFocusedChecker->hasBeenDeleted()) @@ -57092,13 +57183,13 @@ void FileChooserDialogBox::buttonClicked (Button* button) if (button == content->okButton) { if (warnAboutOverwritingExistingFiles - && content->chooserComponent->getMode() == FileBrowserComponent::saveFileMode - && content->chooserComponent->getCurrentFile().exists()) + && content->chooserComponent->isSaveMode() + && content->chooserComponent->getSelectedFile(0).exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:\n\n") - + content->chooserComponent->getCurrentFile().getFullPathName() + + content->chooserComponent->getSelectedFile(0).getFullPathName() + T("\n\nAre you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) @@ -57214,9 +57305,14 @@ FileListComponent::~FileListComponent() deleteAllChildren(); } -const File FileListComponent::getSelectedFile() const +int FileListComponent::getNumSelectedFiles() const { - return fileList.getFile (getSelectedRow()); + return getNumSelectedRows(); +} + +const File FileListComponent::getSelectedFile (int index) const +{ + return fileList.getFile (getSelectedRow (index)); } void FileListComponent::scrollToTop() @@ -58107,12 +58203,7 @@ FileTreeComponent::~FileTreeComponent() delete root; } -const File FileTreeComponent::getSelectedFile() const -{ - return getSelectedFile (0); -} - -const File FileTreeComponent::getSelectedFile (const int index) const throw() +const File FileTreeComponent::getSelectedFile (const int index) const { const FileListTreeItem* const item = dynamic_cast (getSelectedItem (index)); @@ -58245,21 +58336,14 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE -WildcardFileFilter::WildcardFileFilter (const String& wildcardPatterns, +WildcardFileFilter::WildcardFileFilter (const String& fileWildcardPatterns, + const String& directoryWildcardPatterns, const String& description) - : FileFilter (description.isEmpty() ? wildcardPatterns - : (description + T(" (") + wildcardPatterns + T(")"))) + : FileFilter (description.isEmpty() ? fileWildcardPatterns + : (description + T(" (") + fileWildcardPatterns + T(")"))) { - wildcards.addTokens (wildcardPatterns.toLowerCase(), T(";,"), T("\"'")); - - wildcards.trim(); - wildcards.removeEmptyStrings(); - - // special case for *.*, because people use it to mean "any file", but it - // would actually ignore files with no extension. - for (int i = wildcards.size(); --i >= 0;) - if (wildcards[i] == T("*.*")) - wildcards.set (i, T("*")); + parse (fileWildcardPatterns, fileWildcards); + parse (directoryWildcardPatterns, directoryWildcards); } WildcardFileFilter::~WildcardFileFilter() @@ -58267,6 +58351,30 @@ WildcardFileFilter::~WildcardFileFilter() } bool WildcardFileFilter::isFileSuitable (const File& file) const +{ + return match (file, fileWildcards); +} + +bool WildcardFileFilter::isDirectorySuitable (const File& file) const +{ + return match (file, directoryWildcards); +} + +void WildcardFileFilter::parse (const String& pattern, StringArray& result) throw() +{ + result.addTokens (pattern.toLowerCase(), T(";,"), T("\"'")); + + result.trim(); + result.removeEmptyStrings(); + + // special case for *.*, because people use it to mean "any file", but it + // would actually ignore files with no extension. + for (int i = result.size(); --i >= 0;) + if (result[i] == T("*.*")) + result.set (i, T("*")); +} + +bool WildcardFileFilter::match (const File& file, const StringArray& wildcards) throw() { const String filename (file.getFileName()); @@ -58277,11 +58385,6 @@ bool WildcardFileFilter::isFileSuitable (const File& file) const return false; } -bool WildcardFileFilter::isDirectorySuitable (const File&) const -{ - return true; -} - END_JUCE_NAMESPACE /********* End of inlined file: juce_WildcardFileFilter.cpp *********/ @@ -234860,6 +234963,14 @@ bool juce_launchFile (const String& fileName, return hInstance > (HINSTANCE) 32; } +void File::revealToUser() const throw() +{ + if (isDirectory()) + startAsProcess(); + else if (getParentDirectory().exists()) + getParentDirectory().startAsProcess(); +} + struct NamedPipeInternal { HANDLE pipeH; @@ -239483,6 +239594,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, const File& currentFileOrDirectory, const String& filter, bool selectsDirectory, + bool selectsFiles, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, @@ -250774,10 +250886,11 @@ bool juce_launchFile (const String& fileName, cmdString << " " << parameters; if (URL::isProbablyAWebsiteURL (fileName) + || cmdString.startsWithIgnoreCase (T("file:")) || URL::isProbablyAnEmailAddress (fileName)) { // create a command that tries to launch a bunch of likely browsers - const char* const browserNames[] = { "/etc/alternatives/x-www-browser", "firefox", "mozilla", "konqueror", "opera" }; + const char* const browserNames[] = { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla", "konqueror", "opera" }; StringArray cmdLines; @@ -250787,9 +250900,6 @@ bool juce_launchFile (const String& fileName, cmdString = cmdLines.joinIntoString (T(" || ")); } - if (cmdString.startsWithIgnoreCase (T("file:"))) - cmdString = cmdString.substring (5); - const char* const argv[4] = { "/bin/sh", "-c", (const char*) cmdString.toUTF8(), 0 }; const int cpid = fork(); @@ -250806,6 +250916,14 @@ bool juce_launchFile (const String& fileName, return cpid >= 0; } +void File::revealToUser() const throw() +{ + if (isDirectory()) + startAsProcess(); + else if (getParentDirectory().exists()) + getParentDirectory().startAsProcess(); +} + #endif /********* End of inlined file: juce_linux_Files.cpp *********/ @@ -258067,7 +258185,7 @@ MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { re /********* Start of inlined file: juce_linux_AudioCDReader.cpp *********/ // (This file gets included by juce_linux_NativeCode.cpp, rather than being // compiled on its own). -#if JUCE_INCLUDED_FILE +#if JUCE_INCLUDED_FILE && JUCE_USE_CDREADER AudioCDReader::AudioCDReader() : AudioFormatReader (0, T("CD Audio")) @@ -258151,6 +258269,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, const File& file, const String& filters, bool isDirectory, + bool selectsFiles, bool isSave, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, @@ -260343,6 +260462,16 @@ bool juce_launchFile (const String& fileName, #endif } +void File::revealToUser() const throw() +{ +#if ! JUCE_IPHONE + if (exists()) + [[NSWorkspace sharedWorkspace] selectFile: juceStringToNS (getFullPathName()) inFileViewerRootedAtPath: @""]; + else if (getParentDirectory().exists()) + getParentDirectory().revealToUser(); +#endif +} + #if ! JUCE_IPHONE bool PlatformUtilities::makeFSRefFromPath (FSRef* destFSRef, const String& path) { @@ -261505,18 +261634,27 @@ public: bool clipToRectangleList (const RectangleList& clipRegion) { - const int numRects = clipRegion.getNumRectangles(); - CGRect* const rects = new CGRect [numRects]; - for (int i = 0; i < numRects; ++i) + if (clipRegion.isEmpty()) { - const Rectangle& r = clipRegion.getRectangle(i); - rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); + CGContextClipToRect (context, CGRectMake (0, 0, 0, 0)); + return false; } + else + { + const int numRects = clipRegion.getNumRectangles(); - CGContextClipToRects (context, rects, numRects); - delete[] rects; + CGRect* const rects = new CGRect [numRects]; + for (int i = 0; i < numRects; ++i) + { + const Rectangle& r = clipRegion.getRectangle(i); + rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); + } - return ! isClipEmpty(); + CGContextClipToRects (context, rects, numRects); + delete[] rects; + + return ! isClipEmpty(); + } } void excludeClipRectangle (const Rectangle& r) @@ -263211,6 +263349,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, const File& currentFileOrDirectory, const String& filter, bool selectsDirectory, + bool selectsFiles, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, @@ -263235,7 +263374,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, { NSOpenPanel* openPanel = (NSOpenPanel*) panel; [openPanel setCanChooseDirectories: selectsDirectory]; - [openPanel setCanChooseFiles: ! selectsDirectory]; + [openPanel setCanChooseFiles: selectsFiles]; [openPanel setAllowsMultipleSelection: selectMultipleFiles]; } @@ -263283,6 +263422,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, const File& currentFileOrDirectory, const String& filter, bool selectsDirectory, + bool selectsFiles, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, @@ -265962,18 +266102,27 @@ public: bool clipToRectangleList (const RectangleList& clipRegion) { - const int numRects = clipRegion.getNumRectangles(); - CGRect* const rects = new CGRect [numRects]; - for (int i = 0; i < numRects; ++i) + if (clipRegion.isEmpty()) { - const Rectangle& r = clipRegion.getRectangle(i); - rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); + CGContextClipToRect (context, CGRectMake (0, 0, 0, 0)); + return false; } + else + { + const int numRects = clipRegion.getNumRectangles(); - CGContextClipToRects (context, rects, numRects); - delete[] rects; + CGRect* const rects = new CGRect [numRects]; + for (int i = 0; i < numRects; ++i) + { + const Rectangle& r = clipRegion.getRectangle(i); + rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); + } - return ! isClipEmpty(); + CGContextClipToRects (context, rects, numRects); + delete[] rects; + + return ! isClipEmpty(); + } } void excludeClipRectangle (const Rectangle& r) @@ -269433,6 +269582,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, const File& currentFileOrDirectory, const String& filter, bool selectsDirectory, + bool selectsFiles, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, @@ -269457,7 +269607,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, { NSOpenPanel* openPanel = (NSOpenPanel*) panel; [openPanel setCanChooseDirectories: selectsDirectory]; - [openPanel setCanChooseFiles: ! selectsDirectory]; + [openPanel setCanChooseFiles: selectsFiles]; [openPanel setAllowsMultipleSelection: selectMultipleFiles]; } @@ -269505,6 +269655,7 @@ void FileChooser::showPlatformDialog (OwnedArray& results, const File& currentFileOrDirectory, const String& filter, bool selectsDirectory, + bool selectsFiles, bool isSaveDialogue, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, diff --git a/juce_amalgamated.h b/juce_amalgamated.h index d442fea595..2f56d4a3db 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -6801,9 +6801,16 @@ public: default viewer application. - if it's a folder, it will be opened in Explorer, Finder, or equivalent. + + @see revealToUser */ bool startAsProcess (const String& parameters = String::empty) const throw(); + /** Opens Finder, Explorer, or whatever the OS uses, to show the user this file's location. + @see startAsProcess + */ + void revealToUser() const throw(); + /** A set of types of location that can be passed to the getSpecialLocation() method. */ enum SpecialLocationType @@ -11983,6 +11990,9 @@ public: bool isObject() const throw() { return type == objectType; } bool isMethod() const throw() { return type == methodType; } + bool operator== (const var& other) const throw(); + bool operator!= (const var& other) const throw(); + class JUCE_API identifier { public: @@ -50715,11 +50725,16 @@ public: /** Destructor. */ virtual ~DirectoryContentsDisplayComponent(); - /** Returns the file that the user has currently selected. - - Returns File::nonexistent if none is selected. + /** Returns the number of files the user has got selected. + @see getSelectedFile */ - virtual const File getSelectedFile() const = 0; + virtual int getNumSelectedFiles() const = 0; + + /** Returns one of the files that the user has currently selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles + */ + virtual const File getSelectedFile (int index) const = 0; /** Scrolls this view to the top. */ virtual void scrollToTop() = 0; @@ -50838,22 +50853,26 @@ class JUCE_API FileBrowserComponent : public Component, private FileBrowserListener, private TextEditorListener, private ButtonListener, - private ComboBoxListener + private ComboBoxListener, + private FileFilter { public: - /** Various modes that the browser can be used in. + /** Various options for the browser. - One of these is passed into the constructor. + A combination of these is passed into the FileBrowserComponent constructor. */ - enum FileChooserMode + enum FileChooserFlags { - loadFileMode, /**< the component should allow the user to choose an existing - file with the intention of opening it. */ - saveFileMode, /**< the component should allow the user to specify the name of - a file that will be used to save something. */ - chooseDirectoryMode /**< the component should allow the user to select an existing - directory. */ + openMode = 1, /**< the component should allow the user to choose an existing + file with the intention of opening it. */ + saveMode = 2, /**< the component should allow the user to specify the name of + a file that will be used to save something. */ + canSelectFiles = 4, /**< */ + canSelectDirectories = 8, /**< */ + canSelectMultipleItems = 16, /**< */ + useTreeView = 32, /**< */ + filenameBoxIsReadOnly = 64 /**< */ }; /** Creates a FileBrowserComponent. @@ -50875,33 +50894,40 @@ public: @param filenameTextBoxIsReadOnly if true, the user won't be allowed to type their own text into the filename box. */ - FileBrowserComponent (FileChooserMode browserMode, + FileBrowserComponent (int flags, const File& initialFileOrDirectory, const FileFilter* fileFilter, - FilePreviewComponent* previewComp, - const bool useTreeView = false, - const bool filenameTextBoxIsReadOnly = false); + FilePreviewComponent* previewComp); /** Destructor. */ ~FileBrowserComponent(); - /** Returns the file that the user has currently chosen. + /** Returns the number of files that the user has got selected. + If multiple select isn't active, this will only be 0 or 1. To get the complete + list of files they've chosen, pass an index to getCurrentFile(). + */ + int getNumSelectedFiles() const throw(); + + /** Returns one of the files that the user has chosen. + If the box has multi-select enabled, the index lets you specify which of the files + to get - see getNumSelectedFiles() to find out how many files were chosen. @see getHighlightedFile */ - const File getCurrentFile() const throw(); + const File getSelectedFile (int index) const throw(); - /** Returns true if the current file is usable. + /** Returns true if the currently selected file(s) are usable. This can be used to decide whether the user can press "ok" for the current file. What it does depends on the mode, so for example in an "open" - mode, the current file is only valid if one has been selected and if the file - exists. In a "save" mode, a non-existent file would also be valid. + mode, this only returns true if a file has been selected and if it exists. + In a "save" mode, a non-existent file would also be valid. */ bool currentFileIsValid() const; - /** This returns the item in the view that is currently highlighted. + /** This returns the last item in the view that the user has highlighted. This may be different from getCurrentFile(), which returns the value - that is shown in the filename box. + that is shown in the filename box, and if there are multiple selections, + this will only return one of them. @see getCurrentFile */ const File getHighlightedFile() const throw(); @@ -50918,9 +50944,6 @@ public: /** Refreshes the directory that's currently being listed. */ void refresh(); - /** Returns the browser's current mode. */ - FileChooserMode getMode() const throw() { return mode; } - /** Returns a verb to describe what should happen when the file is accepted. E.g. if browsing in "load file" mode, this will be "Open", if in "save file" @@ -50928,6 +50951,10 @@ public: */ virtual const String getActionVerb() const; + /** Returns true if the saveMode flag was set when this component was created. + */ + bool isSaveMode() const throw(); + /** Adds a listener to be told when the user selects and clicks on files. @see removeListener @@ -50962,6 +50989,10 @@ public: void fileClicked (const File& f, const MouseEvent& e); /** @internal */ void fileDoubleClicked (const File& f); + /** @internal */ + bool isFileSuitable (const File& file) const; + /** @internal */ + bool isDirectorySuitable (const File&) const; /** @internal */ FilePreviewComponent* getPreviewComponent() const throw(); @@ -50974,10 +51005,11 @@ protected: private: DirectoryContentsList* fileList; - FileFilter* directoriesOnlyFilter; + const FileFilter* fileFilter; - FileChooserMode mode; + int flags; File currentRoot; + OwnedArray chosenFiles; SortedSet listeners; DirectoryContentsDisplayComponent* fileListComponent; @@ -50989,6 +51021,7 @@ private: TimeSliceThread thread; void sendListenerChangeMessage(); + bool isFileOrDirSuitable (const File& f) const; FileBrowserComponent (const FileBrowserComponent&); const FileBrowserComponent& operator= (const FileBrowserComponent&); @@ -51112,6 +51145,13 @@ public: */ bool browseForDirectory(); + /** Same as browseForFileToOpen, but allows the user to select multiple files and directories. + + The files that are returned can be obtained by calling getResults(). See + browseForFileToOpen() for more info about the behaviour of this method. + */ + bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = 0); + /** Returns the last file that was chosen by one of the browseFor methods. After calling the appropriate browseFor... method, this method lets you @@ -51145,7 +51185,8 @@ private: OwnedArray results; bool useNativeDialogBox; - bool showDialog (const bool isDirectory, + bool showDialog (const bool selectsDirectories, + const bool selectsFiles, const bool isSave, const bool warnAboutOverwritingExistingFiles, const bool selectMultipleFiles, @@ -51155,7 +51196,8 @@ private: const String& title, const File& file, const String& filters, - bool isDirectory, + bool selectsDirectories, + bool selectsFiles, bool isSave, bool warnAboutOverwritingExistingFiles, bool selectMultipleFiles, @@ -51320,11 +51362,16 @@ public: /** Destructor. */ ~FileListComponent(); - /** Returns the file that the user has currently selected. - - Returns File::nonexistent if none is selected. + /** Returns the number of files the user has got selected. + @see getSelectedFile */ - const File getSelectedFile() const; + int getNumSelectedFiles() const; + + /** Returns one of the files that the user has currently selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles + */ + const File getSelectedFile (int index = 0) const; /** Scrolls to the top of the list. */ void scrollToTop(); @@ -51389,21 +51436,16 @@ public: /** Destructor. */ ~FileTreeComponent(); - /** Returns the number of selected files in the tree. + /** Returns the number of files the user has got selected. + @see getSelectedFile */ - int getNumSelectedFiles() const throw() { return TreeView::getNumSelectedItems(); } + int getNumSelectedFiles() const { return TreeView::getNumSelectedItems(); } /** Returns one of the files that the user has currently selected. - - Returns File::nonexistent if none is selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles */ - const File getSelectedFile (int index) const throw(); - - /** Returns the first of the files that the user has currently selected. - - Returns File::nonexistent if none is selected. - */ - const File getSelectedFile() const; + const File getSelectedFile (int index = 0) const; /** Scrolls the list to the top. */ void scrollToTop(); @@ -51752,7 +51794,8 @@ public: The description is a name to show the user in a list of possible patterns, so for the wav/aiff example, your description might be "audio files". */ - WildcardFileFilter (const String& wildcardPatterns, + WildcardFileFilter (const String& fileWildcardPatterns, + const String& directoryWildcardPatterns, const String& description); /** Destructor. */ @@ -51767,7 +51810,10 @@ public: juce_UseDebuggingNewOperator private: - StringArray wildcards; + StringArray fileWildcards, directoryWildcards; + + static void parse (const String& pattern, StringArray& result) throw(); + static bool match (const File& file, const StringArray& wildcards) throw(); }; #endif // __JUCE_WILDCARDFILEFILTER_JUCEHEADER__ diff --git a/src/gui/components/filebrowser/juce_DirectoryContentsDisplayComponent.h b/src/gui/components/filebrowser/juce_DirectoryContentsDisplayComponent.h index d04d9a2c30..043aa18298 100644 --- a/src/gui/components/filebrowser/juce_DirectoryContentsDisplayComponent.h +++ b/src/gui/components/filebrowser/juce_DirectoryContentsDisplayComponent.h @@ -48,11 +48,16 @@ public: virtual ~DirectoryContentsDisplayComponent(); //============================================================================== - /** Returns the file that the user has currently selected. - - Returns File::nonexistent if none is selected. + /** Returns the number of files the user has got selected. + @see getSelectedFile */ - virtual const File getSelectedFile() const = 0; + virtual int getNumSelectedFiles() const = 0; + + /** Returns one of the files that the user has currently selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles + */ + virtual const File getSelectedFile (int index) const = 0; /** Scrolls this view to the top. */ virtual void scrollToTop() = 0; diff --git a/src/gui/components/filebrowser/juce_FileBrowserComponent.cpp b/src/gui/components/filebrowser/juce_FileBrowserComponent.cpp index 51a32cd11c..763fd109c1 100644 --- a/src/gui/components/filebrowser/juce_FileBrowserComponent.cpp +++ b/src/gui/components/filebrowser/juce_FileBrowserComponent.cpp @@ -37,29 +37,24 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -class DirectoriesOnlyFilter : public FileFilter -{ -public: - DirectoriesOnlyFilter() : FileFilter (String::empty) {} - - bool isFileSuitable (const File&) const { return false; } - bool isDirectorySuitable (const File&) const { return true; } -}; - - -//============================================================================== -FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, +FileBrowserComponent::FileBrowserComponent (int flags_, const File& initialFileOrDirectory, - const FileFilter* fileFilter, - FilePreviewComponent* previewComp_, - const bool useTreeView, - const bool filenameTextBoxIsReadOnly) - : directoriesOnlyFilter (0), - mode (mode_), + const FileFilter* fileFilter_, + FilePreviewComponent* previewComp_) + : FileFilter (String::empty), + fileFilter (fileFilter_), + flags (flags_), listeners (2), previewComp (previewComp_), thread ("Juce FileBrowser") { + // You need to specify one or other of the open/save flags.. + jassert ((flags & (saveMode | openMode)) != 0); + jassert ((flags & (saveMode | openMode)) != (saveMode | openMode)); + + // You need to specify at least one of these flags.. + jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0); + String filename; if (initialFileOrDirectory == File::nonexistent) @@ -72,18 +67,20 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, } else { + chosenFiles.add (new File (initialFileOrDirectory)); currentRoot = initialFileOrDirectory.getParentDirectory(); filename = initialFileOrDirectory.getFileName(); } - if (mode_ == chooseDirectoryMode) - fileFilter = directoriesOnlyFilter = new DirectoriesOnlyFilter(); + fileList = new DirectoryContentsList (this, thread); - fileList = new DirectoryContentsList (fileFilter, thread); - - if (useTreeView) + if ((flags & useTreeView) != 0) { FileTreeComponent* const tree = new FileTreeComponent (*fileList); + + if ((flags & canSelectMultipleItems) != 0) + tree->setMultiSelectEnabled (true); + addAndMakeVisible (tree); fileListComponent = tree; } @@ -91,6 +88,10 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, { FileListComponent* const list = new FileListComponent (*fileList); list->setOutlineThickness (1); + + if ((flags & canSelectMultipleItems) != 0) + list->setMultipleSelectionEnabled (true); + addAndMakeVisible (list); fileListComponent = list; } @@ -118,11 +119,11 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, filenameBox->setMultiLine (false); filenameBox->setSelectAllWhenFocused (true); filenameBox->setText (filename, false); - filenameBox->addListener (this); - filenameBox->setReadOnly (filenameTextBoxIsReadOnly); - Label* label = new Label ("f", (mode == chooseDirectoryMode) ? TRANS("folder:") - : TRANS("file:")); + filenameBox->addListener (this); + filenameBox->setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0); + + Label* label = new Label ("f", TRANS("file:")); addAndMakeVisible (label); label->attachToComponent (filenameBox, true); @@ -145,10 +146,7 @@ FileBrowserComponent::~FileBrowserComponent() removeChildComponent (previewComp); deleteAllChildren(); - deleteAndZero (fileList); - delete directoriesOnlyFilter; - thread.stopThread (10000); } @@ -167,19 +165,33 @@ void FileBrowserComponent::removeListener (FileBrowserListener* const listener) } //============================================================================== -const File FileBrowserComponent::getCurrentFile() const throw() +bool FileBrowserComponent::isSaveMode() const throw() { - return currentRoot.getChildFile (filenameBox->getText()); + return (flags & saveMode) != 0; +} + +int FileBrowserComponent::getNumSelectedFiles() const throw() +{ + if (chosenFiles.size() == 0 && currentFileIsValid()) + return 1; + + return chosenFiles.size(); +} + +const File FileBrowserComponent::getSelectedFile (int index) const throw() +{ + if (! filenameBox->isReadOnly()) + return currentRoot.getChildFile (filenameBox->getText()); + else + return chosenFiles[index] != 0 ? *chosenFiles[index] : File::nonexistent; } bool FileBrowserComponent::currentFileIsValid() const { - if (mode == saveFileMode) - return ! getCurrentFile().isDirectory(); - else if (mode == loadFileMode) - return getCurrentFile().existsAsFile(); - else if (mode == chooseDirectoryMode) - return getCurrentFile().isDirectory(); + if (isSaveMode()) + return ! getSelectedFile (0).isDirectory(); + else + return getSelectedFile (0).exists(); jassertfalse return false; @@ -187,7 +199,28 @@ bool FileBrowserComponent::currentFileIsValid() const const File FileBrowserComponent::getHighlightedFile() const throw() { - return fileListComponent->getSelectedFile(); + return fileListComponent->getSelectedFile (0); +} + +//============================================================================== +bool FileBrowserComponent::isFileSuitable (const File& file) const +{ + return (flags & canSelectFiles) != 0 ? (fileFilter == 0 || fileFilter->isFileSuitable (file)) + : false; +} + +bool FileBrowserComponent::isDirectorySuitable (const File&) const +{ + return true; +} + +bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const +{ + if (f.isDirectory()) + return (flags & canSelectDirectories) != 0 && (fileFilter == 0 || fileFilter->isDirectorySuitable (f)); + + return (flags & canSelectFiles) != 0 && f.exists() + && (fileFilter == 0 || fileFilter->isFileSuitable (f)); } //============================================================================== @@ -202,9 +235,6 @@ void FileBrowserComponent::setRoot (const File& newRootDirectory) { fileListComponent->scrollToTop(); - if (mode == chooseDirectoryMode) - filenameBox->setText (String::empty, false); - String path (newRootDirectory.getFullPathName()); if (path.isEmpty()) @@ -256,8 +286,7 @@ void FileBrowserComponent::refresh() const String FileBrowserComponent::getActionVerb() const { - return (mode == chooseDirectoryMode) ? TRANS("Choose") - : ((mode == saveFileMode) ? TRANS("Save") : TRANS("Open")); + return isSaveMode() ? TRANS("Save") : TRANS("Open"); } FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const throw() @@ -280,7 +309,7 @@ void FileBrowserComponent::sendListenerChangeMessage() ComponentDeletionWatcher deletionWatcher (this); if (previewComp != 0) - previewComp->selectedFileChanged (getCurrentFile()); + previewComp->selectedFileChanged (getSelectedFile (0)); jassert (! deletionWatcher.hasBeenDeleted()); @@ -297,14 +326,29 @@ void FileBrowserComponent::sendListenerChangeMessage() void FileBrowserComponent::selectionChanged() { - const File selected (fileListComponent->getSelectedFile()); + StringArray newFilenames; + bool resetChosenFiles = true; - if ((mode == chooseDirectoryMode && selected.isDirectory()) - || selected.existsAsFile()) + for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i) { - filenameBox->setText (selected.getRelativePathFrom (getRoot()), false); + const File f (fileListComponent->getSelectedFile (i)); + + if (isFileOrDirSuitable (f)) + { + if (resetChosenFiles) + { + chosenFiles.clear(); + resetChosenFiles = false; + } + + chosenFiles.add (new File (f)); + newFilenames.add (f.getRelativePathFrom (getRoot())); + } } + if (newFilenames.size() > 0) + filenameBox->setText (newFilenames.joinIntoString (T(", ")), false); + sendListenerChangeMessage(); } @@ -375,17 +419,20 @@ void FileBrowserComponent::textEditorReturnKeyPressed (TextEditor&) if (f.isDirectory()) { setRoot (f); + chosenFiles.clear(); filenameBox->setText (String::empty); } else { setRoot (f.getParentDirectory()); + chosenFiles.clear(); + chosenFiles.add (new File (f)); filenameBox->setText (f.getFileName()); } } else { - fileDoubleClicked (getCurrentFile()); + fileDoubleClicked (getSelectedFile (0)); } } @@ -395,7 +442,7 @@ void FileBrowserComponent::textEditorEscapeKeyPressed (TextEditor&) void FileBrowserComponent::textEditorFocusLost (TextEditor&) { - if (mode != saveFileMode) + if (! isSaveMode()) selectionChanged(); } diff --git a/src/gui/components/filebrowser/juce_FileBrowserComponent.h b/src/gui/components/filebrowser/juce_FileBrowserComponent.h index 771f7024a8..fdec5ed536 100644 --- a/src/gui/components/filebrowser/juce_FileBrowserComponent.h +++ b/src/gui/components/filebrowser/juce_FileBrowserComponent.h @@ -50,22 +50,26 @@ class JUCE_API FileBrowserComponent : public Component, private FileBrowserListener, private TextEditorListener, private ButtonListener, - private ComboBoxListener + private ComboBoxListener, + private FileFilter { public: //============================================================================== - /** Various modes that the browser can be used in. + /** Various options for the browser. - One of these is passed into the constructor. + A combination of these is passed into the FileBrowserComponent constructor. */ - enum FileChooserMode + enum FileChooserFlags { - loadFileMode, /**< the component should allow the user to choose an existing - file with the intention of opening it. */ - saveFileMode, /**< the component should allow the user to specify the name of - a file that will be used to save something. */ - chooseDirectoryMode /**< the component should allow the user to select an existing - directory. */ + openMode = 1, /**< the component should allow the user to choose an existing + file with the intention of opening it. */ + saveMode = 2, /**< the component should allow the user to specify the name of + a file that will be used to save something. */ + canSelectFiles = 4, /**< */ + canSelectDirectories = 8, /**< */ + canSelectMultipleItems = 16, /**< */ + useTreeView = 32, /**< */ + filenameBoxIsReadOnly = 64 /**< */ }; //============================================================================== @@ -88,34 +92,41 @@ public: @param filenameTextBoxIsReadOnly if true, the user won't be allowed to type their own text into the filename box. */ - FileBrowserComponent (FileChooserMode browserMode, + FileBrowserComponent (int flags, const File& initialFileOrDirectory, const FileFilter* fileFilter, - FilePreviewComponent* previewComp, - const bool useTreeView = false, - const bool filenameTextBoxIsReadOnly = false); + FilePreviewComponent* previewComp); /** Destructor. */ ~FileBrowserComponent(); //============================================================================== - /** Returns the file that the user has currently chosen. + /** Returns the number of files that the user has got selected. + If multiple select isn't active, this will only be 0 or 1. To get the complete + list of files they've chosen, pass an index to getCurrentFile(). + */ + int getNumSelectedFiles() const throw(); + + /** Returns one of the files that the user has chosen. + If the box has multi-select enabled, the index lets you specify which of the files + to get - see getNumSelectedFiles() to find out how many files were chosen. @see getHighlightedFile */ - const File getCurrentFile() const throw(); + const File getSelectedFile (int index) const throw(); - /** Returns true if the current file is usable. + /** Returns true if the currently selected file(s) are usable. This can be used to decide whether the user can press "ok" for the current file. What it does depends on the mode, so for example in an "open" - mode, the current file is only valid if one has been selected and if the file - exists. In a "save" mode, a non-existent file would also be valid. + mode, this only returns true if a file has been selected and if it exists. + In a "save" mode, a non-existent file would also be valid. */ bool currentFileIsValid() const; - /** This returns the item in the view that is currently highlighted. + /** This returns the last item in the view that the user has highlighted. This may be different from getCurrentFile(), which returns the value - that is shown in the filename box. + that is shown in the filename box, and if there are multiple selections, + this will only return one of them. @see getCurrentFile */ const File getHighlightedFile() const throw(); @@ -133,9 +144,6 @@ public: /** Refreshes the directory that's currently being listed. */ void refresh(); - /** Returns the browser's current mode. */ - FileChooserMode getMode() const throw() { return mode; } - /** Returns a verb to describe what should happen when the file is accepted. E.g. if browsing in "load file" mode, this will be "Open", if in "save file" @@ -143,6 +151,10 @@ public: */ virtual const String getActionVerb() const; + /** Returns true if the saveMode flag was set when this component was created. + */ + bool isSaveMode() const throw(); + //============================================================================== /** Adds a listener to be told when the user selects and clicks on files. @@ -180,6 +192,10 @@ public: void fileClicked (const File& f, const MouseEvent& e); /** @internal */ void fileDoubleClicked (const File& f); + /** @internal */ + bool isFileSuitable (const File& file) const; + /** @internal */ + bool isDirectorySuitable (const File&) const; /** @internal */ FilePreviewComponent* getPreviewComponent() const throw(); @@ -192,10 +208,11 @@ protected: private: //============================================================================== DirectoryContentsList* fileList; - FileFilter* directoriesOnlyFilter; + const FileFilter* fileFilter; - FileChooserMode mode; + int flags; File currentRoot; + OwnedArray chosenFiles; SortedSet listeners; DirectoryContentsDisplayComponent* fileListComponent; @@ -207,6 +224,7 @@ private: TimeSliceThread thread; void sendListenerChangeMessage(); + bool isFileOrDirSuitable (const File& f) const; FileBrowserComponent (const FileBrowserComponent&); const FileBrowserComponent& operator= (const FileBrowserComponent&); diff --git a/src/gui/components/filebrowser/juce_FileChooser.cpp b/src/gui/components/filebrowser/juce_FileChooser.cpp index 4ccf9a8e3f..66393408e0 100644 --- a/src/gui/components/filebrowser/juce_FileChooser.cpp +++ b/src/gui/components/filebrowser/juce_FileChooser.cpp @@ -120,9 +120,11 @@ bool FileChooser::showDialog (const bool selectsDirectories, && previewComponent->getHeight() > 10)); #if JUCE_WINDOWS - if (useNativeDialogBox) -#else + if (useNativeDialogBox && ! (selectsFiles && selectsDirectories)) +#elif JUCE_MAC if (useNativeDialogBox && (previewComponent == 0)) +#else + if (false) #endif { showPlatformDialog (results, title, startingFile, filters, @@ -133,14 +135,23 @@ bool FileChooser::showDialog (const bool selectsDirectories, } else { - jassert (! selectMultipleFiles); // not yet implemented for juce dialogs! + WildcardFileFilter wildcard (selectsFiles ? filters : String::empty, + selectsDirectories ? "*" : String::empty, + String::empty); - WildcardFileFilter wildcard (filters, String::empty); + int flags = isSave ? FileBrowserComponent::saveMode + : FileBrowserComponent::openMode; - FileBrowserComponent browserComponent (selectsDirectories ? FileBrowserComponent::chooseDirectoryMode - : (isSave ? FileBrowserComponent::saveFileMode - : FileBrowserComponent::loadFileMode), - startingFile, &wildcard, previewComponent); + if (selectsFiles) + flags |= FileBrowserComponent::canSelectFiles; + + if (selectsDirectories) + flags |= FileBrowserComponent::canSelectDirectories; + + if (selectMultipleFiles) + flags |= FileBrowserComponent::canSelectMultipleItems; + + FileBrowserComponent browserComponent (flags, startingFile, &wildcard, previewComponent); FileChooserDialogBox box (title, String::empty, browserComponent, @@ -148,7 +159,10 @@ bool FileChooser::showDialog (const bool selectsDirectories, browserComponent.findColour (AlertWindow::backgroundColourId)); if (box.show()) - results.add (new File (browserComponent.getCurrentFile())); + { + for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i) + results.add (new File (browserComponent.getSelectedFile (i))); + } } if (currentlyFocused != 0 && ! currentlyFocusedChecker->hasBeenDeleted()) diff --git a/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp b/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp index 7f01398829..849a15881f 100644 --- a/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp +++ b/src/gui/components/filebrowser/juce_FileChooserDialogBox.cpp @@ -101,13 +101,13 @@ void FileChooserDialogBox::buttonClicked (Button* button) if (button == content->okButton) { if (warnAboutOverwritingExistingFiles - && content->chooserComponent->getMode() == FileBrowserComponent::saveFileMode - && content->chooserComponent->getCurrentFile().exists()) + && content->chooserComponent->isSaveMode() + && content->chooserComponent->getSelectedFile(0).exists()) { if (! AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), TRANS("There's already a file called:\n\n") - + content->chooserComponent->getCurrentFile().getFullPathName() + + content->chooserComponent->getSelectedFile(0).getFullPathName() + T("\n\nAre you sure you want to overwrite it?"), TRANS("overwrite"), TRANS("cancel"))) diff --git a/src/gui/components/filebrowser/juce_FileListComponent.cpp b/src/gui/components/filebrowser/juce_FileListComponent.cpp index 5c53b139d3..fdf375bf60 100644 --- a/src/gui/components/filebrowser/juce_FileListComponent.cpp +++ b/src/gui/components/filebrowser/juce_FileListComponent.cpp @@ -50,9 +50,14 @@ FileListComponent::~FileListComponent() deleteAllChildren(); } -const File FileListComponent::getSelectedFile() const +int FileListComponent::getNumSelectedFiles() const { - return fileList.getFile (getSelectedRow()); + return getNumSelectedRows(); +} + +const File FileListComponent::getSelectedFile (int index) const +{ + return fileList.getFile (getSelectedRow (index)); } void FileListComponent::scrollToTop() diff --git a/src/gui/components/filebrowser/juce_FileListComponent.h b/src/gui/components/filebrowser/juce_FileListComponent.h index 18065dfd2f..ef2c8152e9 100644 --- a/src/gui/components/filebrowser/juce_FileListComponent.h +++ b/src/gui/components/filebrowser/juce_FileListComponent.h @@ -59,11 +59,16 @@ public: ~FileListComponent(); //============================================================================== - /** Returns the file that the user has currently selected. - - Returns File::nonexistent if none is selected. + /** Returns the number of files the user has got selected. + @see getSelectedFile */ - const File getSelectedFile() const; + int getNumSelectedFiles() const; + + /** Returns one of the files that the user has currently selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles + */ + const File getSelectedFile (int index = 0) const; /** Scrolls to the top of the list. */ void scrollToTop(); diff --git a/src/gui/components/filebrowser/juce_FileTreeComponent.cpp b/src/gui/components/filebrowser/juce_FileTreeComponent.cpp index bd835c36cf..6ca0305582 100644 --- a/src/gui/components/filebrowser/juce_FileTreeComponent.cpp +++ b/src/gui/components/filebrowser/juce_FileTreeComponent.cpp @@ -248,12 +248,7 @@ FileTreeComponent::~FileTreeComponent() } //============================================================================== -const File FileTreeComponent::getSelectedFile() const -{ - return getSelectedFile (0); -} - -const File FileTreeComponent::getSelectedFile (const int index) const throw() +const File FileTreeComponent::getSelectedFile (const int index) const { const FileListTreeItem* const item = dynamic_cast (getSelectedItem (index)); diff --git a/src/gui/components/filebrowser/juce_FileTreeComponent.h b/src/gui/components/filebrowser/juce_FileTreeComponent.h index 6181894f4b..e32073782a 100644 --- a/src/gui/components/filebrowser/juce_FileTreeComponent.h +++ b/src/gui/components/filebrowser/juce_FileTreeComponent.h @@ -55,21 +55,16 @@ public: ~FileTreeComponent(); //============================================================================== - /** Returns the number of selected files in the tree. + /** Returns the number of files the user has got selected. + @see getSelectedFile */ - int getNumSelectedFiles() const throw() { return TreeView::getNumSelectedItems(); } + int getNumSelectedFiles() const { return TreeView::getNumSelectedItems(); } /** Returns one of the files that the user has currently selected. - - Returns File::nonexistent if none is selected. + The index should be in the range 0 to (getNumSelectedFiles() - 1). + @see getNumSelectedFiles */ - const File getSelectedFile (int index) const throw(); - - /** Returns the first of the files that the user has currently selected. - - Returns File::nonexistent if none is selected. - */ - const File getSelectedFile() const; + const File getSelectedFile (int index = 0) const; /** Scrolls the list to the top. */ void scrollToTop(); diff --git a/src/gui/components/filebrowser/juce_WildcardFileFilter.cpp b/src/gui/components/filebrowser/juce_WildcardFileFilter.cpp index be0b2ef257..4f195ccf75 100644 --- a/src/gui/components/filebrowser/juce_WildcardFileFilter.cpp +++ b/src/gui/components/filebrowser/juce_WildcardFileFilter.cpp @@ -31,30 +31,46 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -WildcardFileFilter::WildcardFileFilter (const String& wildcardPatterns, +WildcardFileFilter::WildcardFileFilter (const String& fileWildcardPatterns, + const String& directoryWildcardPatterns, const String& description) - : FileFilter (description.isEmpty() ? wildcardPatterns - : (description + T(" (") + wildcardPatterns + T(")"))) + : FileFilter (description.isEmpty() ? fileWildcardPatterns + : (description + T(" (") + fileWildcardPatterns + T(")"))) { - wildcards.addTokens (wildcardPatterns.toLowerCase(), T(";,"), T("\"'")); - - wildcards.trim(); - wildcards.removeEmptyStrings(); - - // special case for *.*, because people use it to mean "any file", but it - // would actually ignore files with no extension. - for (int i = wildcards.size(); --i >= 0;) - if (wildcards[i] == T("*.*")) - wildcards.set (i, T("*")); + parse (fileWildcardPatterns, fileWildcards); + parse (directoryWildcardPatterns, directoryWildcards); } WildcardFileFilter::~WildcardFileFilter() { } +bool WildcardFileFilter::isFileSuitable (const File& file) const +{ + return match (file, fileWildcards); +} + +bool WildcardFileFilter::isDirectorySuitable (const File& file) const +{ + return match (file, directoryWildcards); +} //============================================================================== -bool WildcardFileFilter::isFileSuitable (const File& file) const +void WildcardFileFilter::parse (const String& pattern, StringArray& result) throw() +{ + result.addTokens (pattern.toLowerCase(), T(";,"), T("\"'")); + + result.trim(); + result.removeEmptyStrings(); + + // special case for *.*, because people use it to mean "any file", but it + // would actually ignore files with no extension. + for (int i = result.size(); --i >= 0;) + if (result[i] == T("*.*")) + result.set (i, T("*")); +} + +bool WildcardFileFilter::match (const File& file, const StringArray& wildcards) throw() { const String filename (file.getFileName()); @@ -65,10 +81,5 @@ bool WildcardFileFilter::isFileSuitable (const File& file) const return false; } -bool WildcardFileFilter::isDirectorySuitable (const File&) const -{ - return true; -} - END_JUCE_NAMESPACE diff --git a/src/gui/components/filebrowser/juce_WildcardFileFilter.h b/src/gui/components/filebrowser/juce_WildcardFileFilter.h index edbd075f51..582f6653cb 100644 --- a/src/gui/components/filebrowser/juce_WildcardFileFilter.h +++ b/src/gui/components/filebrowser/juce_WildcardFileFilter.h @@ -52,7 +52,8 @@ public: The description is a name to show the user in a list of possible patterns, so for the wav/aiff example, your description might be "audio files". */ - WildcardFileFilter (const String& wildcardPatterns, + WildcardFileFilter (const String& fileWildcardPatterns, + const String& directoryWildcardPatterns, const String& description); /** Destructor. */ @@ -65,12 +66,14 @@ public: /** This always returns true. */ bool isDirectorySuitable (const File& file) const; - //============================================================================== juce_UseDebuggingNewOperator private: - StringArray wildcards; + StringArray fileWildcards, directoryWildcards; + + static void parse (const String& pattern, StringArray& result) throw(); + static bool match (const File& file, const StringArray& wildcards) throw(); };