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.

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