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.

334 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. // From OS X 10.6 you can only specify allowed extensions, so any filters containing wildcards
  30. // must be of the form "*.extension"
  31. jassert (filters[i] == "*"
  32. || (filters[i].startsWith ("*.") && filters[i].lastIndexOfChar ('*') == 0));
  33. const String f (filters[i].replace ("*.", ""));
  34. if (f == "*")
  35. return nil;
  36. [filterArray addObject: juceStringToNS (f)];
  37. }
  38. return filterArray;
  39. }
  40. //==============================================================================
  41. class FileChooser::Native : public Component,
  42. public FileChooser::Pimpl
  43. {
  44. public:
  45. Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComponent)
  46. : owner (fileChooser), preview (previewComponent),
  47. selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
  48. selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
  49. isSave ((flags & FileBrowserComponent::saveMode) != 0),
  50. selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0),
  51. panel (isSave ? [[NSSavePanel alloc] init] : [[NSOpenPanel alloc] init])
  52. {
  53. setBounds (0, 0, 0, 0);
  54. setOpaque (true);
  55. static DelegateClass cls;
  56. delegate = [cls.createInstance() init];
  57. object_setInstanceVariable (delegate, "cppObject", this);
  58. [panel setDelegate: delegate];
  59. filters.addTokens (owner.filters.replaceCharacters (",:", ";;"), ";", String());
  60. filters.trim();
  61. filters.removeEmptyStrings();
  62. [panel setTitle: juceStringToNS (owner.title)];
  63. [panel setAllowedFileTypes: createAllowedTypesArray (filters)];
  64. if (! isSave)
  65. {
  66. NSOpenPanel* openPanel = (NSOpenPanel*) panel;
  67. [openPanel setCanChooseDirectories: selectsDirectories];
  68. [openPanel setCanChooseFiles: selectsFiles];
  69. [openPanel setAllowsMultipleSelection: selectMultiple];
  70. [openPanel setResolvesAliases: YES];
  71. if (owner.treatFilePackagesAsDirs)
  72. [openPanel setTreatsFilePackagesAsDirectories: YES];
  73. }
  74. if (preview != nullptr)
  75. {
  76. nsViewPreview = [[NSView alloc] initWithFrame: makeNSRect (preview->getLocalBounds())];
  77. preview->addToDesktop (0, (void*) nsViewPreview);
  78. preview->setVisible (true);
  79. [panel setAccessoryView: nsViewPreview];
  80. }
  81. if (isSave || selectsDirectories)
  82. [panel setCanCreateDirectories: YES];
  83. [panel setLevel:NSModalPanelWindowLevel];
  84. if (owner.startingFile.isDirectory())
  85. {
  86. startingDirectory = owner.startingFile.getFullPathName();
  87. }
  88. else
  89. {
  90. startingDirectory = owner.startingFile.getParentDirectory().getFullPathName();
  91. filename = owner.startingFile.getFileName();
  92. }
  93. [panel setDirectoryURL: createNSURLFromFile (startingDirectory)];
  94. [panel setNameFieldStringValue: juceStringToNS (filename)];
  95. }
  96. ~Native() override
  97. {
  98. exitModalState (0);
  99. removeFromDesktop();
  100. if (panel != nil)
  101. {
  102. [panel setDelegate: nil];
  103. if (nsViewPreview != nil)
  104. {
  105. [panel setAccessoryView: nil];
  106. [nsViewPreview release];
  107. nsViewPreview = nil;
  108. preview = nullptr;
  109. }
  110. [panel close];
  111. [panel release];
  112. }
  113. if (delegate != nil)
  114. {
  115. [delegate release];
  116. delegate = nil;
  117. }
  118. }
  119. void launch() override
  120. {
  121. if (panel != nil)
  122. {
  123. setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
  124. addToDesktop (0);
  125. enterModalState (true);
  126. [panel beginWithCompletionHandler:CreateObjCBlock (this, &Native::finished)];
  127. }
  128. }
  129. void runModally() override
  130. {
  131. std::unique_ptr<TemporaryMainMenuWithStandardCommands> tempMenu;
  132. if (JUCEApplicationBase::isStandaloneApp())
  133. tempMenu.reset (new TemporaryMainMenuWithStandardCommands());
  134. jassert (panel != nil);
  135. auto result = [panel runModal];
  136. finished (result);
  137. }
  138. bool canModalEventBeSentToComponent (const Component* targetComponent) override
  139. {
  140. if (targetComponent == nullptr)
  141. return false;
  142. return targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr;
  143. }
  144. private:
  145. //==============================================================================
  146. typedef NSObject<NSOpenSavePanelDelegate> DelegateType;
  147. void finished (NSInteger result)
  148. {
  149. Array<URL> chooserResults;
  150. exitModalState (0);
  151. if (panel != nil && result ==
  152. #if defined (MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
  153. NSModalResponseOK)
  154. #else
  155. NSFileHandlingPanelOKButton)
  156. #endif
  157. {
  158. auto addURLResult = [&chooserResults] (NSURL* urlToAdd)
  159. {
  160. auto scheme = nsStringToJuce ([urlToAdd scheme]);
  161. auto pathComponents = StringArray::fromTokens (nsStringToJuce ([urlToAdd path]), "/", {});
  162. for (auto& component : pathComponents)
  163. component = URL::addEscapeChars (component, false);
  164. chooserResults.add (URL (scheme + "://" + pathComponents.joinIntoString ("/")));
  165. };
  166. if (isSave)
  167. {
  168. addURLResult ([panel URL]);
  169. }
  170. else
  171. {
  172. auto* openPanel = (NSOpenPanel*) panel;
  173. auto urls = [openPanel URLs];
  174. for (unsigned int i = 0; i < [urls count]; ++i)
  175. addURLResult ([urls objectAtIndex: i]);
  176. }
  177. }
  178. owner.finished (chooserResults);
  179. }
  180. bool shouldShowFilename (const String& filenameToTest)
  181. {
  182. const File f (filenameToTest);
  183. auto nsFilename = juceStringToNS (filenameToTest);
  184. for (int i = filters.size(); --i >= 0;)
  185. if (f.getFileName().matchesWildcard (filters[i], true))
  186. return true;
  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. 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. addProtocol (@protocol (NSOpenSavePanelDelegate));
  229. registerClass();
  230. }
  231. private:
  232. static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
  233. {
  234. auto* _this = getIvar<Native*> (self, "cppObject");
  235. return _this->shouldShowFilename (nsStringToJuce (filename)) ? YES : NO;
  236. }
  237. static void panelSelectionDidChange (id self, SEL, id sender)
  238. {
  239. auto* _this = getIvar<Native*> (self, "cppObject");
  240. _this->panelSelectionDidChange (sender);
  241. }
  242. };
  243. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  244. };
  245. FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  246. FilePreviewComponent* preview)
  247. {
  248. return new FileChooser::Native (owner, flags, preview);
  249. }
  250. bool FileChooser::isPlatformDialogAvailable()
  251. {
  252. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  253. return false;
  254. #else
  255. return true;
  256. #endif
  257. }
  258. }