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.

903 lines
30KB

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