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.

352 lines
11KB

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