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.

338 lines
12KB

  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. if (SystemStats::isRunningInAppExtensionSandbox())
  70. {
  71. [controller.get() setModalPresentationStyle:UIModalPresentationFullScreen];
  72. if (auto* editorPeer = ComponentPeer::getPeer (0))
  73. {
  74. auto chooserBounds = editorPeer->getComponent().getLocalBounds();
  75. setBounds (chooserBounds);
  76. setAlwaysOnTop (true);
  77. editorPeer->getComponent().addAndMakeVisible (this);
  78. }
  79. }
  80. else
  81. {
  82. auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
  83. setBounds (chooserBounds);
  84. setAlwaysOnTop (true);
  85. addToDesktop (0);
  86. }
  87. }
  88. ~Native()
  89. {
  90. exitModalState (0);
  91. }
  92. void launch() override
  93. {
  94. enterModalState (true, nullptr, true);
  95. }
  96. void runModally() override
  97. {
  98. #if JUCE_MODAL_LOOPS_PERMITTED
  99. runModalLoop();
  100. #endif
  101. }
  102. private:
  103. //==============================================================================
  104. void parentHierarchyChanged() override
  105. {
  106. auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
  107. if (peer != newPeer)
  108. {
  109. peer = newPeer;
  110. if (auto* parentController = peer->controller)
  111. [parentController showViewController: controller.get() sender: parentController];
  112. if (peer->view.window != nil)
  113. peer->view.window.autoresizesSubviews = YES;
  114. }
  115. }
  116. //==============================================================================
  117. static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
  118. {
  119. auto filters = StringArray::fromTokens (filterWildcards, ";", "");
  120. StringArray result;
  121. firstExtension = {};
  122. if (! filters.contains ("*") && filters.size() > 0)
  123. {
  124. for (auto filter : filters)
  125. {
  126. if (filter.isEmpty())
  127. continue;
  128. // iOS only supports file extension wild cards
  129. jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
  130. auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
  131. auto fileExtensionCF = fileExtension.toCFString();
  132. if (firstExtension.isEmpty())
  133. firstExtension = fileExtension;
  134. auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
  135. if (tag != nullptr)
  136. {
  137. result.add (String::fromCFString (tag));
  138. CFRelease (tag);
  139. }
  140. CFRelease (fileExtensionCF);
  141. }
  142. }
  143. else
  144. result.add ("public.data");
  145. return result;
  146. }
  147. static String getFilename (const File& path, const String& fallbackExtension)
  148. {
  149. auto filename = path.getFileNameWithoutExtension();
  150. auto extension = path.getFileExtension().substring (1);
  151. if (filename.isEmpty())
  152. filename = "Untitled";
  153. if (extension.isEmpty())
  154. extension = fallbackExtension;
  155. if (extension.isNotEmpty())
  156. filename += "." + extension;
  157. return filename;
  158. }
  159. //==============================================================================
  160. void didPickDocumentAtURL (NSURL* url)
  161. {
  162. bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
  163. | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
  164. NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
  165. auto* fileAccessIntent = isWriting
  166. ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
  167. : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
  168. NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
  169. auto* fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter: nil];
  170. [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
  171. {
  172. Array<URL> chooserResults;
  173. if (err == nil)
  174. {
  175. [url startAccessingSecurityScopedResource];
  176. NSError* error = nil;
  177. NSData* bookmark = [url bookmarkDataWithOptions: 0
  178. includingResourceValuesForKeys: nil
  179. relativeToURL: nil
  180. error: &error];
  181. [bookmark retain];
  182. [url stopAccessingSecurityScopedResource];
  183. URL juceUrl (nsStringToJuce ([url absoluteString]));
  184. if (error == nil)
  185. {
  186. setURLBookmark (juceUrl, (void*) bookmark);
  187. }
  188. else
  189. {
  190. auto* desc = [error localizedDescription];
  191. ignoreUnused (desc);
  192. jassertfalse;
  193. }
  194. chooserResults.add (juceUrl);
  195. }
  196. else
  197. {
  198. auto* desc = [err localizedDescription];
  199. ignoreUnused (desc);
  200. jassertfalse;
  201. }
  202. owner.finished (chooserResults);
  203. }];
  204. }
  205. void pickerWasCancelled()
  206. {
  207. Array<URL> chooserResults;
  208. owner.finished (chooserResults);
  209. exitModalState (0);
  210. }
  211. //==============================================================================
  212. struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
  213. {
  214. FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
  215. {
  216. addIvar<Native*> ("owner");
  217. addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
  218. addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
  219. addProtocol (@protocol (UIDocumentPickerDelegate));
  220. registerClass();
  221. }
  222. static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
  223. static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
  224. //==============================================================================
  225. static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
  226. {
  227. auto picker = getOwner (self);
  228. if (picker != nullptr)
  229. picker->didPickDocumentAtURL (url);
  230. }
  231. static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
  232. {
  233. auto picker = getOwner (self);
  234. if (picker != nullptr)
  235. picker->pickerWasCancelled();
  236. }
  237. };
  238. //==============================================================================
  239. FileChooser& owner;
  240. std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate;
  241. std::unique_ptr<UIDocumentPickerViewController, NSObjectDeleter> controller;
  242. UIViewComponentPeer* peer = nullptr;
  243. static FileChooserDelegateClass fileChooserDelegateClass;
  244. //==============================================================================
  245. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
  246. };
  247. //==============================================================================
  248. bool FileChooser::isPlatformDialogAvailable()
  249. {
  250. #if JUCE_DISABLE_NATIVE_FILECHOOSERS
  251. return false;
  252. #else
  253. return true;
  254. #endif
  255. }
  256. FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
  257. FilePreviewComponent*)
  258. {
  259. return new FileChooser::Native (owner, flags);
  260. }
  261. } // namespace juce