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.

822 lines
27KB

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