|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2020 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 6 End-User License
- Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
-
- End User License Agreement: www.juce.com/juce-6-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- class FileChooser::Native : public FileChooser::Pimpl,
- public Component,
- private AsyncUpdater
- {
- public:
- Native (FileChooser& fileChooser, int flags)
- : owner (fileChooser)
- {
- static FileChooserDelegateClass delegateClass;
- delegate.reset ([delegateClass.createInstance() init]);
- FileChooserDelegateClass::setOwner (delegate.get(), this);
-
- static FileChooserControllerClass controllerClass;
- auto* controllerClassInstance = controllerClass.createInstance();
-
- String firstFileExtension;
- auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
-
- if ((flags & FileBrowserComponent::saveMode) != 0)
- {
- auto currentFileOrDirectory = owner.startingFile;
-
- UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
- ? UIDocumentPickerModeExportToService
- : UIDocumentPickerModeMoveToService;
-
- if (! currentFileOrDirectory.existsAsFile())
- {
- auto filename = getFilename (currentFileOrDirectory, firstFileExtension);
- auto tmpDirectory = File::createTempFile ("JUCE-filepath");
-
- if (tmpDirectory.createDirectory().wasOk())
- {
- currentFileOrDirectory = tmpDirectory.getChildFile (filename);
- currentFileOrDirectory.replaceWithText ("");
- }
- else
- {
- // Temporary directory creation failed! You need to specify a
- // path you have write access to. Saving will not work for
- // current path.
- jassertfalse;
- }
- }
-
- auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
-
- controller.reset ([controllerClassInstance initWithURL: url
- inMode: pickerMode]);
-
- [url release];
- }
- else
- {
- controller.reset ([controllerClassInstance initWithDocumentTypes: utTypeArray
- inMode: UIDocumentPickerModeOpen]);
- }
-
- FileChooserControllerClass::setOwner (controller.get(), this);
-
- [controller.get() setDelegate: delegate.get()];
- [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
-
- setOpaque (false);
-
- if (fileChooser.parent != nullptr)
- {
- [controller.get() setModalPresentationStyle: UIModalPresentationFullScreen];
-
- auto chooserBounds = fileChooser.parent->getBounds();
- setBounds (chooserBounds);
-
- setAlwaysOnTop (true);
- fileChooser.parent->addAndMakeVisible (this);
- }
- else
- {
- if (SystemStats::isRunningInAppExtensionSandbox())
- {
- // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a
- // parent component (for example your editor) to parent the native file chooser window. To do this
- // specify a parent component in the FileChooser's constructor!
- jassertfalse;
- return;
- }
-
- auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
- setBounds (chooserBounds);
-
- setAlwaysOnTop (true);
- setVisible (true);
- addToDesktop (0);
- }
- }
-
- ~Native() override
- {
- exitModalState (0);
- }
-
- void launch() override
- {
- enterModalState (true, nullptr, true);
- }
-
- void runModally() override
- {
- #if JUCE_MODAL_LOOPS_PERMITTED
- runModalLoop();
- #endif
- }
-
- void parentHierarchyChanged() override
- {
- auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
-
- if (peer != newPeer)
- {
- peer = newPeer;
-
- if (peer != nullptr)
- {
- if (auto* parentController = peer->controller)
- [parentController showViewController: controller.get() sender: parentController];
-
- peer->toFront (false);
- }
- }
- }
-
- private:
- //==============================================================================
- void handleAsyncUpdate() override
- {
- pickerWasCancelled();
- }
-
- //==============================================================================
- static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
- {
- auto filters = StringArray::fromTokens (filterWildcards, ";", "");
- StringArray result;
-
- firstExtension = {};
-
- if (! filters.contains ("*") && filters.size() > 0)
- {
- for (auto filter : filters)
- {
- if (filter.isEmpty())
- continue;
-
- // iOS only supports file extension wild cards
- jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
-
- auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
- auto fileExtensionCF = fileExtension.toCFString();
-
- if (firstExtension.isEmpty())
- firstExtension = fileExtension;
-
- auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
-
- if (tag != nullptr)
- {
- result.add (String::fromCFString (tag));
- CFRelease (tag);
- }
-
- CFRelease (fileExtensionCF);
- }
- }
- else
- {
- result.add ("public.data");
- }
-
- return result;
- }
-
- static String getFilename (const File& path, const String& fallbackExtension)
- {
- auto filename = path.getFileNameWithoutExtension();
- auto extension = path.getFileExtension().substring (1);
-
- if (filename.isEmpty())
- filename = "Untitled";
-
- if (extension.isEmpty())
- extension = fallbackExtension;
-
- if (extension.isNotEmpty())
- filename += "." + extension;
-
- return filename;
- }
-
- //==============================================================================
- void didPickDocumentAtURL (NSURL* url)
- {
- cancelPendingUpdate();
-
- bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
- | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
-
- NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
-
- auto* fileAccessIntent = isWriting
- ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
- : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
-
- NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
-
- auto fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
-
- [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
- {
- Array<URL> chooserResults;
-
- if (err == nil)
- {
- [url startAccessingSecurityScopedResource];
-
- NSError* error = nil;
-
- NSData* bookmark = [url bookmarkDataWithOptions: 0
- includingResourceValuesForKeys: nil
- relativeToURL: nil
- error: &error];
-
- [bookmark retain];
-
- [url stopAccessingSecurityScopedResource];
-
- URL juceUrl (nsStringToJuce ([url absoluteString]));
-
- if (error == nil)
- {
- setURLBookmark (juceUrl, (void*) bookmark);
- }
- else
- {
- auto desc = [error localizedDescription];
- ignoreUnused (desc);
- jassertfalse;
- }
-
- chooserResults.add (juceUrl);
- }
- else
- {
- auto desc = [err localizedDescription];
- ignoreUnused (desc);
- jassertfalse;
- }
-
- owner.finished (chooserResults);
- }];
- }
-
- void pickerWasCancelled()
- {
- cancelPendingUpdate();
-
- owner.finished ({});
- exitModalState (0);
- }
-
- //==============================================================================
- struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
- {
- FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
- {
- addIvar<Native*> ("owner");
-
- addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
- addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
-
- addProtocol (@protocol (UIDocumentPickerDelegate));
-
- registerClass();
- }
-
- static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
- static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
-
- //==============================================================================
- static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
- {
- if (auto* picker = getOwner (self))
- picker->didPickDocumentAtURL (url);
- }
-
- static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
- {
- if (auto* picker = getOwner (self))
- picker->pickerWasCancelled();
- }
- };
-
- struct FileChooserControllerClass : public ObjCClass<UIDocumentPickerViewController>
- {
- FileChooserControllerClass() : ObjCClass<UIDocumentPickerViewController> ("FileChooserController_")
- {
- addIvar<Native*> ("owner");
- addMethod (@selector (viewDidDisappear:), viewDidDisappear, "v@:@c");
-
- registerClass();
- }
-
- static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
- static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
-
- //==============================================================================
- static void viewDidDisappear (id self, SEL, BOOL animated)
- {
- sendSuperclassMessage<void> (self, @selector (viewDidDisappear:), animated);
-
- if (auto* picker = getOwner (self))
- picker->triggerAsyncUpdate();
- }
- };
-
- //==============================================================================
- FileChooser& owner;
- std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate;
- std::unique_ptr<UIDocumentPickerViewController, NSObjectDeleter> controller;
- UIViewComponentPeer* peer = nullptr;
-
- static FileChooserDelegateClass fileChooserDelegateClass;
- static FileChooserControllerClass fileChooserControllerClass;
-
- //==============================================================================
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
- };
-
- //==============================================================================
- bool FileChooser::isPlatformDialogAvailable()
- {
- #if JUCE_DISABLE_NATIVE_FILECHOOSERS
- return false;
- #else
- return true;
- #endif
- }
-
- FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags,
- FilePreviewComponent*)
- {
- return new FileChooser::Native (owner, flags);
- }
-
- } // namespace juce
|