Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_win32_FileChooser.cpp 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. namespace FileChooserHelpers
  22. {
  23. struct FileChooserCallbackInfo
  24. {
  25. String initialPath;
  26. String returnedString; // need this to get non-existent pathnames from the directory chooser
  27. ScopedPointer<Component> customComponent;
  28. };
  29. static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
  30. {
  31. FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) lpData;
  32. if (msg == BFFM_INITIALIZED)
  33. SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) info->initialPath.toWideCharPointer());
  34. else if (msg == BFFM_VALIDATEFAILEDW)
  35. info->returnedString = (LPCWSTR) lParam;
  36. else if (msg == BFFM_VALIDATEFAILEDA)
  37. info->returnedString = (const char*) lParam;
  38. return 0;
  39. }
  40. static UINT_PTR CALLBACK openCallback (HWND hdlg, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
  41. {
  42. if (uiMsg == WM_INITDIALOG)
  43. {
  44. Component* customComp = ((FileChooserCallbackInfo*) (((OPENFILENAMEW*) lParam)->lCustData))->customComponent;
  45. HWND dialogH = GetParent (hdlg);
  46. jassert (dialogH != 0);
  47. if (dialogH == 0)
  48. dialogH = hdlg;
  49. RECT r, cr;
  50. GetWindowRect (dialogH, &r);
  51. GetClientRect (dialogH, &cr);
  52. SetWindowPos (dialogH, 0,
  53. r.left, r.top,
  54. customComp->getWidth() + jmax (150, (int) (r.right - r.left)),
  55. jmax (150, (int) (r.bottom - r.top)),
  56. SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
  57. customComp->setBounds (cr.right, cr.top, customComp->getWidth(), cr.bottom - cr.top);
  58. customComp->addToDesktop (0, dialogH);
  59. }
  60. else if (uiMsg == WM_NOTIFY)
  61. {
  62. LPOFNOTIFY ofn = (LPOFNOTIFY) lParam;
  63. if (ofn->hdr.code == CDN_SELCHANGE)
  64. {
  65. FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) ofn->lpOFN->lCustData;
  66. if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (info->customComponent->getChildComponent(0)))
  67. {
  68. WCHAR path [MAX_PATH * 2] = { 0 };
  69. if (CommDlg_OpenSave_GetFilePath (GetParent (hdlg), (LPARAM) &path, MAX_PATH) < 0)
  70. return 0;
  71. comp->selectedFileChanged (File (path));
  72. }
  73. }
  74. }
  75. return 0;
  76. }
  77. class CustomComponentHolder : public Component
  78. {
  79. public:
  80. CustomComponentHolder (Component* const customComp)
  81. {
  82. setVisible (true);
  83. setOpaque (true);
  84. addAndMakeVisible (customComp);
  85. setSize (jlimit (20, 800, customComp->getWidth()), customComp->getHeight());
  86. }
  87. void paint (Graphics& g) override
  88. {
  89. g.fillAll (Colours::lightgrey);
  90. }
  91. void resized() override
  92. {
  93. if (Component* const c = getChildComponent(0))
  94. c->setBounds (getLocalBounds());
  95. }
  96. private:
  97. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
  98. };
  99. }
  100. //==============================================================================
  101. bool FileChooser::isPlatformDialogAvailable()
  102. {
  103. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  104. return false;
  105. #else
  106. return true;
  107. #endif
  108. }
  109. void FileChooser::showPlatformDialog (Array<File>& results, const String& title_, const File& currentFileOrDirectory,
  110. const String& filter, bool selectsDirectory, bool /*selectsFiles*/,
  111. bool isSaveDialogue, bool warnAboutOverwritingExistingFiles,
  112. bool selectMultipleFiles, bool /*treatFilePackagesAsDirs*/,
  113. FilePreviewComponent* extraInfoComponent)
  114. {
  115. using namespace FileChooserHelpers;
  116. const String title (title_);
  117. String defaultExtension; // scope of these strings must extend beyond dialog's lifetime.
  118. HeapBlock<WCHAR> files;
  119. const size_t charsAvailableForResult = 32768;
  120. files.calloc (charsAvailableForResult + 1);
  121. int filenameOffset = 0;
  122. FileChooserCallbackInfo info;
  123. // use a modal window as the parent for this dialog box
  124. // to block input from other app windows
  125. Component parentWindow;
  126. const Rectangle<int> mainMon (Desktop::getInstance().getDisplays().getMainDisplay().userArea);
  127. parentWindow.setBounds (mainMon.getX() + mainMon.getWidth() / 4,
  128. mainMon.getY() + mainMon.getHeight() / 4,
  129. 0, 0);
  130. parentWindow.setOpaque (true);
  131. parentWindow.setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  132. parentWindow.addToDesktop (0);
  133. if (extraInfoComponent == nullptr)
  134. parentWindow.enterModalState();
  135. auto parentDirectory = currentFileOrDirectory.getParentDirectory();
  136. // Handle nonexistent root directories in the same way as existing ones
  137. if (currentFileOrDirectory.isDirectory() || currentFileOrDirectory.isRoot())
  138. {
  139. info.initialPath = currentFileOrDirectory.getFullPathName();
  140. }
  141. else
  142. {
  143. currentFileOrDirectory.getFileName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR));
  144. info.initialPath = parentDirectory.getFullPathName();
  145. }
  146. if (selectsDirectory)
  147. {
  148. BROWSEINFO bi = { 0 };
  149. bi.hwndOwner = (HWND) parentWindow.getWindowHandle();
  150. bi.pszDisplayName = files;
  151. bi.lpszTitle = title.toWideCharPointer();
  152. bi.lParam = (LPARAM) &info;
  153. bi.lpfn = browseCallbackProc;
  154. #ifdef BIF_USENEWUI
  155. bi.ulFlags = BIF_USENEWUI | BIF_VALIDATE;
  156. #else
  157. bi.ulFlags = 0x50;
  158. #endif
  159. LPITEMIDLIST list = SHBrowseForFolder (&bi);
  160. if (! SHGetPathFromIDListW (list, files))
  161. {
  162. files[0] = 0;
  163. info.returnedString.clear();
  164. }
  165. LPMALLOC al;
  166. if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
  167. al->Free (list);
  168. if (info.returnedString.isNotEmpty())
  169. {
  170. results.add (File (String (files.get())).getSiblingFile (info.returnedString));
  171. return;
  172. }
  173. }
  174. else
  175. {
  176. DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;
  177. if (warnAboutOverwritingExistingFiles)
  178. flags |= OFN_OVERWRITEPROMPT;
  179. if (selectMultipleFiles)
  180. flags |= OFN_ALLOWMULTISELECT;
  181. if (extraInfoComponent != nullptr)
  182. {
  183. flags |= OFN_ENABLEHOOK;
  184. info.customComponent = new CustomComponentHolder (extraInfoComponent);
  185. info.customComponent->enterModalState (false);
  186. }
  187. const size_t filterSpaceNumChars = 2048;
  188. HeapBlock<WCHAR> filters;
  189. filters.calloc (filterSpaceNumChars);
  190. const size_t bytesWritten = filter.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
  191. filter.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
  192. ((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));
  193. for (size_t i = 0; i < filterSpaceNumChars; ++i)
  194. if (filters[i] == '|')
  195. filters[i] = 0;
  196. OPENFILENAMEW of = { 0 };
  197. String localPath (info.initialPath);
  198. #ifdef OPENFILENAME_SIZE_VERSION_400W
  199. of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
  200. #else
  201. of.lStructSize = sizeof (of);
  202. #endif
  203. of.hwndOwner = (HWND) parentWindow.getWindowHandle();
  204. of.lpstrFilter = filters.getData();
  205. of.nFilterIndex = 1;
  206. of.lpstrFile = files;
  207. of.nMaxFile = (DWORD) charsAvailableForResult;
  208. of.lpstrInitialDir = localPath.toWideCharPointer();
  209. of.lpstrTitle = title.toWideCharPointer();
  210. of.Flags = flags;
  211. of.lCustData = (LPARAM) &info;
  212. if (extraInfoComponent != nullptr)
  213. of.lpfnHook = &openCallback;
  214. if (isSaveDialogue)
  215. {
  216. StringArray tokens;
  217. tokens.addTokens (filter, ";,", "\"'");
  218. tokens.trim();
  219. tokens.removeEmptyStrings();
  220. if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
  221. {
  222. defaultExtension = tokens[0].fromFirstOccurrenceOf (".", false, false);
  223. of.lpstrDefExt = defaultExtension.toWideCharPointer();
  224. }
  225. if (! GetSaveFileName (&of))
  226. return;
  227. }
  228. else
  229. {
  230. if (! GetOpenFileName (&of))
  231. return;
  232. }
  233. filenameOffset = of.nFileOffset;
  234. }
  235. if (selectMultipleFiles && filenameOffset > 0 && files [filenameOffset - 1] == 0)
  236. {
  237. const WCHAR* filename = files + filenameOffset;
  238. while (*filename != 0)
  239. {
  240. results.add (File (String (files.get())).getChildFile (String (filename)));
  241. filename += wcslen (filename) + 1;
  242. }
  243. }
  244. else if (files[0] != 0)
  245. {
  246. results.add (File (String (files.get())));
  247. }
  248. }
  249. } // namespace juce