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.

326 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. template <> struct ContainerDeletePolicy<UIDocumentPickerViewController> { static void destroy (NSObject* o) { [o release]; } };
  23. template <> struct ContainerDeletePolicy<NSObject<UIDocumentPickerDelegate>> { static void destroy (NSObject* o) { [o release]; } };
  24. class FileChooser::Native : private Component,
  25. public FileChooser::Pimpl
  26. {
  27. public:
  28. Native (FileChooser& fileChooser, int flags)
  29. : owner (fileChooser)
  30. {
  31. String firstFileExtension;
  32. static FileChooserDelegateClass cls;
  33. delegate = [cls.createInstance() init];
  34. FileChooserDelegateClass::setOwner (delegate, this);
  35. auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
  36. if ((flags & FileBrowserComponent::saveMode) != 0)
  37. {
  38. auto currentFileOrDirectory = owner.startingFile;
  39. UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
  40. ? UIDocumentPickerModeExportToService
  41. : UIDocumentPickerModeMoveToService;
  42. if (! currentFileOrDirectory.existsAsFile())
  43. {
  44. auto filename = getFilename (currentFileOrDirectory, firstFileExtension);
  45. auto tmpDirectory = File::createTempFile ("JUCE-filepath");
  46. if (tmpDirectory.createDirectory().wasOk())
  47. {
  48. currentFileOrDirectory = tmpDirectory.getChildFile (filename);
  49. currentFileOrDirectory.replaceWithText ("");
  50. }
  51. else
  52. {
  53. // Temporary directory creation failed! You need to specify a
  54. // path you have write access to. Saving will not work for
  55. // current path.
  56. jassertfalse;
  57. }
  58. }
  59. auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
  60. controller = [[UIDocumentPickerViewController alloc] initWithURL: url
  61. inMode: pickerMode];
  62. [url release];
  63. }
  64. else
  65. {
  66. controller = [[UIDocumentPickerViewController alloc] initWithDocumentTypes: utTypeArray
  67. inMode: UIDocumentPickerModeOpen];
  68. }
  69. [controller setDelegate: delegate];
  70. [controller setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
  71. setOpaque (false);
  72. auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  73. setBounds (chooserBounds);
  74. setAlwaysOnTop (true);
  75. addToDesktop (0);
  76. }
  77. ~Native()
  78. {
  79. exitModalState (0);
  80. }
  81. void launch() override
  82. {
  83. enterModalState (true, nullptr, true);
  84. }
  85. void runModally() override
  86. {
  87. #if JUCE_MODAL_LOOPS_PERMITTED
  88. runModalLoop();
  89. #endif
  90. }
  91. private:
  92. //==============================================================================
  93. void parentHierarchyChanged() override
  94. {
  95. auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
  96. if (peer != newPeer)
  97. {
  98. peer = newPeer;
  99. if (auto* parentController = peer->controller)
  100. [parentController showViewController: controller sender: parentController];
  101. if (peer->view.window != nil)
  102. peer->view.window.autoresizesSubviews = YES;
  103. }
  104. }
  105. //==============================================================================
  106. static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
  107. {
  108. auto filters = StringArray::fromTokens (filterWildcards, ";", "");
  109. StringArray result;
  110. firstExtension = {};
  111. if (! filters.contains ("*") && filters.size() > 0)
  112. {
  113. for (auto filter : filters)
  114. {
  115. if (filter.isEmpty())
  116. continue;
  117. // iOS only supports file extension wild cards
  118. jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
  119. auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
  120. auto fileExtensionCF = fileExtension.toCFString();
  121. if (firstExtension.isEmpty())
  122. firstExtension = fileExtension;
  123. auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
  124. if (tag != nullptr)
  125. {
  126. result.add (String::fromCFString (tag));
  127. CFRelease (tag);
  128. }
  129. CFRelease (fileExtensionCF);
  130. }
  131. }
  132. else
  133. result.add ("public.data");
  134. return result;
  135. }
  136. static String getFilename (const File& path, const String& fallbackExtension)
  137. {
  138. auto filename = path.getFileNameWithoutExtension();
  139. auto extension = path.getFileExtension().substring (1);
  140. if (filename.isEmpty())
  141. filename = "Untitled";
  142. if (extension.isEmpty())
  143. extension = fallbackExtension;
  144. if (extension.isNotEmpty())
  145. filename += "." + extension;
  146. return filename;
  147. }
  148. //==============================================================================
  149. void didPickDocumentAtURL (NSURL* url)
  150. {
  151. bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
  152. | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
  153. NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
  154. auto* fileAccessIntent = isWriting
  155. ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
  156. : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
  157. NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
  158. auto* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter: nil];
  159. [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
  160. {
  161. Array<URL> chooserResults;
  162. if (err == nil)
  163. {
  164. [url startAccessingSecurityScopedResource];
  165. NSError* error = nil;
  166. NSData* bookmark = [url bookmarkDataWithOptions: 0
  167. includingResourceValuesForKeys: nil
  168. relativeToURL: nil
  169. error: &error];
  170. [bookmark retain];
  171. [url stopAccessingSecurityScopedResource];
  172. URL juceUrl (nsStringToJuce ([url absoluteString]));
  173. if (error == nil)
  174. {
  175. setURLBookmark (juceUrl, (void*) bookmark);
  176. }
  177. else
  178. {
  179. auto* desc = [error localizedDescription];
  180. ignoreUnused (desc);
  181. jassertfalse;
  182. }
  183. chooserResults.add (juceUrl);
  184. }
  185. else
  186. {
  187. auto* desc = [err localizedDescription];
  188. ignoreUnused (desc);
  189. jassertfalse;
  190. }
  191. owner.finished (chooserResults);
  192. }];
  193. }
  194. void pickerWasCancelled()
  195. {
  196. Array<URL> chooserResults;
  197. owner.finished (chooserResults);
  198. exitModalState (0);
  199. }
  200. //==============================================================================
  201. struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
  202. {
  203. FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
  204. {
  205. addIvar<Native*> ("owner");
  206. addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
  207. addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
  208. addProtocol (@protocol (UIDocumentPickerDelegate));
  209. registerClass();
  210. }
  211. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  212. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  213. //==============================================================================
  214. static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
  215. {
  216. auto picker = getOwner (self);
  217. if (picker != nullptr)
  218. picker->didPickDocumentAtURL (url);
  219. }
  220. static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
  221. {
  222. auto picker = getOwner (self);
  223. if (picker != nullptr)
  224. picker->pickerWasCancelled();
  225. }
  226. };
  227. //==============================================================================
  228. FileChooser& owner;
  229. ScopedPointer<NSObject<UIDocumentPickerDelegate>> delegate;
  230. ScopedPointer<UIDocumentPickerViewController> controller;
  231. UIViewComponentPeer* peer = nullptr;
  232. static FileChooserDelegateClass fileChooserDelegateClass;
  233. //==============================================================================
  234. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  235. };
  236. //==============================================================================
  237. bool FileChooser::isPlatformDialogAvailable()
  238. {
  239. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  240. return false;
  241. #else
  242. return [[NSFileManager defaultManager] ubiquityIdentityToken] != nil;
  243. #endif
  244. }
  245. FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  246. FilePreviewComponent*)
  247. {
  248. return new FileChooser::Native (owner, flags);
  249. }
  250. } // namespace juce