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_mac_FileChooser.mm 10KB

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