Audio plugin host https://kx.studio/carla
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.

859 lines
28KB

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