Browse Source

FileChooser: misc fixes and improvements to iOS and Android file choosers.

tags/2021-05-28
Lukasz Kozakiewicz 8 years ago
parent
commit
5358756d58
12 changed files with 412 additions and 106 deletions
  1. +19
    -2
      examples/Demo/Source/Demos/DialogsDemo.cpp
  2. +72
    -4
      modules/juce_core/native/juce_android_Files.cpp
  3. +1
    -1
      modules/juce_core/native/juce_android_Network.cpp
  4. +145
    -0
      modules/juce_core/network/juce_URL.cpp
  5. +14
    -0
      modules/juce_core/network/juce_URL.h
  6. +6
    -30
      modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp
  7. +35
    -42
      modules/juce_gui_basics/filebrowser/juce_FileChooser.h
  8. +2
    -2
      modules/juce_gui_basics/native/juce_android_FileChooser.cpp
  9. +114
    -21
      modules/juce_gui_basics/native/juce_ios_FileChooser.mm
  10. +1
    -1
      modules/juce_gui_basics/native/juce_linux_FileChooser.cpp
  11. +1
    -1
      modules/juce_gui_basics/native/juce_mac_FileChooser.mm
  12. +2
    -2
      modules/juce_gui_basics/native/juce_win32_FileChooser.cpp

+ 19
- 2
examples/Demo/Source/Demos/DialogsDemo.cpp View File

@@ -331,17 +331,34 @@ private:
useNativeVersion);
fc->launchAsync (FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles,
[] (const FileChooser& chooser)
[fileToSave] (const FileChooser& chooser)
{
auto result = chooser.getURLResult();
auto name = result.isEmpty() ? String()
: (result.isLocalFile() ? result.getLocalFile().getFullPathName()
: result.toString (true));
// Android and iOS file choosers will create placeholder files for chosen
// paths, so we may as well write into those files.
#if JUCE_ANDROID || JUCE_IOS
if (! result.isEmpty())
{
ScopedPointer<InputStream> wi (fileToSave.createInputStream());
ScopedPointer<OutputStream> wo (result.createOutputStream());
if (wi != nullptr && wo != nullptr)
{
auto numWritten = wo->writeFromInputStream (*wi, -1);
jassert (numWritten > 0);
ignoreUnused (numWritten);
}
}
#endif
AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
"File Chooser...",
"You picked: " + name);
}, nullptr, fileToSave);
});
}
else if (type == directoryChooser)
{


+ 72
- 4
modules/juce_core/native/juce_android_Files.cpp View File

@@ -33,8 +33,9 @@ DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (query, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \
METHOD (openInputStream, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;") \
METHOD (query, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \
METHOD (openInputStream, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;") \
METHOD (openOutputStream, "openOutputStream", "(Landroid/net/Uri;)Ljava/io/OutputStream;")
DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver");
#undef JNI_CLASS_MEMBERS
@@ -61,6 +62,14 @@ DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment");
DECLARE_JNI_CLASS (AndroidFile, "java/io/File");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (close, "close", "()V") \
METHOD (flush, "flush", "()V") \
METHOD (write, "write", "([BII)V")
DECLARE_JNI_CLASS (AndroidOutputStream, "java/io/OutputStream");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
STATICMETHOD (withAppendedId, "withAppendedId", "(Landroid/net/Uri;J)Landroid/net/Uri;") \
@@ -71,7 +80,7 @@ DECLARE_JNI_CLASS (ContentUris, "android/content/ContentUris");
struct AndroidContentUriResolver
{
public:
static LocalRef<jobject> getInputStreamForContentUri (const URL& url)
static LocalRef<jobject> getStreamForContentUri (const URL& url, bool inputStream)
{
// only use this method for content URIs
jassert (url.getScheme() == "content");
@@ -80,7 +89,10 @@ public:
LocalRef<jobject> contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver));
if (contentResolver)
return LocalRef<jobject> ((env->CallObjectMethod (contentResolver.get(), ContentResolver.openInputStream, urlToUri (url).get())));
return LocalRef<jobject> ((env->CallObjectMethod (contentResolver.get(),
inputStream ? ContentResolver.openInputStream
: ContentResolver.openOutputStream,
urlToUri (url).get())));
return LocalRef<jobject>();
}
@@ -354,6 +366,62 @@ private:
}
};
//==============================================================================
struct AndroidContentUriOutputStream : public OutputStream
{
AndroidContentUriOutputStream (LocalRef<jobject>&& outputStream)
: stream (outputStream)
{
}
~AndroidContentUriOutputStream()
{
stream.callVoidMethod (AndroidOutputStream.close);
}
void flush() override
{
stream.callVoidMethod (AndroidOutputStream.flush);
}
bool setPosition (int64 newPos) override
{
return (newPos == pos);
}
int64 getPosition() override
{
return pos;
}
bool write (const void* dataToWrite, size_t numberOfBytes) override
{
if (numberOfBytes == 0)
return true;
JNIEnv* env = getEnv();
jbyteArray javaArray = env->NewByteArray ((jsize) numberOfBytes);
env->SetByteArrayRegion (javaArray, 0, (jsize) numberOfBytes, (const jbyte*) dataToWrite);
stream.callVoidMethod (AndroidOutputStream.write, javaArray, 0, (jint) numberOfBytes);
env->DeleteLocalRef (javaArray);
pos += static_cast<int64> (numberOfBytes);
return true;
}
GlobalRef stream;
int64 pos = 0;
};
OutputStream* juce_CreateContentURIOutputStream (const URL& url)
{
auto stream = AndroidContentUriResolver::getStreamForContentUri (url, false);
return (stream.get() != 0 ? new AndroidContentUriOutputStream (static_cast<LocalRef<jobject>&&> (stream)) : nullptr);
}
//==============================================================================
class MediaScannerConnectionClient : public AndroidInterfaceImplementer
{


+ 1
- 1
modules/juce_core/native/juce_android_Network.cpp View File

@@ -137,7 +137,7 @@ public:
if (isContentURL)
{
auto inputStream = AndroidContentUriResolver::getInputStreamForContentUri (url);
auto inputStream = AndroidContentUriResolver::getStreamForContentUri (url, true);
if (inputStream != nullptr)
{


+ 145
- 0
modules/juce_core/network/juce_URL.cpp View File

@@ -197,6 +197,9 @@ URL::URL (URL&& other)
parameterNames (static_cast<StringArray&&> (other.parameterNames)),
parameterValues (static_cast<StringArray&&> (other.parameterValues)),
filesToUpload (static_cast<ReferenceCountedArray<Upload>&&> (other.filesToUpload))
#if JUCE_IOS
, bookmark (other.bookmark)
#endif
{
}
@@ -207,6 +210,9 @@ URL& URL::operator= (URL&& other)
parameterNames = static_cast<StringArray&&> (other.parameterNames);
parameterValues = static_cast<StringArray&&> (other.parameterValues);
filesToUpload = static_cast<ReferenceCountedArray<Upload>&&> (other.filesToUpload);
#if JUCE_IOS
bookmark = other.bookmark;
#endif
return *this;
}
@@ -494,6 +500,114 @@ bool URL::isProbablyAnEmailAddress (const String& possibleEmailAddress)
&& ! possibleEmailAddress.endsWithChar ('.');
}
#if JUCE_IOS
void setURLBookmark (URL& u, void* bookmark)
{
u.bookmark = bookmark;
}
void* getURLBookmark (URL& u)
{
return u.bookmark;
}
template <typename Stream> struct iOSFileStreamWrapperFlush { static void flush (Stream*) {} };
template <> struct iOSFileStreamWrapperFlush<FileOutputStream> { static void flush (OutputStream* o) { o->flush(); } };
template <typename Stream>
class iOSFileStreamWrapper : public Stream
{
public:
iOSFileStreamWrapper (URL& urlToUse)
: Stream (getLocalFileAccess (urlToUse)),
url (urlToUse)
{}
~iOSFileStreamWrapper()
{
iOSFileStreamWrapperFlush<Stream>::flush (this);
if (NSData* bookmark = (NSData*) getURLBookmark (url))
{
BOOL isBookmarkStale = false;
NSError* error = nil;
auto* nsURL = [NSURL URLByResolvingBookmarkData: bookmark
options: 0
relativeToURL: nil
bookmarkDataIsStale: &isBookmarkStale
error: &error];
if (error == nil)
{
if (isBookmarkStale)
updateStaleBookmark (nsURL, url);
[nsURL stopAccessingSecurityScopedResource];
}
else
{
auto* desc = [error localizedDescription];
ignoreUnused (desc);
jassertfalse;
}
}
}
private:
URL url;
bool securityAccessSucceeded = false;
File getLocalFileAccess (URL& urlToUse)
{
if (NSData* bookmark = (NSData*) getURLBookmark (urlToUse))
{
BOOL isBookmarkStale = false;
NSError* error = nil;
auto* nsURL = [NSURL URLByResolvingBookmarkData: bookmark
options: 0
relativeToURL: nil
bookmarkDataIsStale: &isBookmarkStale
error: &error];
if (error == nil)
{
securityAccessSucceeded = [nsURL startAccessingSecurityScopedResource];
if (isBookmarkStale)
updateStaleBookmark (nsURL, urlToUse);
return urlToUse.getLocalFile();
}
else
{
auto* desc = [error localizedDescription];
ignoreUnused (desc);
jassertfalse;
}
}
return urlToUse.getLocalFile();
}
void updateStaleBookmark (NSURL* nsURL, URL& juceUrl)
{
NSError* error = nil;
NSData* bookmark = [nsURL bookmarkDataWithOptions: NSURLBookmarkCreationSuitableForBookmarkFile
includingResourceValuesForKeys: nil
relativeToURL: nil
error: &error];
if (error == nil)
setURLBookmark (juceUrl, (void*) bookmark);
else
jassertfalse;
}
};
#endif
//==============================================================================
InputStream* URL::createInputStream (const bool usePostCommand,
OpenStreamProgressCallback* const progressCallback,
@@ -506,7 +620,15 @@ InputStream* URL::createInputStream (const bool usePostCommand,
String httpRequestCmd) const
{
if (isLocalFile())
{
#if JUCE_IOS
// We may need to refresh the embedded bookmark.
return new iOSFileStreamWrapper<FileInputStream> (const_cast<URL&>(*this));
#else
return getLocalFile().createInputStream();
#endif
}
ScopedPointer<WebInputStream> wi (new WebInputStream (*this, usePostCommand));
@@ -558,6 +680,29 @@ InputStream* URL::createInputStream (const bool usePostCommand,
return wi.release();
}
#if JUCE_ANDROID
OutputStream* juce_CreateContentURIOutputStream (const URL&);
#endif
OutputStream* URL::createOutputStream() const
{
if (isLocalFile())
{
#if JUCE_IOS
// We may need to refresh the embedded bookmark.
return new iOSFileStreamWrapper<FileOutputStream> (const_cast<URL&> (*this));
#else
return new FileOutputStream (getLocalFile());
#endif
}
#if JUCE_ANDROID
return juce_CreateContentURIOutputStream (*this);
#else
return nullptr;
#endif
}
//==============================================================================
bool URL::readEntireBinaryStream (MemoryBlock& destData, bool usePostCommand) const
{


+ 14
- 0
modules/juce_core/network/juce_URL.h View File

@@ -327,6 +327,13 @@ public:
int numRedirectsToFollow = 5,
String httpRequestCmd = String()) const;
/** Attempts to open an output stream to a URL for writing
This method can only be used for certain scheme types such as local files
and content:// URIs on Android.
*/
OutputStream* createOutputStream() const;
//==============================================================================
/** Represents a download task.
Returned by downloadToFile to allow querying and controling the download task.
@@ -521,6 +528,13 @@ private:
friend struct ContainerDeletePolicy<Upload>;
ReferenceCountedArray<Upload> filesToUpload;
#if JUCE_IOS
void* bookmark;
friend void setURLBookmark (URL&, void*);
friend void* getURLBookmark (URL&);
#endif
URL (const String&, int);
void init();
void addParameter (const String&, const String&);


+ 6
- 30
modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp View File

@@ -73,7 +73,7 @@ private:
result.add (URL (browserComponent.getSelectedFile (i)));
}
owner.finished (result, true);
owner.finished (result);
}
//==============================================================================
@@ -137,13 +137,12 @@ bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* pre
previewComp);
}
bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite,
const File& fileWhichShouldBeSaved)
bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
{
return showDialog (FileBrowserComponent::saveMode
| FileBrowserComponent::canSelectFiles
| (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
nullptr, fileWhichShouldBeSaved);
nullptr);
}
bool FileChooser::browseForDirectory()
@@ -153,11 +152,8 @@ bool FileChooser::browseForDirectory()
nullptr);
}
bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp,
const File& fileWhichShouldBeSaved)
bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
{
fileToSave = (flags & FileBrowserComponent::saveMode) != 0 ? fileWhichShouldBeSaved : File();
FocusRestorer focusRestorer;
pimpl = createPimpl (flags, previewComp);
@@ -171,8 +167,7 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ
#endif
void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
FilePreviewComponent* previewComp,
const File& fileWhichShouldBeSaved)
FilePreviewComponent* previewComp)
{
// You must specify a callback when using launchAsync
jassert (callback);
@@ -180,8 +175,6 @@ void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&
// you cannot run two file chooser dialog boxes at the same time
jassert (asyncCallback == nullptr);
fileToSave = (flags & FileBrowserComponent::saveMode) != 0 ? fileWhichShouldBeSaved : File();
asyncCallback = static_cast<std::function<void (const FileChooser&)>&&> (callback);
pimpl = createPimpl (flags, previewComp);
@@ -256,30 +249,13 @@ URL FileChooser::getURLResult() const
return results.getFirst();
}
void FileChooser::finished (const Array<URL>& asyncResults, bool shouldMove)
void FileChooser::finished (const Array<URL>& asyncResults)
{
std::function<void (const FileChooser&)> callback;
std::swap (callback, asyncCallback);
results = asyncResults;
if (shouldMove && fileToSave.existsAsFile())
{
if (results.size() > 0)
{
// The user either selected multiple files or wants to save the file to a URL
// Both are not supported
jassert (results.size() == 1 && results.getReference (0).isLocalFile());
if (! fileToSave.moveFileTo (results.getReference (0).getLocalFile()))
results.clear();
}
else
{
fileToSave.deleteFile();
}
}
pimpl = nullptr;
if (callback)


+ 35
- 42
modules/juce_gui_basics/filebrowser/juce_FileChooser.h View File

@@ -66,8 +66,27 @@ public:
@param initialFileOrDirectory the file or directory that should be selected
when the dialog box opens. If this parameter is
set to File(), a sensible default directory will
be used instead. This parameter is ignored for native
iOS file choosers.
be used instead.
Note: on iOS when saving a file, a user will not
be able to change a file name, so it may be a good
idea to include at list a valid file name in
initialFileOrDirectory. When no filename is found,
"Untitled" will be used.
Also, if you pass an already existing file on iOS,
that file will be automatically copied to the
destination chosen by user and if it can be previewed,
its preview will be presented in the dialog too. You
will still be able to write into this file copy, since
its URL will be returned by getURLResult(). This can be
useful when you want to save e.g. an image, so that
you can pass a (temporary) file with low quality
preview and after the user picks the destination,
you can write a high quality image into the copied
file. If you create such a temporary file, you need
to delete it yourself, once it is not needed anymore.
@param filePatternsAllowed a set of file patterns to specify which files
can be selected - each pattern should be
separated by a comma or semi-colon, e.g. "*" or
@@ -123,22 +142,12 @@ public:
@param warnAboutOverwritingExistingFiles if true, the dialog box will ask
the user if they're sure they want to overwrite a file that already
exists
@param fileWhichShouldBeSaved if this parameter is specified, then, if the the user
selects a valid location to save the file, fileWhichShouldBeSaved will
automaitcally be moved to the location selected by the user when the user
clicks 'ok'. If you do not specify this parameter, then it is your
responsibility to save your file at the location that is returned from this
file chooser. Typically, when using this parameter, you already write the
file you wish to save to a temporary location and then supply the path to
this file to this parameter. This parameter is required on iOS when using
native file save dialogs but can be used on all other platforms.
@returns true if the user chose a file and pressed 'ok', in which case, use
the getResult() method to find out what the file was. Returns false
if they cancelled instead.
@see browseForFileToOpen, browseForDirectory
*/
bool browseForFileToSave (bool warnAboutOverwritingExistingFiles,
const File& fileWhichShouldBeSaved = File());
bool browseForFileToSave (bool warnAboutOverwritingExistingFiles);
/** Shows a dialog box to choose a directory.
@@ -163,24 +172,12 @@ public:
/** Runs a dialog box for the given set of option flags.
The flag values used are those in FileBrowserComponent::FileChooserFlags.
@param fileWhichShouldBeSaved if this parameter is specified and saveMode is
specified, then, if the the user selects a valid location to save the file,
fileWhichShouldBeSaved will automaitcally be moved to the location selected
by the user when the user clicks 'ok'. If you do not specify this parameter,
then it is your responsibility to save your file at the location that is
returned from this file chooser. Typically, when using this parameter,
you already write the file you wish to save to a temporary location and
then supply the path to this file to this parameter. This parameter is
required on iOS when using native file save dialogs but can be used on all
other platforms.
@returns true if the user chose a directory and pressed 'ok', in which case, use
the getResult() method to find out what they chose. Returns false
if they cancelled instead.
@see FileBrowserComponent::FileChooserFlags
*/
bool showDialog (int flags, FilePreviewComponent* previewComponent,
const File& fileWhichShouldBeSaved = File());
bool showDialog (int flags, FilePreviewComponent* previewComponent);
/** Use this method to launch the file browser window asynchronously.
@@ -196,22 +193,10 @@ public:
You must ensure that the lifetime of the callback object is longer than
the lifetime of the file-chooser.
@param fileWhichShouldBeSaved if this parameter is specified and saveMode is
specified, then, if the the user selects a valid location to save the file,
fileWhichShouldBeSaved will automaitcally be moved to the location selected
by the user when the user clicks 'ok'. If you do not specify this parameter,
then it is your responsibility to save your file at the location that is
returned from this file chooser. Typically, when using this parameter,
you already write the file you wish to save to a temporary location and
then supply the path to this file to this parameter. This parameter is
required on iOS when using native file save dialogs but can be used on all
other platforms.
*/
void launchAsync (int flags,
std::function<void (const FileChooser&)>,
FilePreviewComponent* previewComponent = nullptr,
const File& fileWhichShouldBeSaved = File());
FilePreviewComponent* previewComponent = nullptr);
//==============================================================================
/** Returns the last file that was chosen by one of the browseFor methods.
@@ -252,6 +237,10 @@ public:
may return a URL to a remote document. If a local file is chosen then you can
convert this file to a JUCE File class via the URL::getLocalFile method.
Note: on iOS it is best to dispose any copies of returned URL as soon as
you finish dealing with the file. This is because URL might be security
scoped and a system allows only for a limited number of such URLs.
@see getResult, URL::getLocalFile
*/
URL getURLResult() const;
@@ -266,6 +255,10 @@ public:
This array may be empty if no files were chosen, or can contain multiple entries
if multiple files were chosen.
Note: on iOS it is best to dispose any copies of returned URLs as soon as
you finish dealing with the file. This is because URLs might be security
scoped and a system allows only for a limited number of such URLs.
@see getResults, URL::getLocalFile
*/
const Array<URL>& getURLResults() const noexcept { return results; }
@@ -288,14 +281,14 @@ public:
private:
//==============================================================================
String title, filters;
File startingFile, fileToSave;
File startingFile;
Array<URL> results;
const bool useNativeDialogBox;
const bool treatFilePackagesAsDirs;
std::function<void (const FileChooser&)> asyncCallback;
//==============================================================================
void finished (const Array<URL>&, bool);
void finished (const Array<URL>&);
//==============================================================================
struct Pimpl
@@ -303,7 +296,7 @@ private:
virtual ~Pimpl() {}
virtual void launch() = 0;
virtual void runModally() = 0;
virtual void runModally() = 0;
};
ScopedPointer<Pimpl> pimpl;


+ 2
- 2
modules/juce_gui_basics/native/juce_android_FileChooser.cpp View File

@@ -109,7 +109,7 @@ public:
{
env->SetObjectArrayElement (jMimeTypes.get(), i, javaString (mimeTypes[i]).get());
if (mimeGroup != mimeTypes[0].upToFirstOccurrenceOf ("/", false, false))
if (mimeGroup != mimeTypes[i].upToFirstOccurrenceOf ("/", false, false))
allMimeTypesHaveSameGroup = false;
}
@@ -168,7 +168,7 @@ public:
}
}
owner.finished (chosenURLs, true);
owner.finished (chosenURLs);
}
static Native* currentFileChooser;


+ 114
- 21
modules/juce_gui_basics/native/juce_ios_FileChooser.mm View File

@@ -28,8 +28,8 @@ namespace juce
{
//==============================================================================
template <> struct ContainerDeletePolicy<UIDocumentPickerViewController> { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSObject<UIDocumentPickerDelegate>> { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<UIDocumentPickerViewController> { static void destroy (NSObject* o) { [o release]; } };
template <> struct ContainerDeletePolicy<NSObject<UIDocumentPickerDelegate>> { static void destroy (NSObject* o) { [o release]; } };
class FileChooser::Native : private Component, public FileChooser::Pimpl
{
@@ -37,32 +37,54 @@ public:
Native (FileChooser& fileChooser, int flags)
: owner (fileChooser)
{
String firstFileExtension;
static FileChooserDelegateClass cls;
delegate = [cls.createInstance() init];
FileChooserDelegateClass::setOwner (delegate, this);
auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters));
auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
if ((flags & FileBrowserComponent::saveMode) != 0)
{
// You must specify the fileWhichShouldBeSaved parameter when using
// the native save dialog on iOS!
jassert (owner.fileToSave.existsAsFile());
auto currentFileOrDirectory = owner.startingFile;
UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
? UIDocumentPickerModeExportToService
: UIDocumentPickerModeMoveToService;
auto url = [[NSURL alloc] initFileURLWithPath:juceStringToNS (owner.fileToSave.getFullPathName())];
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;
}
}
controller = [[UIDocumentPickerViewController alloc] initWithURL:url
inMode:UIDocumentPickerModeExportToService];
auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
controller = [[UIDocumentPickerViewController alloc] initWithURL: url
inMode: pickerMode];
[url release];
}
else
{
controller = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:utTypeArray
inMode:UIDocumentPickerModeOpen];
controller = [[UIDocumentPickerViewController alloc] initWithDocumentTypes: utTypeArray
inMode: UIDocumentPickerModeOpen];
}
[controller setDelegate:delegate];
[controller setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[controller setDelegate: delegate];
[controller setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
setOpaque (false);
@@ -101,7 +123,7 @@ private:
peer = newPeer;
if (auto* parentController = peer->controller)
[parentController showViewController:controller sender:parentController];
[parentController showViewController: controller sender: parentController];
if (peer->view.window != nil)
peer->view.window.autoresizesSubviews = YES;
@@ -109,11 +131,13 @@ private:
}
//==============================================================================
static StringArray getUTTypesForWildcards (const String& filterWildcards)
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)
@@ -124,6 +148,9 @@ private:
auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
auto fileExtensionCF = fileExtension.toCFString();
if (firstExtension.isEmpty())
firstExtension = fileExtension;
auto tag = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF, nullptr);
if (tag != nullptr)
@@ -141,21 +168,87 @@ private:
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 += String (".") + extension;
return filename;
}
//==============================================================================
void didPickDocumentAtURL (NSURL* url)
{
Array<URL> chooserResults;
chooserResults.add (URL (nsStringToJuce ([url absoluteString])));
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];
[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];
[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, false);
exitModalState (1);
owner.finished (chooserResults);
}];
}
void pickerWasCancelled()
{
Array<URL> chooserResults;
owner.finished (chooserResults, false);
owner.finished (chooserResults);
exitModalState (0);
}
@@ -174,7 +267,7 @@ private:
registerClass();
}
static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
//==============================================================================


+ 1
- 1
modules/juce_gui_basics/native/juce_linux_FileChooser.cpp View File

@@ -122,7 +122,7 @@ private:
if (! shouldKill)
{
child.waitForProcessToFinish (60 * 1000);
owner.finished (selection, true);
owner.finished (selection);
}
}


+ 1
- 1
modules/juce_gui_basics/native/juce_mac_FileChooser.mm View File

@@ -213,7 +213,7 @@ private:
}
}
owner.finished (chooserResults, true);
owner.finished (chooserResults);
}
bool shouldShowFilename (const String& filenameToTest)


+ 2
- 2
modules/juce_gui_basics/native/juce_win32_FileChooser.cpp View File

@@ -535,7 +535,7 @@ public:
[safeThis] (int)
{
if (safeThis != nullptr)
safeThis->owner.finished (safeThis->nativeFileChooser->results, true);
safeThis->owner.finished (safeThis->nativeFileChooser->results);
}));
nativeFileChooser->open (true);
@@ -548,7 +548,7 @@ public:
exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0);
nativeFileChooser->cancel();
owner.finished (nativeFileChooser->results, true);
owner.finished (nativeFileChooser->results);
}
private:


Loading…
Cancel
Save