Audio plugin host https://kx.studio/carla
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.

341 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. class FileChooser::Native : private Component,
  21. public FileChooser::Pimpl
  22. {
  23. public:
  24. Native (FileChooser& fileChooser, int flags)
  25. : owner (fileChooser)
  26. {
  27. String firstFileExtension;
  28. static FileChooserDelegateClass cls;
  29. delegate.reset ([cls.createInstance() init]);
  30. FileChooserDelegateClass::setOwner (delegate.get(), this);
  31. auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
  32. if ((flags & FileBrowserComponent::saveMode) != 0)
  33. {
  34. auto currentFileOrDirectory = owner.startingFile;
  35. UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
  36. ? UIDocumentPickerModeExportToService
  37. : UIDocumentPickerModeMoveToService;
  38. if (! currentFileOrDirectory.existsAsFile())
  39. {
  40. auto filename = getFilename (currentFileOrDirectory, firstFileExtension);
  41. auto tmpDirectory = File::createTempFile ("JUCE-filepath");
  42. if (tmpDirectory.createDirectory().wasOk())
  43. {
  44. currentFileOrDirectory = tmpDirectory.getChildFile (filename);
  45. currentFileOrDirectory.replaceWithText ("");
  46. }
  47. else
  48. {
  49. // Temporary directory creation failed! You need to specify a
  50. // path you have write access to. Saving will not work for
  51. // current path.
  52. jassertfalse;
  53. }
  54. }
  55. auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
  56. controller.reset ([[UIDocumentPickerViewController alloc] initWithURL: url
  57. inMode: pickerMode]);
  58. [url release];
  59. }
  60. else
  61. {
  62. controller.reset ([[UIDocumentPickerViewController alloc] initWithDocumentTypes: utTypeArray
  63. inMode: UIDocumentPickerModeOpen]);
  64. }
  65. [controller.get() setDelegate: delegate.get()];
  66. [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
  67. setOpaque (false);
  68. if (SystemStats::isRunningInAppExtensionSandbox())
  69. {
  70. if (fileChooser.parent != nullptr)
  71. {
  72. [controller.get() setModalPresentationStyle:UIModalPresentationFullScreen];
  73. auto chooserBounds = fileChooser.parent->getBounds();
  74. setBounds (chooserBounds);
  75. setAlwaysOnTop (true);
  76. fileChooser.parent->addAndMakeVisible (this);
  77. }
  78. else
  79. {
  80. // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a
  81. // parent component (for example your editor) to parent the native file chooser window. To do this
  82. // specify a parent component in the FileChooser's constructor!
  83. jassert (fileChooser.parent != nullptr);
  84. }
  85. }
  86. else
  87. {
  88. auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  89. setBounds (chooserBounds);
  90. setAlwaysOnTop (true);
  91. addToDesktop (0);
  92. }
  93. }
  94. ~Native() override
  95. {
  96. exitModalState (0);
  97. }
  98. void launch() override
  99. {
  100. enterModalState (true, nullptr, true);
  101. }
  102. void runModally() override
  103. {
  104. #if JUCE_MODAL_LOOPS_PERMITTED
  105. runModalLoop();
  106. #endif
  107. }
  108. private:
  109. //==============================================================================
  110. void parentHierarchyChanged() override
  111. {
  112. auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
  113. if (peer != newPeer)
  114. {
  115. peer = newPeer;
  116. if (auto* parentController = peer->controller)
  117. [parentController showViewController: controller.get() sender: parentController];
  118. }
  119. }
  120. //==============================================================================
  121. static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
  122. {
  123. auto filters = StringArray::fromTokens (filterWildcards, ";", "");
  124. StringArray result;
  125. firstExtension = {};
  126. if (! filters.contains ("*") && filters.size() > 0)
  127. {
  128. for (auto filter : filters)
  129. {
  130. if (filter.isEmpty())
  131. continue;
  132. // iOS only supports file extension wild cards
  133. jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
  134. auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
  135. auto fileExtensionCF = fileExtension.toCFString();
  136. if (firstExtension.isEmpty())
  137. firstExtension = fileExtension;
  138. auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
  139. if (tag != nullptr)
  140. {
  141. result.add (String::fromCFString (tag));
  142. CFRelease (tag);
  143. }
  144. CFRelease (fileExtensionCF);
  145. }
  146. }
  147. else
  148. result.add ("public.data");
  149. return result;
  150. }
  151. static String getFilename (const File& path, const String& fallbackExtension)
  152. {
  153. auto filename = path.getFileNameWithoutExtension();
  154. auto extension = path.getFileExtension().substring (1);
  155. if (filename.isEmpty())
  156. filename = "Untitled";
  157. if (extension.isEmpty())
  158. extension = fallbackExtension;
  159. if (extension.isNotEmpty())
  160. filename += "." + extension;
  161. return filename;
  162. }
  163. //==============================================================================
  164. void didPickDocumentAtURL (NSURL* url)
  165. {
  166. bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
  167. | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
  168. NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
  169. auto* fileAccessIntent = isWriting
  170. ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
  171. : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
  172. NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
  173. auto fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
  174. [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
  175. {
  176. Array<URL> chooserResults;
  177. if (err == nil)
  178. {
  179. [url startAccessingSecurityScopedResource];
  180. NSError* error = nil;
  181. NSData* bookmark = [url bookmarkDataWithOptions: 0
  182. includingResourceValuesForKeys: nil
  183. relativeToURL: nil
  184. error: &error];
  185. [bookmark retain];
  186. [url stopAccessingSecurityScopedResource];
  187. URL juceUrl (nsStringToJuce ([url absoluteString]));
  188. if (error == nil)
  189. {
  190. setURLBookmark (juceUrl, (void*) bookmark);
  191. }
  192. else
  193. {
  194. auto desc = [error localizedDescription];
  195. ignoreUnused (desc);
  196. jassertfalse;
  197. }
  198. chooserResults.add (juceUrl);
  199. }
  200. else
  201. {
  202. auto desc = [err localizedDescription];
  203. ignoreUnused (desc);
  204. jassertfalse;
  205. }
  206. owner.finished (chooserResults);
  207. }];
  208. }
  209. void pickerWasCancelled()
  210. {
  211. Array<URL> chooserResults;
  212. owner.finished (chooserResults);
  213. exitModalState (0);
  214. }
  215. //==============================================================================
  216. struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
  217. {
  218. FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
  219. {
  220. addIvar<Native*> ("owner");
  221. addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
  222. addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
  223. addProtocol (@protocol (UIDocumentPickerDelegate));
  224. registerClass();
  225. }
  226. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  227. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  228. //==============================================================================
  229. static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
  230. {
  231. auto picker = getOwner (self);
  232. if (picker != nullptr)
  233. picker->didPickDocumentAtURL (url);
  234. }
  235. static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
  236. {
  237. auto picker = getOwner (self);
  238. if (picker != nullptr)
  239. picker->pickerWasCancelled();
  240. }
  241. };
  242. //==============================================================================
  243. FileChooser& owner;
  244. std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate;
  245. std::unique_ptr<UIDocumentPickerViewController, NSObjectDeleter> controller;
  246. UIViewComponentPeer* peer = nullptr;
  247. static FileChooserDelegateClass fileChooserDelegateClass;
  248. //==============================================================================
  249. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  250. };
  251. //==============================================================================
  252. bool FileChooser::isPlatformDialogAvailable()
  253. {
  254. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  255. return false;
  256. #else
  257. return true;
  258. #endif
  259. }
  260. FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  261. FilePreviewComponent*)
  262. {
  263. return new FileChooser::Native (owner, flags);
  264. }
  265. } // namespace juce