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.

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