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.

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