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.

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