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.

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