The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

802 lines
26KB

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