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

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