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.

333 lines
11KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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. //==============================================================================
  21. static NSMutableArray* createAllowedTypesArray (const StringArray& filters)
  22. {
  23. if (filters.size() == 0)
  24. return nil;
  25. NSMutableArray* filterArray = [[[NSMutableArray alloc] init] autorelease];
  26. for (int i = 0; i < filters.size(); ++i)
  27. {
  28. // From OS X 10.6 you can only specify allowed extensions, so any filters containing wildcards
  29. // must be of the form "*.extension"
  30. jassert (filters[i] == "*"
  31. || (filters[i].startsWith ("*.") && filters[i].lastIndexOfChar ('*') == 0));
  32. const String f (filters[i].replace ("*.", ""));
  33. if (f == "*")
  34. return nil;
  35. [filterArray addObject: juceStringToNS (f)];
  36. }
  37. return filterArray;
  38. }
  39. //==============================================================================
  40. class FileChooser::Native : public Component,
  41. public FileChooser::Pimpl
  42. {
  43. public:
  44. Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComponent)
  45. : owner (fileChooser), preview (previewComponent),
  46. selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
  47. selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
  48. isSave ((flags & FileBrowserComponent::saveMode) != 0),
  49. selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
  50. panel (isSave ? [[NSSavePanel alloc] init] : [[NSOpenPanel alloc] init])
  51. {
  52. setBounds (0, 0, 0, 0);
  53. setOpaque (true);
  54. static DelegateClass cls;
  55. delegate = [cls.createInstance() init];
  56. object_setInstanceVariable (delegate, "cppObject", this);
  57. [panel setDelegate: delegate];
  58. filters.addTokens (owner.filters.replaceCharacters (",:", ";;"), ";", String());
  59. filters.trim();
  60. filters.removeEmptyStrings();
  61. [panel setTitle: juceStringToNS (owner.title)];
  62. [panel setAllowedFileTypes: createAllowedTypesArray (filters)];
  63. if (! isSave)
  64. {
  65. NSOpenPanel* openPanel = (NSOpenPanel*) panel;
  66. [openPanel setCanChooseDirectories: selectsDirectories];
  67. [openPanel setCanChooseFiles: selectsFiles];
  68. [openPanel setAllowsMultipleSelection: selectMultiple];
  69. [openPanel setResolvesAliases: YES];
  70. if (owner.treatFilePackagesAsDirs)
  71. [openPanel setTreatsFilePackagesAsDirectories: YES];
  72. }
  73. if (preview != nullptr)
  74. {
  75. nsViewPreview = [[NSView alloc] initWithFrame: makeNSRect (preview->getLocalBounds())];
  76. preview->addToDesktop (0, (void*) nsViewPreview);
  77. preview->setVisible (true);
  78. [panel setAccessoryView: nsViewPreview];
  79. }
  80. if (isSave || selectsDirectories)
  81. [panel setCanCreateDirectories: YES];
  82. [panel setLevel:NSModalPanelWindowLevel];
  83. if (owner.startingFile.isDirectory())
  84. {
  85. startingDirectory = owner.startingFile.getFullPathName();
  86. }
  87. else
  88. {
  89. startingDirectory = owner.startingFile.getParentDirectory().getFullPathName();
  90. filename = owner.startingFile.getFileName();
  91. }
  92. [panel setDirectoryURL: createNSURLFromFile (startingDirectory)];
  93. [panel setNameFieldStringValue: juceStringToNS (filename)];
  94. }
  95. ~Native() override
  96. {
  97. exitModalState (0);
  98. removeFromDesktop();
  99. if (panel != nil)
  100. {
  101. [panel setDelegate: nil];
  102. if (nsViewPreview != nil)
  103. {
  104. [panel setAccessoryView: nil];
  105. [nsViewPreview release];
  106. nsViewPreview = nil;
  107. preview = nullptr;
  108. }
  109. [panel close];
  110. [panel release];
  111. }
  112. if (delegate != nil)
  113. {
  114. [delegate release];
  115. delegate = nil;
  116. }
  117. }
  118. void launch() override
  119. {
  120. if (panel != nil)
  121. {
  122. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  123. addToDesktop (0);
  124. enterModalState (true);
  125. [panel beginWithCompletionHandler:CreateObjCBlock (this, &Native::finished)];
  126. }
  127. }
  128. void runModally() override
  129. {
  130. std::unique_ptr<TemporaryMainMenuWithStandardCommands> tempMenu;
  131. if (JUCEApplicationBase::isStandaloneApp())
  132. tempMenu.reset (new TemporaryMainMenuWithStandardCommands());
  133. jassert (panel != nil);
  134. auto result = [panel runModal];
  135. finished (result);
  136. }
  137. bool canModalEventBeSentToComponent (const Component* targetComponent) override
  138. {
  139. if (targetComponent == nullptr)
  140. return false;
  141. return targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr;
  142. }
  143. private:
  144. //==============================================================================
  145. typedef NSObject<NSOpenSavePanelDelegate> DelegateType;
  146. void finished (NSInteger result)
  147. {
  148. Array<URL> chooserResults;
  149. exitModalState (0);
  150. if (panel != nil && result ==
  151. #if defined (MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
  152. NSModalResponseOK)
  153. #else
  154. NSFileHandlingPanelOKButton)
  155. #endif
  156. {
  157. auto addURLResult = [&chooserResults] (NSURL* urlToAdd)
  158. {
  159. auto scheme = nsStringToJuce ([urlToAdd scheme]);
  160. auto pathComponents = StringArray::fromTokens (nsStringToJuce ([urlToAdd path]), "/", {});
  161. for (auto& component : pathComponents)
  162. component = URL::addEscapeChars (component, false);
  163. chooserResults.add (URL (scheme + "://" + pathComponents.joinIntoString ("/")));
  164. };
  165. if (isSave)
  166. {
  167. addURLResult ([panel URL]);
  168. }
  169. else
  170. {
  171. auto* openPanel = (NSOpenPanel*) panel;
  172. auto urls = [openPanel URLs];
  173. for (unsigned int i = 0; i < [urls count]; ++i)
  174. addURLResult ([urls objectAtIndex: i]);
  175. }
  176. }
  177. owner.finished (chooserResults);
  178. }
  179. bool shouldShowFilename (const String& filenameToTest)
  180. {
  181. const File f (filenameToTest);
  182. auto nsFilename = juceStringToNS (filenameToTest);
  183. for (int i = filters.size(); --i >= 0;)
  184. if (f.getFileName().matchesWildcard (filters[i], true))
  185. return true;
  186. return f.isDirectory()
  187. && ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: nsFilename];
  188. }
  189. void panelSelectionDidChange (id sender)
  190. {
  191. // NB: would need to extend FilePreviewComponent to handle the full list rather than just the first one
  192. if (preview != nullptr)
  193. preview->selectedFileChanged (File (getSelectedPaths (sender)[0]));
  194. }
  195. static StringArray getSelectedPaths (id sender)
  196. {
  197. StringArray paths;
  198. if ([sender isKindOfClass: [NSOpenPanel class]])
  199. {
  200. NSArray* urls = [(NSOpenPanel*) sender URLs];
  201. for (NSUInteger i = 0; i < [urls count]; ++i)
  202. paths.add (nsStringToJuce ([[urls objectAtIndex: i] path]));
  203. }
  204. else if ([sender isKindOfClass: [NSSavePanel class]])
  205. {
  206. paths.add (nsStringToJuce ([[(NSSavePanel*) sender URL] path]));
  207. }
  208. return paths;
  209. }
  210. //==============================================================================
  211. FileChooser& owner;
  212. FilePreviewComponent* preview;
  213. NSView* nsViewPreview = nullptr;
  214. bool selectsDirectories, selectsFiles, isSave, selectMultiple;
  215. NSSavePanel* panel;
  216. DelegateType* delegate;
  217. StringArray filters;
  218. String startingDirectory, filename;
  219. //==============================================================================
  220. struct DelegateClass : ObjCClass<DelegateType>
  221. {
  222. DelegateClass() : ObjCClass <DelegateType> ("JUCEFileChooser_")
  223. {
  224. addIvar<Native*> ("cppObject");
  225. addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@");
  226. addMethod (@selector (panelSelectionDidChange:), panelSelectionDidChange, "c@");
  227. addProtocol (@protocol (NSOpenSavePanelDelegate));
  228. registerClass();
  229. }
  230. private:
  231. static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
  232. {
  233. auto* _this = getIvar<Native*> (self, "cppObject");
  234. return _this->shouldShowFilename (nsStringToJuce (filename)) ? YES : NO;
  235. }
  236. static void panelSelectionDidChange (id self, SEL, id sender)
  237. {
  238. auto* _this = getIvar<Native*> (self, "cppObject");
  239. _this->panelSelectionDidChange (sender);
  240. }
  241. };
  242. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  243. };
  244. FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  245. FilePreviewComponent* preview)
  246. {
  247. return new FileChooser::Native (owner, flags, preview);
  248. }
  249. bool FileChooser::isPlatformDialogAvailable()
  250. {
  251. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  252. return false;
  253. #else
  254. return true;
  255. #endif
  256. }
  257. }