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.

286 lines
9.9KB

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