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.

899 lines
29KB

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