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.

862 lines
28KB

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