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