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.

404 lines
14KB

  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. #if ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0)
  21. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  22. #define JUCE_DEPRECATION_IGNORED 1
  23. #endif
  24. class FileChooser::Native : public FileChooser::Pimpl,
  25. public Component,
  26. private AsyncUpdater
  27. {
  28. public:
  29. Native (FileChooser& fileChooser, int flags)
  30. : owner (fileChooser)
  31. {
  32. static FileChooserDelegateClass delegateClass;
  33. delegate.reset ([delegateClass.createInstance() init]);
  34. FileChooserDelegateClass::setOwner (delegate.get(), this);
  35. static FileChooserControllerClass controllerClass;
  36. auto* controllerClassInstance = controllerClass.createInstance();
  37. String firstFileExtension;
  38. auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
  39. if ((flags & FileBrowserComponent::saveMode) != 0)
  40. {
  41. auto currentFileOrDirectory = owner.startingFile;
  42. UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
  43. ? UIDocumentPickerModeExportToService
  44. : UIDocumentPickerModeMoveToService;
  45. if (! currentFileOrDirectory.existsAsFile())
  46. {
  47. auto filename = getFilename (currentFileOrDirectory, firstFileExtension);
  48. auto tmpDirectory = File::createTempFile ("JUCE-filepath");
  49. if (tmpDirectory.createDirectory().wasOk())
  50. {
  51. currentFileOrDirectory = tmpDirectory.getChildFile (filename);
  52. currentFileOrDirectory.replaceWithText ("");
  53. }
  54. else
  55. {
  56. // Temporary directory creation failed! You need to specify a
  57. // path you have write access to. Saving will not work for
  58. // current path.
  59. jassertfalse;
  60. }
  61. }
  62. auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
  63. controller.reset ([controllerClassInstance initWithURL: url
  64. inMode: pickerMode]);
  65. [url release];
  66. }
  67. else
  68. {
  69. controller.reset ([controllerClassInstance initWithDocumentTypes: utTypeArray
  70. inMode: UIDocumentPickerModeOpen]);
  71. if (@available (iOS 11.0, *))
  72. [controller.get() setAllowsMultipleSelection: (flags & FileBrowserComponent::canSelectMultipleItems) != 0];
  73. }
  74. FileChooserControllerClass::setOwner (controller.get(), this);
  75. [controller.get() setDelegate: delegate.get()];
  76. [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
  77. setOpaque (false);
  78. if (fileChooser.parent != nullptr)
  79. {
  80. [controller.get() setModalPresentationStyle: UIModalPresentationFullScreen];
  81. auto chooserBounds = fileChooser.parent->getBounds();
  82. setBounds (chooserBounds);
  83. setAlwaysOnTop (true);
  84. fileChooser.parent->addAndMakeVisible (this);
  85. }
  86. else
  87. {
  88. if (SystemStats::isRunningInAppExtensionSandbox())
  89. {
  90. // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a
  91. // parent component (for example your editor) to parent the native file chooser window. To do this
  92. // specify a parent component in the FileChooser's constructor!
  93. jassertfalse;
  94. return;
  95. }
  96. auto chooserBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
  97. setBounds (chooserBounds);
  98. setAlwaysOnTop (true);
  99. setVisible (true);
  100. addToDesktop (0);
  101. }
  102. }
  103. ~Native() override
  104. {
  105. exitModalState (0);
  106. }
  107. void launch() override
  108. {
  109. enterModalState (true, nullptr, true);
  110. }
  111. void runModally() override
  112. {
  113. #if JUCE_MODAL_LOOPS_PERMITTED
  114. runModalLoop();
  115. #else
  116. jassertfalse;
  117. #endif
  118. }
  119. void parentHierarchyChanged() override
  120. {
  121. auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
  122. if (peer != newPeer)
  123. {
  124. peer = newPeer;
  125. if (peer != nullptr)
  126. {
  127. if (auto* parentController = peer->controller)
  128. [parentController showViewController: controller.get() sender: parentController];
  129. peer->toFront (false);
  130. }
  131. }
  132. }
  133. private:
  134. //==============================================================================
  135. void handleAsyncUpdate() override
  136. {
  137. pickerWasCancelled();
  138. }
  139. //==============================================================================
  140. static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
  141. {
  142. auto filters = StringArray::fromTokens (filterWildcards, ";", "");
  143. StringArray result;
  144. firstExtension = {};
  145. if (! filters.contains ("*") && filters.size() > 0)
  146. {
  147. for (auto filter : filters)
  148. {
  149. if (filter.isEmpty())
  150. continue;
  151. // iOS only supports file extension wild cards
  152. jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
  153. auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
  154. CFUniquePtr<CFStringRef> fileExtensionCF (fileExtension.toCFString());
  155. if (firstExtension.isEmpty())
  156. firstExtension = fileExtension;
  157. if (auto tag = CFUniquePtr<CFStringRef> (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr)))
  158. result.add (String::fromCFString (tag.get()));
  159. }
  160. }
  161. else
  162. {
  163. result.add ("public.data");
  164. }
  165. return result;
  166. }
  167. static String getFilename (const File& path, const String& fallbackExtension)
  168. {
  169. auto filename = path.getFileNameWithoutExtension();
  170. auto extension = path.getFileExtension().substring (1);
  171. if (filename.isEmpty())
  172. filename = "Untitled";
  173. if (extension.isEmpty())
  174. extension = fallbackExtension;
  175. if (extension.isNotEmpty())
  176. filename += "." + extension;
  177. return filename;
  178. }
  179. //==============================================================================
  180. void didPickDocumentsAtURLs (NSArray<NSURL*>* urls)
  181. {
  182. cancelPendingUpdate();
  183. const auto isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
  184. || controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
  185. const auto accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
  186. auto* fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
  187. auto* intents = [[[NSMutableArray alloc] init] autorelease];
  188. for (NSURL* url in urls)
  189. {
  190. auto* fileAccessIntent = isWriting
  191. ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
  192. : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
  193. [intents addObject: fileAccessIntent];
  194. }
  195. [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
  196. {
  197. if (err != nil)
  198. {
  199. auto desc = [err localizedDescription];
  200. ignoreUnused (desc);
  201. jassertfalse;
  202. return;
  203. }
  204. Array<URL> result;
  205. for (NSURL* url in urls)
  206. {
  207. [url startAccessingSecurityScopedResource];
  208. NSError* error = nil;
  209. auto* bookmark = [url bookmarkDataWithOptions: 0
  210. includingResourceValuesForKeys: nil
  211. relativeToURL: nil
  212. error: &error];
  213. [bookmark retain];
  214. [url stopAccessingSecurityScopedResource];
  215. URL juceUrl (nsStringToJuce ([url absoluteString]));
  216. if (error == nil)
  217. {
  218. setURLBookmark (juceUrl, (void*) bookmark);
  219. }
  220. else
  221. {
  222. auto desc = [error localizedDescription];
  223. ignoreUnused (desc);
  224. jassertfalse;
  225. }
  226. result.add (std::move (juceUrl));
  227. }
  228. owner.finished (std::move (result));
  229. }];
  230. }
  231. void didPickDocumentAtURL (NSURL* url)
  232. {
  233. didPickDocumentsAtURLs (@[url]);
  234. }
  235. void pickerWasCancelled()
  236. {
  237. cancelPendingUpdate();
  238. owner.finished ({});
  239. exitModalState (0);
  240. }
  241. //==============================================================================
  242. struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
  243. {
  244. FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
  245. {
  246. addIvar<Native*> ("owner");
  247. addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL);
  248. addMethod (@selector (documentPicker:didPickDocumentsAtURLs:), didPickDocumentsAtURLs);
  249. addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled);
  250. addProtocol (@protocol (UIDocumentPickerDelegate));
  251. registerClass();
  252. }
  253. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  254. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  255. //==============================================================================
  256. static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
  257. {
  258. if (auto* picker = getOwner (self))
  259. picker->didPickDocumentAtURL (url);
  260. }
  261. static void didPickDocumentsAtURLs (id self, SEL, UIDocumentPickerViewController*, NSArray<NSURL*>* urls)
  262. {
  263. if (auto* picker = getOwner (self))
  264. picker->didPickDocumentsAtURLs (urls);
  265. }
  266. static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
  267. {
  268. if (auto* picker = getOwner (self))
  269. picker->pickerWasCancelled();
  270. }
  271. };
  272. struct FileChooserControllerClass : public ObjCClass<UIDocumentPickerViewController>
  273. {
  274. FileChooserControllerClass() : ObjCClass<UIDocumentPickerViewController> ("FileChooserController_")
  275. {
  276. addIvar<Native*> ("owner");
  277. addMethod (@selector (viewDidDisappear:), viewDidDisappear);
  278. registerClass();
  279. }
  280. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  281. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  282. //==============================================================================
  283. static void viewDidDisappear (id self, SEL, BOOL animated)
  284. {
  285. sendSuperclassMessage<void> (self, @selector (viewDidDisappear:), animated);
  286. if (auto* picker = getOwner (self))
  287. picker->triggerAsyncUpdate();
  288. }
  289. };
  290. //==============================================================================
  291. FileChooser& owner;
  292. NSUniquePtr<NSObject<UIDocumentPickerDelegate>> delegate;
  293. NSUniquePtr<UIDocumentPickerViewController> controller;
  294. UIViewComponentPeer* peer = nullptr;
  295. static FileChooserDelegateClass fileChooserDelegateClass;
  296. static FileChooserControllerClass fileChooserControllerClass;
  297. //==============================================================================
  298. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  299. };
  300. //==============================================================================
  301. bool FileChooser::isPlatformDialogAvailable()
  302. {
  303. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  304. return false;
  305. #else
  306. return true;
  307. #endif
  308. }
  309. std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  310. FilePreviewComponent*)
  311. {
  312. return std::make_shared<FileChooser::Native> (owner, flags);
  313. }
  314. #if JUCE_DEPRECATION_IGNORED
  315. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  316. #endif
  317. } // namespace juce