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.

847 lines
28KB

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