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.

395 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_15_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_15_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. }
  72. FileChooserControllerClass::setOwner (controller.get(), this);
  73. [controller.get() setDelegate: delegate.get()];
  74. [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
  75. setOpaque (false);
  76. if (fileChooser.parent != nullptr)
  77. {
  78. [controller.get() setModalPresentationStyle: UIModalPresentationFullScreen];
  79. auto chooserBounds = fileChooser.parent->getBounds();
  80. setBounds (chooserBounds);
  81. setAlwaysOnTop (true);
  82. fileChooser.parent->addAndMakeVisible (this);
  83. }
  84. else
  85. {
  86. if (SystemStats::isRunningInAppExtensionSandbox())
  87. {
  88. // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a
  89. // parent component (for example your editor) to parent the native file chooser window. To do this
  90. // specify a parent component in the FileChooser's constructor!
  91. jassertfalse;
  92. return;
  93. }
  94. auto chooserBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
  95. setBounds (chooserBounds);
  96. setAlwaysOnTop (true);
  97. setVisible (true);
  98. addToDesktop (0);
  99. }
  100. }
  101. ~Native() override
  102. {
  103. exitModalState (0);
  104. }
  105. void launch() override
  106. {
  107. enterModalState (true, nullptr, true);
  108. }
  109. void runModally() override
  110. {
  111. #if JUCE_MODAL_LOOPS_PERMITTED
  112. runModalLoop();
  113. #else
  114. jassertfalse;
  115. #endif
  116. }
  117. void parentHierarchyChanged() override
  118. {
  119. auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
  120. if (peer != newPeer)
  121. {
  122. peer = newPeer;
  123. if (peer != nullptr)
  124. {
  125. if (auto* parentController = peer->controller)
  126. [parentController showViewController: controller.get() sender: parentController];
  127. peer->toFront (false);
  128. }
  129. }
  130. }
  131. private:
  132. //==============================================================================
  133. void handleAsyncUpdate() override
  134. {
  135. pickerWasCancelled();
  136. }
  137. //==============================================================================
  138. static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
  139. {
  140. auto filters = StringArray::fromTokens (filterWildcards, ";", "");
  141. StringArray result;
  142. firstExtension = {};
  143. if (! filters.contains ("*") && filters.size() > 0)
  144. {
  145. for (auto filter : filters)
  146. {
  147. if (filter.isEmpty())
  148. continue;
  149. // iOS only supports file extension wild cards
  150. jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
  151. auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
  152. auto fileExtensionCF = fileExtension.toCFString();
  153. if (firstExtension.isEmpty())
  154. firstExtension = fileExtension;
  155. auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
  156. if (tag != nullptr)
  157. {
  158. result.add (String::fromCFString (tag));
  159. CFRelease (tag);
  160. }
  161. CFRelease (fileExtensionCF);
  162. }
  163. }
  164. else
  165. {
  166. result.add ("public.data");
  167. }
  168. return result;
  169. }
  170. static String getFilename (const File& path, const String& fallbackExtension)
  171. {
  172. auto filename = path.getFileNameWithoutExtension();
  173. auto extension = path.getFileExtension().substring (1);
  174. if (filename.isEmpty())
  175. filename = "Untitled";
  176. if (extension.isEmpty())
  177. extension = fallbackExtension;
  178. if (extension.isNotEmpty())
  179. filename += "." + extension;
  180. return filename;
  181. }
  182. //==============================================================================
  183. void didPickDocumentAtURL (NSURL* url)
  184. {
  185. cancelPendingUpdate();
  186. bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
  187. | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
  188. NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
  189. auto* fileAccessIntent = isWriting
  190. ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
  191. : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
  192. NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
  193. auto fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
  194. [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
  195. {
  196. Array<URL> chooserResults;
  197. if (err == nil)
  198. {
  199. [url startAccessingSecurityScopedResource];
  200. NSError* error = nil;
  201. NSData* bookmark = [url bookmarkDataWithOptions: 0
  202. includingResourceValuesForKeys: nil
  203. relativeToURL: nil
  204. error: &error];
  205. [bookmark retain];
  206. [url stopAccessingSecurityScopedResource];
  207. URL juceUrl (nsStringToJuce ([url absoluteString]));
  208. if (error == nil)
  209. {
  210. setURLBookmark (juceUrl, (void*) bookmark);
  211. }
  212. else
  213. {
  214. auto desc = [error localizedDescription];
  215. ignoreUnused (desc);
  216. jassertfalse;
  217. }
  218. chooserResults.add (juceUrl);
  219. }
  220. else
  221. {
  222. auto desc = [err localizedDescription];
  223. ignoreUnused (desc);
  224. jassertfalse;
  225. }
  226. owner.finished (chooserResults);
  227. }];
  228. }
  229. void pickerWasCancelled()
  230. {
  231. cancelPendingUpdate();
  232. owner.finished ({});
  233. exitModalState (0);
  234. }
  235. //==============================================================================
  236. struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
  237. {
  238. FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
  239. {
  240. addIvar<Native*> ("owner");
  241. addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
  242. addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
  243. addProtocol (@protocol (UIDocumentPickerDelegate));
  244. registerClass();
  245. }
  246. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  247. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  248. //==============================================================================
  249. static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
  250. {
  251. if (auto* picker = getOwner (self))
  252. picker->didPickDocumentAtURL (url);
  253. }
  254. static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
  255. {
  256. if (auto* picker = getOwner (self))
  257. picker->pickerWasCancelled();
  258. }
  259. };
  260. struct FileChooserControllerClass : public ObjCClass<UIDocumentPickerViewController>
  261. {
  262. FileChooserControllerClass() : ObjCClass<UIDocumentPickerViewController> ("FileChooserController_")
  263. {
  264. addIvar<Native*> ("owner");
  265. addMethod (@selector (viewDidDisappear:), viewDidDisappear, "v@:@c");
  266. registerClass();
  267. }
  268. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  269. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  270. //==============================================================================
  271. static void viewDidDisappear (id self, SEL, BOOL animated)
  272. {
  273. sendSuperclassMessage<void> (self, @selector (viewDidDisappear:), animated);
  274. if (auto* picker = getOwner (self))
  275. picker->triggerAsyncUpdate();
  276. }
  277. };
  278. //==============================================================================
  279. FileChooser& owner;
  280. std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate;
  281. std::unique_ptr<UIDocumentPickerViewController, NSObjectDeleter> controller;
  282. UIViewComponentPeer* peer = nullptr;
  283. static FileChooserDelegateClass fileChooserDelegateClass;
  284. static FileChooserControllerClass fileChooserControllerClass;
  285. //==============================================================================
  286. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  287. };
  288. //==============================================================================
  289. bool FileChooser::isPlatformDialogAvailable()
  290. {
  291. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  292. return false;
  293. #else
  294. return true;
  295. #endif
  296. }
  297. std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  298. FilePreviewComponent*)
  299. {
  300. return std::make_shared<FileChooser::Native> (owner, flags);
  301. }
  302. #if JUCE_DEPRECATION_IGNORED
  303. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  304. #endif
  305. #undef JUCE_DEPRECATION_IGNORED
  306. } // namespace juce