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 // Win32NativeFileChooser needs to be a reference counted object as there
30 // is no way for the parent to know when the dialog HWND has actually been
31 // created without pumping the message thread (which is forbidden when modal
32 // loops are disabled). However, the HWND pointer is the only way to cancel
33 // the dialog box. This means that the actual native FileChooser HWND may
34 // not have been created yet when the user deletes JUCE's FileChooser class. If this
35 // occurs the Win32NativeFileChooser will still have a reference count of 1 and will
36 // simply delete itself immediately once the HWND will have been created a while later.
37 class Win32NativeFileChooser  : public ReferenceCountedObject,
38                                 private Thread
39 {
40 public:
41     using Ptr = ReferenceCountedObjectPtr<Win32NativeFileChooser>;
42 
43     enum { charsAvailableForResult = 32768 };
44 
Win32NativeFileChooser(Component * parent,int flags,FilePreviewComponent * previewComp,const File & startingFile,const String & titleToUse,const String & filtersToUse)45     Win32NativeFileChooser (Component* parent, int flags, FilePreviewComponent* previewComp,
46                             const File& startingFile, const String& titleToUse,
47                             const String& filtersToUse)
48         : Thread ("Native Win32 FileChooser"),
49           owner (parent), title (titleToUse), filtersString (filtersToUse),
50           selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories)   != 0),
51           isSave             ((flags & FileBrowserComponent::saveMode)               != 0),
52           warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting)   != 0),
53           selectMultiple     ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
54           nativeDialogRef (nullptr), shouldCancel (0)
55     {
56         auto parentDirectory = startingFile.getParentDirectory();
57 
58         // Handle nonexistent root directories in the same way as existing ones
59         files.calloc (static_cast<size_t> (charsAvailableForResult) + 1);
60 
61         if (startingFile.isDirectory() || startingFile.isRoot())
62         {
63             initialPath = startingFile.getFullPathName();
64         }
65         else
66         {
67             startingFile.getFileName().copyToUTF16 (files,
68                                                     static_cast<size_t> (charsAvailableForResult) * sizeof (WCHAR));
69             initialPath = parentDirectory.getFullPathName();
70         }
71 
72         if (! selectsDirectories)
73         {
74             if (previewComp != nullptr)
75                 customComponent.reset (new CustomComponentHolder (previewComp));
76 
77             setupFilters();
78         }
79     }
80 
~Win32NativeFileChooser()81     ~Win32NativeFileChooser()
82     {
83         signalThreadShouldExit();
84         waitForThreadToExit (-1);
85     }
86 
open(bool async)87     void open (bool async)
88     {
89         results.clear();
90 
91         // the thread should not be running
92         nativeDialogRef.set (nullptr);
93 
94         if (async)
95         {
96             jassert (! isThreadRunning());
97 
98             threadHasReference.reset();
99             startThread();
100 
101             threadHasReference.wait (-1);
102         }
103         else
104         {
105             results = openDialog (false);
106             owner->exitModalState (results.size() > 0 ? 1 : 0);
107         }
108     }
109 
cancel()110     void cancel()
111     {
112         ScopedLock lock (deletingDialog);
113 
114         customComponent = nullptr;
115         shouldCancel.set (1);
116 
117         if (auto hwnd = nativeDialogRef.get())
118             EndDialog (hwnd, 0);
119     }
120 
getCustomComponent()121     Component* getCustomComponent()    { return customComponent.get(); }
122 
123     Array<URL> results;
124 
125 private:
126     //==============================================================================
127     class CustomComponentHolder  : public Component
128     {
129     public:
CustomComponentHolder(Component * const customComp)130         CustomComponentHolder (Component* const customComp)
131         {
132             setVisible (true);
133             setOpaque (true);
134             addAndMakeVisible (customComp);
135             setSize (jlimit (20, 800, customComp->getWidth()), customComp->getHeight());
136         }
137 
paint(Graphics & g)138         void paint (Graphics& g) override
139         {
140             g.fillAll (Colours::lightgrey);
141         }
142 
resized()143         void resized() override
144         {
145             if (Component* const c = getChildComponent(0))
146                 c->setBounds (getLocalBounds());
147         }
148 
149     private:
150         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
151     };
152 
153     //==============================================================================
154     Component::SafePointer<Component> owner;
155     String title, filtersString;
156     std::unique_ptr<CustomComponentHolder> customComponent;
157     String initialPath, returnedString;
158 
159     WaitableEvent threadHasReference;
160     CriticalSection deletingDialog;
161 
162     bool selectsDirectories, isSave, warnAboutOverwrite, selectMultiple;
163 
164     HeapBlock<WCHAR> files;
165     HeapBlock<WCHAR> filters;
166 
167     Atomic<HWND> nativeDialogRef;
168     Atomic<int>  shouldCancel;
169 
170    #if JUCE_MSVC
showDialog(IFileDialog & dialog,bool async) const171     bool showDialog (IFileDialog& dialog, bool async) const
172     {
173         FILEOPENDIALOGOPTIONS flags = {};
174 
175         if (FAILED (dialog.GetOptions (&flags)))
176             return false;
177 
178         const auto setBit = [] (FILEOPENDIALOGOPTIONS& field, bool value, FILEOPENDIALOGOPTIONS option)
179         {
180             if (value)
181                 field |= option;
182             else
183                 field &= ~option;
184         };
185 
186         setBit (flags, selectsDirectories,         FOS_PICKFOLDERS);
187         setBit (flags, warnAboutOverwrite,         FOS_OVERWRITEPROMPT);
188         setBit (flags, selectMultiple,             FOS_ALLOWMULTISELECT);
189         setBit (flags, customComponent != nullptr, FOS_FORCEPREVIEWPANEON);
190 
191         if (FAILED (dialog.SetOptions (flags)) || FAILED (dialog.SetTitle (title.toUTF16())))
192             return false;
193 
194         PIDLIST_ABSOLUTE pidl = {};
195 
196         if (FAILED (SHParseDisplayName (initialPath.toWideCharPointer(), nullptr, &pidl, SFGAO_FOLDER, nullptr)))
197             return false;
198 
199         const auto item = [&]
200         {
201             ComSmartPtr<IShellItem> ptr;
202             SHCreateShellItem (nullptr, nullptr, pidl, ptr.resetAndGetPointerAddress());
203             return ptr;
204         }();
205 
206         if (item == nullptr || FAILED (dialog.SetFolder (item)))
207             return false;
208 
209         String filename (files.getData());
210 
211         if (FAILED (dialog.SetFileName (filename.toWideCharPointer())))
212             return false;
213 
214         auto extension = getDefaultFileExtension (filename);
215 
216         if (extension.isNotEmpty() && FAILED (dialog.SetDefaultExtension (extension.toWideCharPointer())))
217             return false;
218 
219         const COMDLG_FILTERSPEC spec[] { { filtersString.toWideCharPointer(), filtersString.toWideCharPointer() } };
220 
221         if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec)))
222             return false;
223 
224         return dialog.Show (static_cast<HWND> (async ? nullptr : owner->getWindowHandle())) == S_OK;
225     }
226 
227     //==============================================================================
openDialogVistaAndUp(bool async)228     Array<URL> openDialogVistaAndUp (bool async)
229     {
230         const auto getUrl = [] (IShellItem& item)
231         {
232             struct Free
233             {
234                 void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); }
235             };
236 
237             LPWSTR ptr = nullptr;
238             item.GetDisplayName (SIGDN_URL, &ptr);
239             return std::unique_ptr<WCHAR, Free> { ptr };
240         };
241 
242         if (isSave)
243         {
244             const auto dialog = [&]
245             {
246                 ComSmartPtr<IFileDialog> ptr;
247                 ptr.CoCreateInstance (CLSID_FileSaveDialog, CLSCTX_INPROC_SERVER);
248                 return ptr;
249             }();
250 
251             if (dialog == nullptr)
252                 return {};
253 
254             showDialog (*dialog, async);
255 
256             const auto item = [&]
257             {
258                 ComSmartPtr<IShellItem> ptr;
259                 dialog->GetResult (ptr.resetAndGetPointerAddress());
260                 return ptr;
261             }();
262 
263             if (item == nullptr)
264                 return {};
265 
266             return { URL (String (getUrl (*item).get())) };
267         }
268 
269         const auto dialog = [&]
270         {
271             ComSmartPtr<IFileOpenDialog> ptr;
272             ptr.CoCreateInstance (CLSID_FileOpenDialog, CLSCTX_INPROC_SERVER);
273             return ptr;
274         }();
275 
276         if (dialog == nullptr)
277             return {};
278 
279         showDialog (*dialog, async);
280 
281         const auto items = [&]
282         {
283             ComSmartPtr<IShellItemArray> ptr;
284             dialog->GetResults (ptr.resetAndGetPointerAddress());
285             return ptr;
286         }();
287 
288         if (items == nullptr)
289             return {};
290 
291         Array<URL> result;
292 
293         DWORD numItems = 0;
294         items->GetCount (&numItems);
295 
296         for (DWORD i = 0; i < numItems; ++i)
297         {
298             ComSmartPtr<IShellItem> scope;
299             items->GetItemAt (i, scope.resetAndGetPointerAddress());
300 
301             if (scope != nullptr)
302                 result.add (String (getUrl (*scope).get()));
303         }
304 
305         return result;
306     }
307    #endif
308 
openDialogPreVista(bool async)309     Array<URL> openDialogPreVista (bool async)
310     {
311         Array<URL> selections;
312 
313         if (selectsDirectories)
314         {
315             BROWSEINFO bi = {};
316             bi.hwndOwner = (HWND) (async ? nullptr : owner->getWindowHandle());
317             bi.pszDisplayName = files;
318             bi.lpszTitle = title.toWideCharPointer();
319             bi.lParam = (LPARAM) this;
320             bi.lpfn = browseCallbackProc;
321            #ifdef BIF_USENEWUI
322             bi.ulFlags = BIF_USENEWUI | BIF_VALIDATE;
323            #else
324             bi.ulFlags = 0x50;
325            #endif
326 
327             LPITEMIDLIST list = SHBrowseForFolder (&bi);
328 
329             if (! SHGetPathFromIDListW (list, files))
330             {
331                 files[0] = 0;
332                 returnedString.clear();
333             }
334 
335             LPMALLOC al;
336 
337             if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
338                 al->Free (list);
339 
340             if (files[0] != 0)
341             {
342                 File result (String (files.get()));
343 
344                 if (returnedString.isNotEmpty())
345                     result = result.getSiblingFile (returnedString);
346 
347                 selections.add (URL (result));
348             }
349         }
350         else
351         {
352             OPENFILENAMEW of = {};
353 
354            #ifdef OPENFILENAME_SIZE_VERSION_400W
355             of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
356            #else
357             of.lStructSize = sizeof (of);
358            #endif
359             of.hwndOwner = (HWND) (async ? nullptr : owner->getWindowHandle());
360             of.lpstrFilter = filters.getData();
361             of.nFilterIndex = 1;
362             of.lpstrFile = files;
363             of.nMaxFile = (DWORD) charsAvailableForResult;
364             of.lpstrInitialDir = initialPath.toWideCharPointer();
365             of.lpstrTitle = title.toWideCharPointer();
366             of.Flags = getOpenFilenameFlags (async);
367             of.lCustData = (LPARAM) this;
368             of.lpfnHook = &openCallback;
369 
370             if (isSave)
371             {
372                 auto extension = getDefaultFileExtension (files.getData());
373 
374                 if (extension.isNotEmpty())
375                     of.lpstrDefExt = extension.toWideCharPointer();
376 
377                 if (! GetSaveFileName (&of))
378                     return {};
379             }
380             else
381             {
382                 if (! GetOpenFileName (&of))
383                     return {};
384             }
385 
386             if (selectMultiple && of.nFileOffset > 0 && files[of.nFileOffset - 1] == 0)
387             {
388                 const WCHAR* filename = files + of.nFileOffset;
389 
390                 while (*filename != 0)
391                 {
392                     selections.add (URL (File (String (files.get())).getChildFile (String (filename))));
393                     filename += wcslen (filename) + 1;
394                 }
395             }
396             else if (files[0] != 0)
397             {
398                 selections.add (URL (File (String (files.get()))));
399             }
400         }
401 
402         return selections;
403     }
404 
openDialog(bool async)405     Array<URL> openDialog (bool async)
406     {
407         struct Remover
408         {
409             explicit Remover (Win32NativeFileChooser& chooser) : item (chooser) {}
410             ~Remover() { getNativeDialogList().removeValue (&item); }
411 
412             Win32NativeFileChooser& item;
413         };
414 
415         const Remover remover (*this);
416 
417        #if JUCE_MSVC
418         if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
419             return openDialogVistaAndUp (async);
420        #endif
421 
422         return openDialogPreVista (async);
423     }
424 
run()425     void run() override
426     {
427         // as long as the thread is running, don't delete this class
428         Ptr safeThis (this);
429         threadHasReference.signal();
430 
431         auto r = openDialog (true);
432         MessageManager::callAsync ([safeThis, r]
433         {
434             safeThis->results = r;
435 
436             if (safeThis->owner != nullptr)
437                 safeThis->owner->exitModalState (r.size() > 0 ? 1 : 0);
438         });
439     }
440 
getNativeDialogList()441     static HashMap<HWND, Win32NativeFileChooser*>& getNativeDialogList()
442     {
443         static HashMap<HWND, Win32NativeFileChooser*> dialogs;
444         return dialogs;
445     }
446 
getNativePointerForDialog(HWND hWnd)447     static Win32NativeFileChooser* getNativePointerForDialog (HWND hWnd)
448     {
449         return getNativeDialogList()[hWnd];
450     }
451 
452     //==============================================================================
setupFilters()453     void setupFilters()
454     {
455         const size_t filterSpaceNumChars = 2048;
456         filters.calloc (filterSpaceNumChars);
457 
458         const size_t bytesWritten = filtersString.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
459         filtersString.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
460                                    ((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));
461 
462         for (size_t i = 0; i < filterSpaceNumChars; ++i)
463             if (filters[i] == '|')
464                 filters[i] = 0;
465     }
466 
getOpenFilenameFlags(bool async)467     DWORD getOpenFilenameFlags (bool async)
468     {
469         DWORD ofFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;
470 
471         if (warnAboutOverwrite)
472             ofFlags |= OFN_OVERWRITEPROMPT;
473 
474         if (selectMultiple)
475             ofFlags |= OFN_ALLOWMULTISELECT;
476 
477         if (async || customComponent != nullptr)
478             ofFlags |= OFN_ENABLEHOOK;
479 
480         return ofFlags;
481     }
482 
getDefaultFileExtension(const String & filename) const483     String getDefaultFileExtension (const String& filename) const
484     {
485         auto extension = filename.fromLastOccurrenceOf (".", false, false);
486 
487         if (extension.isEmpty())
488         {
489             auto tokens = StringArray::fromTokens (filtersString, ";,", "\"'");
490             tokens.trim();
491             tokens.removeEmptyStrings();
492 
493             if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
494                 extension = tokens[0].fromFirstOccurrenceOf (".", false, false);
495         }
496 
497         return extension;
498     }
499 
500     //==============================================================================
initialised(HWND hWnd)501     void initialised (HWND hWnd)
502     {
503         SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) initialPath.toWideCharPointer());
504         initDialog (hWnd);
505     }
506 
validateFailed(const String & path)507     void validateFailed (const String& path)
508     {
509         returnedString = path;
510     }
511 
initDialog(HWND hdlg)512     void initDialog (HWND hdlg)
513     {
514         ScopedLock lock (deletingDialog);
515         getNativeDialogList().set (hdlg, this);
516 
517         if (shouldCancel.get() != 0)
518         {
519             EndDialog (hdlg, 0);
520         }
521         else
522         {
523             nativeDialogRef.set (hdlg);
524 
525             if (customComponent != nullptr)
526             {
527                 Component::SafePointer<Component> safeCustomComponent (customComponent.get());
528 
529                 RECT dialogScreenRect, dialogClientRect;
530                 GetWindowRect (hdlg, &dialogScreenRect);
531                 GetClientRect (hdlg, &dialogClientRect);
532 
533                 auto screenRectangle = Rectangle<int>::leftTopRightBottom (dialogScreenRect.left,  dialogScreenRect.top,
534                                                                            dialogScreenRect.right, dialogScreenRect.bottom);
535 
536                 auto scale = Desktop::getInstance().getDisplays().findDisplayForRect (screenRectangle, true).scale;
537                 auto physicalComponentWidth = roundToInt (safeCustomComponent->getWidth() * scale);
538 
539                 SetWindowPos (hdlg, nullptr, screenRectangle.getX(), screenRectangle.getY(),
540                               physicalComponentWidth + jmax (150, screenRectangle.getWidth()),
541                               jmax (150, screenRectangle.getHeight()),
542                               SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
543 
544                 auto appendCustomComponent = [safeCustomComponent, dialogClientRect, scale, hdlg]() mutable
545                 {
546                     if (safeCustomComponent != nullptr)
547                     {
548                         auto scaledClientRectangle = Rectangle<int>::leftTopRightBottom (dialogClientRect.left, dialogClientRect.top,
549                                                                                          dialogClientRect.right, dialogClientRect.bottom) / scale;
550 
551                         safeCustomComponent->setBounds (scaledClientRectangle.getRight(), scaledClientRectangle.getY(),
552                                                         safeCustomComponent->getWidth(), scaledClientRectangle.getHeight());
553                         safeCustomComponent->addToDesktop (0, hdlg);
554                     }
555                 };
556 
557                 if (MessageManager::getInstance()->isThisTheMessageThread())
558                     appendCustomComponent();
559                 else
560                     MessageManager::callAsync (appendCustomComponent);
561             }
562         }
563     }
564 
destroyDialog(HWND hdlg)565     void destroyDialog (HWND hdlg)
566     {
567         ScopedLock exiting (deletingDialog);
568 
569         getNativeDialogList().remove (hdlg);
570         nativeDialogRef.set (nullptr);
571 
572         if (MessageManager::getInstance()->isThisTheMessageThread())
573             customComponent = nullptr;
574         else
575             MessageManager::callAsync ([this] { customComponent = nullptr; });
576     }
577 
selectionChanged(HWND hdlg)578     void selectionChanged (HWND hdlg)
579     {
580         ScopedLock lock (deletingDialog);
581 
582         if (customComponent != nullptr && shouldCancel.get() == 0)
583         {
584             if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent (0)))
585             {
586                 WCHAR path [MAX_PATH * 2] = { 0 };
587                 CommDlg_OpenSave_GetFilePath (hdlg, (LPARAM) &path, MAX_PATH);
588 
589                 if (MessageManager::getInstance()->isThisTheMessageThread())
590                 {
591                     comp->selectedFileChanged (File (path));
592                 }
593                 else
594                 {
595                     Component::SafePointer<FilePreviewComponent> safeComp (comp);
596 
597                     File selectedFile (path);
598                     MessageManager::callAsync ([safeComp, selectedFile]() mutable
599                                                {
600                                                     safeComp->selectedFileChanged (selectedFile);
601                                                });
602                 }
603             }
604         }
605     }
606 
607     //==============================================================================
browseCallbackProc(HWND hWnd,UINT msg,LPARAM lParam,LPARAM lpData)608     static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
609     {
610         auto* self = reinterpret_cast<Win32NativeFileChooser*> (lpData);
611 
612         switch (msg)
613         {
614             case BFFM_INITIALIZED:       self->initialised (hWnd);                             break;
615             case BFFM_VALIDATEFAILEDW:   self->validateFailed (String ((LPCWSTR)     lParam)); break;
616             case BFFM_VALIDATEFAILEDA:   self->validateFailed (String ((const char*) lParam)); break;
617             default: break;
618         }
619 
620         return 0;
621     }
622 
openCallback(HWND hwnd,UINT uiMsg,WPARAM,LPARAM lParam)623     static UINT_PTR CALLBACK openCallback (HWND hwnd, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
624     {
625         auto hdlg = getDialogFromHWND (hwnd);
626 
627         switch (uiMsg)
628         {
629             case WM_INITDIALOG:
630             {
631                 if (auto* self = reinterpret_cast<Win32NativeFileChooser*> (((OPENFILENAMEW*) lParam)->lCustData))
632                     self->initDialog (hdlg);
633 
634                 break;
635             }
636 
637             case WM_DESTROY:
638             {
639                 if (auto* self = getNativeDialogList()[hdlg])
640                     self->destroyDialog (hdlg);
641 
642                 break;
643             }
644 
645             case WM_NOTIFY:
646             {
647                 auto ofn = reinterpret_cast<LPOFNOTIFY> (lParam);
648 
649                 if (ofn->hdr.code == CDN_SELCHANGE)
650                     if (auto* self = reinterpret_cast<Win32NativeFileChooser*> (ofn->lpOFN->lCustData))
651                         self->selectionChanged (hdlg);
652 
653                 break;
654             }
655 
656             default:
657                 break;
658         }
659 
660         return 0;
661     }
662 
getDialogFromHWND(HWND hwnd)663     static HWND getDialogFromHWND (HWND hwnd)
664     {
665         if (hwnd == nullptr)
666             return nullptr;
667 
668         HWND dialogH = GetParent (hwnd);
669 
670         if (dialogH == nullptr)
671             dialogH = hwnd;
672 
673         return dialogH;
674     }
675 
676     //==============================================================================
677     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser)
678 };
679 
680 class FileChooser::Native     : public Component,
681                                 public FileChooser::Pimpl
682 {
683 public:
Native(FileChooser & fileChooser,int flags,FilePreviewComponent * previewComp)684     Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp)
685         : owner (fileChooser),
686           nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile,
687                                                          fileChooser.title, fileChooser.filters))
688     {
689         auto mainMon = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
690 
691         setBounds (mainMon.getX() + mainMon.getWidth() / 4,
692                    mainMon.getY() + mainMon.getHeight() / 4,
693                    0, 0);
694 
695         setOpaque (true);
696         setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
697         addToDesktop (0);
698     }
699 
~Native()700     ~Native() override
701     {
702         exitModalState (0);
703         nativeFileChooser->cancel();
704         nativeFileChooser = nullptr;
705     }
706 
launch()707     void launch() override
708     {
709         SafePointer<Native> safeThis (this);
710 
711         enterModalState (true, ModalCallbackFunction::create (
712                          [safeThis] (int)
713                          {
714                              if (safeThis != nullptr)
715                                  safeThis->owner.finished (safeThis->nativeFileChooser->results);
716                          }));
717 
718         nativeFileChooser->open (true);
719     }
720 
runModally()721     void runModally() override
722     {
723         enterModalState (true);
724         nativeFileChooser->open (false);
725         exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0);
726         nativeFileChooser->cancel();
727 
728         owner.finished (nativeFileChooser->results);
729     }
730 
canModalEventBeSentToComponent(const Component * targetComponent)731     bool canModalEventBeSentToComponent (const Component* targetComponent) override
732     {
733         if (targetComponent == nullptr)
734             return false;
735 
736         if (targetComponent == nativeFileChooser->getCustomComponent())
737             return true;
738 
739         return targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr;
740     }
741 
742 private:
743     FileChooser& owner;
744     Win32NativeFileChooser::Ptr nativeFileChooser;
745 };
746 
747 //==============================================================================
isPlatformDialogAvailable()748 bool FileChooser::isPlatformDialogAvailable()
749 {
750    #if JUCE_DISABLE_NATIVE_FILECHOOSERS
751     return false;
752    #else
753     return true;
754    #endif
755 }
756 
showPlatformDialog(FileChooser & owner,int flags,FilePreviewComponent * preview)757 FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
758                                                      FilePreviewComponent* preview)
759 {
760     return new FileChooser::Native (owner, flags, preview);
761 }
762 
763 } // namespace juce
764