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.

299 lines
10KB

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