1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 //==============================================================================
30 class FileChooser::NonNative    : public FileChooser::Pimpl
31 {
32 public:
NonNative(FileChooser & fileChooser,int flags,FilePreviewComponent * preview)33     NonNative (FileChooser& fileChooser, int flags, FilePreviewComponent* preview)
34         : owner (fileChooser),
35           selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories)   != 0),
36           selectsFiles       ((flags & FileBrowserComponent::canSelectFiles)         != 0),
37           warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting)   != 0),
38 
39           filter (selectsFiles ? owner.filters : String(), selectsDirectories ? "*" : String(), {}),
40           browserComponent (flags, owner.startingFile, &filter, preview),
41           dialogBox (owner.title, {}, browserComponent, warnAboutOverwrite,
42                      browserComponent.findColour (AlertWindow::backgroundColourId), owner.parent)
43     {}
44 
~NonNative()45     ~NonNative() override
46     {
47         dialogBox.exitModalState (0);
48     }
49 
launch()50     void launch() override
51     {
52         dialogBox.centreWithDefaultSize (nullptr);
53         dialogBox.enterModalState (true, ModalCallbackFunction::create ([this] (int r) { modalStateFinished (r); }), true);
54     }
55 
runModally()56     void runModally() override
57     {
58        #if JUCE_MODAL_LOOPS_PERMITTED
59         modalStateFinished (dialogBox.show() ? 1 : 0);
60        #else
61         jassertfalse;
62        #endif
63     }
64 
65 private:
modalStateFinished(int returnValue)66     void modalStateFinished (int returnValue)
67     {
68         Array<URL> result;
69 
70         if (returnValue != 0)
71         {
72             for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
73                 result.add (URL (browserComponent.getSelectedFile (i)));
74         }
75 
76         owner.finished (result);
77     }
78 
79     //==============================================================================
80     FileChooser& owner;
81     bool selectsDirectories, selectsFiles, warnAboutOverwrite;
82 
83     WildcardFileFilter filter;
84     FileBrowserComponent browserComponent;
85     FileChooserDialogBox dialogBox;
86 
87     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonNative)
88 };
89 
90 //==============================================================================
FileChooser(const String & chooserBoxTitle,const File & currentFileOrDirectory,const String & fileFilters,const bool useNativeBox,const bool treatFilePackagesAsDirectories,Component * parentComponentToUse)91 FileChooser::FileChooser (const String& chooserBoxTitle,
92                           const File& currentFileOrDirectory,
93                           const String& fileFilters,
94                           const bool useNativeBox,
95                           const bool treatFilePackagesAsDirectories,
96                           Component* parentComponentToUse)
97     : title (chooserBoxTitle),
98       filters (fileFilters),
99       startingFile (currentFileOrDirectory),
100       parent (parentComponentToUse),
101       useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
102       treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
103 {
104    #ifndef JUCE_MAC
105     ignoreUnused (treatFilePackagesAsDirs);
106    #endif
107 
108     if (! fileFilters.containsNonWhitespaceChars())
109         filters = "*";
110 }
111 
~FileChooser()112 FileChooser::~FileChooser()
113 {
114     asyncCallback = nullptr;
115 }
116 
117 #if JUCE_MODAL_LOOPS_PERMITTED
browseForFileToOpen(FilePreviewComponent * previewComp)118 bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
119 {
120     return showDialog (FileBrowserComponent::openMode
121                         | FileBrowserComponent::canSelectFiles,
122                        previewComp);
123 }
124 
browseForMultipleFilesToOpen(FilePreviewComponent * previewComp)125 bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComp)
126 {
127     return showDialog (FileBrowserComponent::openMode
128                         | FileBrowserComponent::canSelectFiles
129                         | FileBrowserComponent::canSelectMultipleItems,
130                        previewComp);
131 }
132 
browseForMultipleFilesOrDirectories(FilePreviewComponent * previewComp)133 bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComp)
134 {
135     return showDialog (FileBrowserComponent::openMode
136                         | FileBrowserComponent::canSelectFiles
137                         | FileBrowserComponent::canSelectDirectories
138                         | FileBrowserComponent::canSelectMultipleItems,
139                        previewComp);
140 }
141 
browseForFileToSave(const bool warnAboutOverwrite)142 bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
143 {
144     return showDialog (FileBrowserComponent::saveMode
145                         | FileBrowserComponent::canSelectFiles
146                         | (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
147                        nullptr);
148 }
149 
browseForDirectory()150 bool FileChooser::browseForDirectory()
151 {
152     return showDialog (FileBrowserComponent::openMode
153                         | FileBrowserComponent::canSelectDirectories,
154                        nullptr);
155 }
156 
showDialog(const int flags,FilePreviewComponent * const previewComp)157 bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
158 {
159     FocusRestorer focusRestorer;
160 
161     pimpl.reset (createPimpl (flags, previewComp));
162     pimpl->runModally();
163 
164     // ensure that the finished function was invoked
165     jassert (pimpl == nullptr);
166 
167     return (results.size() > 0);
168 }
169 #endif
170 
launchAsync(int flags,std::function<void (const FileChooser &)> callback,FilePreviewComponent * previewComp)171 void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
172                                FilePreviewComponent* previewComp)
173 {
174     // You must specify a callback when using launchAsync
175     jassert (callback);
176 
177     // you cannot run two file chooser dialog boxes at the same time
178     jassert (asyncCallback == nullptr);
179 
180     asyncCallback = std::move (callback);
181 
182     pimpl.reset (createPimpl (flags, previewComp));
183     pimpl->launch();
184 }
185 
186 
createPimpl(int flags,FilePreviewComponent * previewComp)187 FileChooser::Pimpl* FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp)
188 {
189     results.clear();
190 
191     // the preview component needs to be the right size before you pass it in here..
192     jassert (previewComp == nullptr || (previewComp->getWidth() > 10
193                                          && previewComp->getHeight() > 10));
194 
195     if (pimpl != nullptr)
196     {
197         // you cannot run two file chooser dialog boxes at the same time
198         jassertfalse;
199         pimpl.reset();
200     }
201 
202     // You've set the flags for both saveMode and openMode!
203     jassert (! (((flags & FileBrowserComponent::saveMode) != 0)
204                 && ((flags & FileBrowserComponent::openMode) != 0)));
205 
206    #if JUCE_WINDOWS
207     const bool selectsFiles       = (flags & FileBrowserComponent::canSelectFiles) != 0;
208     const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
209 
210     if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
211    #else
212     if (useNativeDialogBox)
213    #endif
214     {
215         return showPlatformDialog (*this, flags, previewComp);
216     }
217     else
218     {
219         return new NonNative (*this, flags, previewComp);
220     }
221 }
222 
getResults() const223 Array<File> FileChooser::getResults() const noexcept
224 {
225     Array<File> files;
226 
227     for (auto url : getURLResults())
228         if (url.isLocalFile())
229             files.add (url.getLocalFile());
230 
231     return files;
232 }
233 
getResult() const234 File FileChooser::getResult() const
235 {
236     auto fileResults = getResults();
237 
238     // if you've used a multiple-file select, you should use the getResults() method
239     // to retrieve all the files that were chosen.
240     jassert (fileResults.size() <= 1);
241 
242     return fileResults.getFirst();
243 }
244 
getURLResult() const245 URL FileChooser::getURLResult() const
246 {
247     // if you've used a multiple-file select, you should use the getResults() method
248     // to retrieve all the files that were chosen.
249     jassert (results.size() <= 1);
250 
251     return results.getFirst();
252 }
253 
finished(const Array<URL> & asyncResults)254 void FileChooser::finished (const Array<URL>& asyncResults)
255 {
256      std::function<void (const FileChooser&)> callback;
257      std::swap (callback, asyncCallback);
258 
259      results = asyncResults;
260 
261      pimpl.reset();
262 
263      if (callback)
264          callback (*this);
265 }
266 
267 //==============================================================================
FilePreviewComponent()268 FilePreviewComponent::FilePreviewComponent() {}
~FilePreviewComponent()269 FilePreviewComponent::~FilePreviewComponent() {}
270 
271 } // namespace juce
272