1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

ContentSharer: Update interface to return safer ScopedMessageBox instances

This commit is contained in:
reuk 2023-03-20 20:55:26 +00:00
parent 557d690ff4
commit 9d1a6a3b28
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
22 changed files with 1304 additions and 1033 deletions

View file

@ -4,6 +4,32 @@ JUCE breaking changes
develop
=======
Change
------
The signatures of the ContentSharer member functions have been updated. The
ContentSharer class itself is no longer a singleton.
Possible Issues
---------------
Projects that use the old signatures will not build until they are updated.
Workaround
----------
Instead of calling content sharer functions through a singleton instance, e.g.
ContentSharer::getInstance()->shareText (...);
call the static member functions directly:
ScopedMessageBox messageBox = ContentSharer::shareTextScoped (...);
The new functions return a ScopedMessageBox instance. On iOS, the content
sharer will only remain open for as long as the ScopedMessageBox remains alive.
On Android, this functionality will be added as/when the native APIs allow.
Rationale
---------
The new signatures are safer and easier to use. The ScopedMessageBox also
allows content sharers to be dismissed programmatically, which wasn't
previously possible.
Change
------
The minimum supported AAX library version has been bumped to 2.4.0 and the

View file

@ -54,6 +54,7 @@ target_sources(DemoRunner PRIVATE
target_compile_definitions(DemoRunner PRIVATE
PIP_JUCE_EXAMPLES_DIRECTORY_STRING="${JUCE_SOURCE_DIR}/examples"
JUCE_ALLOW_STATIC_NULL_VARIABLES=0
JUCE_CONTENT_SHARING=1
JUCE_DEMO_RUNNER=1
JUCE_PLUGINHOST_LV2=1
JUCE_PLUGINHOST_VST3=1

View file

@ -314,8 +314,7 @@ private:
SafePointer<CameraDemo> safeThis (this);
juce::ContentSharer::getInstance()->shareFiles ({url},
[safeThis] (bool success, const String&) mutable
messageBox = ContentSharer::shareFilesScoped ({ url }, [safeThis] (bool success, const String&)
{
if (safeThis)
safeThis->sharingFinished (success, false);
@ -355,8 +354,7 @@ private:
SafePointer<CameraDemo> safeThis (this);
juce::ContentSharer::getInstance()->shareFiles ({url},
[safeThis] (bool success, const String&) mutable
messageBox = ContentSharer::shareFilesScoped ({ url }, [safeThis] (bool success, const String&)
{
if (safeThis)
safeThis->sharingFinished (success, true);

View file

@ -462,7 +462,7 @@ private:
}
else if (type == shareText)
{
ContentSharer::getInstance()->shareText ("I love JUCE!", [ptr = Component::SafePointer (this)] (bool success, const String& error)
messageBox = ContentSharer::shareTextScoped ("I love JUCE!", [ptr = Component::SafePointer (this)] (bool success, const String& error)
{
if (ptr == nullptr)
return;
@ -489,7 +489,7 @@ private:
Array<URL> urls;
urls.add (URL (fileToSave));
ContentSharer::getInstance()->shareFiles (urls, [ptr = Component::SafePointer (this)] (bool success, const String& error)
messageBox = ContentSharer::shareFilesScoped (urls, [ptr = Component::SafePointer (this)] (bool success, const String& error)
{
if (ptr == nullptr)
return;
@ -519,7 +519,7 @@ private:
Array<Image> images { myImage, myImage2 };
ContentSharer::getInstance()->shareImages (images, [ptr = Component::SafePointer (this)] (bool success, const String& error)
messageBox = ContentSharer::shareImagesScoped (images, nullptr, [ptr = Component::SafePointer (this)] (bool success, const String& error)
{
if (ptr == nullptr)
return;

View file

@ -48,6 +48,7 @@ juce_add_binary_data(AudioPluginHostData SOURCES
target_compile_definitions(AudioPluginHost PRIVATE
JUCE_ALSA=1
JUCE_CONTENT_SHARING=1
JUCE_DIRECTSOUND=1
JUCE_DISABLE_CAUTIOUS_PARAMETER_ID_CHECKING=1
JUCE_PLUGINHOST_LADSPA=1

View file

@ -1675,6 +1675,10 @@ private:
if (! isLibrary())
{
auto* receiver = getOrCreateChildWithName (application, "receiver");
setAttributeIfNotPresent (*receiver, "android:name", "com.rmsl.juce.Receiver");
setAttributeIfNotPresent (*receiver, "android:exported", "false");
auto* app = createApplicationElement (*manifest);
auto* act = createActivityElement (*app);

View file

@ -94,8 +94,8 @@ struct SystemJavaClassComparator
if ((! isSysClassA) && (! isSysClassB))
{
return DefaultElementComparator<bool>::compareElements (first != nullptr ? first->byteCode != nullptr : false,
second != nullptr ? second->byteCode != nullptr : false);
return DefaultElementComparator<bool>::compareElements (first != nullptr && first->byteCode != nullptr,
second != nullptr && second->byteCode != nullptr);
}
return DefaultElementComparator<bool>::compareElements (isSystemClass (first),
@ -631,43 +631,17 @@ jobject FragmentOverlay::getNativeHandle()
}
//==============================================================================
class ActivityLauncher : public FragmentOverlay
void startAndroidActivityForResult (const LocalRef<jobject>& intent,
int requestCode,
std::function<void (int, int, LocalRef<jobject>)>&& callback)
{
public:
ActivityLauncher (const LocalRef<jobject>& intentToUse,
int requestCodeToUse,
std::function<void (int, int, LocalRef<jobject>)> && callbackToUse)
: intent (intentToUse), requestCode (requestCodeToUse), callback (std::move (callbackToUse))
{}
void onStart() override
auto* launcher = new ActivityLauncher (intent, requestCode);
launcher->callback = [launcher, c = std::move (callback)] (auto&&... args)
{
if (! std::exchange (activityHasStarted, true))
getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult,
intent.get(), requestCode);
}
void onActivityResult (int activityRequestCode, int resultCode, LocalRef<jobject> data) override
{
if (callback)
callback (activityRequestCode, resultCode, std::move (data));
getEnv()->CallVoidMethod (getNativeHandle(), JuceFragmentOverlay.close);
delete this;
}
private:
GlobalRef intent;
int requestCode;
std::function<void (int, int, LocalRef<jobject>)> callback;
bool activityHasStarted = false;
};
void startAndroidActivityForResult (const LocalRef<jobject>& intent, int requestCode,
std::function<void (int, int, LocalRef<jobject>)> && callback)
{
auto* activityLauncher = new ActivityLauncher (intent, requestCode, std::move (callback));
activityLauncher->open();
NullCheckedInvocation::invoke (c, args...);
delete launcher;
};
launcher->open();
}
//==============================================================================

View file

@ -262,7 +262,8 @@ template <typename T, size_t N> constexpr auto numBytes (const T (&) [N]) { retu
METHOD (getApplicationInfo, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;") \
METHOD (checkCallingOrSelfPermission, "checkCallingOrSelfPermission", "(Ljava/lang/String;)I") \
METHOD (checkCallingOrSelfUriPermission, "checkCallingOrSelfUriPermission", "(Landroid/net/Uri;I)I") \
METHOD (getCacheDir, "getCacheDir", "()Ljava/io/File;")
METHOD (getCacheDir, "getCacheDir", "()Ljava/io/File;") \
METHOD (registerReceiver, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;") \
DECLARE_JNI_CLASS (AndroidContext, "android/content/Context")
#undef JNI_CLASS_MEMBERS
@ -418,6 +419,7 @@ DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread")
METHOD (putExtraStrings, "putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;") \
METHOD (putExtraParcelable, "putExtra", "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;") \
METHOD (putExtraBool, "putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;") \
METHOD (putExtraInt, "putExtra", "(Ljava/lang/String;I)Landroid/content/Intent;") \
METHOD (putParcelableArrayListExtra, "putParcelableArrayListExtra", "(Ljava/lang/String;Ljava/util/ArrayList;)Landroid/content/Intent;") \
METHOD (setAction, "setAction", "(Ljava/lang/String;)Landroid/content/Intent;") \
METHOD (setFlags, "setFlags", "(I)Landroid/content/Intent;") \
@ -427,6 +429,12 @@ DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread")
DECLARE_JNI_CLASS (AndroidIntent, "android/content/Intent")
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (createChooser, "createChooser", "(Landroid/content/Intent;Ljava/lang/CharSequence;Landroid/content/IntentSender;)Landroid/content/Intent;") \
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidIntent22, "android/content/Intent", 22)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (constructor, "<init>", "()V") \
METHOD (postRotate, "postRotate", "(FFF)Z") \
@ -490,7 +498,8 @@ DECLARE_JNI_CLASS (AndroidPaint, "android/graphics/Paint")
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getActivity, "getActivity", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \
METHOD (getIntentSender, "getIntentSender", "()Landroid/content/IntentSender;")
STATICMETHOD (getBroadcast, "getBroadcast", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \
METHOD (getIntentSender, "getIntentSender", "()Landroid/content/IntentSender;") \
DECLARE_JNI_CLASS (AndroidPendingIntent, "android/app/PendingIntent")
#undef JNI_CLASS_MEMBERS
@ -1026,10 +1035,37 @@ private:
//==============================================================================
// Allows you to start an activity without requiring to have an activity
void startAndroidActivityForResult (const LocalRef<jobject>& intent, int requestCode,
std::function<void (int, int, LocalRef<jobject>)> && callback);
void startAndroidActivityForResult (const LocalRef<jobject>& intent,
int requestCode,
std::function<void (int, int, LocalRef<jobject>)>&& callback);
//==============================================================================
class ActivityLauncher : public FragmentOverlay
{
public:
ActivityLauncher (const LocalRef<jobject>& intentToUse, int requestCodeToUse)
: intent (intentToUse), requestCode (requestCodeToUse)
{}
void onStart() override
{
if (! std::exchange (activityHasStarted, true))
getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult, intent.get(), requestCode);
}
void onActivityResult (int activityRequestCode, int resultCode, LocalRef<jobject> data) override
{
NullCheckedInvocation::invoke (callback, activityRequestCode, resultCode, std::move (data));
}
std::function<void (int, int, LocalRef<jobject>)> callback;
private:
GlobalRef intent;
int requestCode;
bool activityHasStarted = false;
};
//==============================================================================
bool androidHasSystemFeature (const String& property);
String audioManagerGetProperty (const String& property);

View file

@ -0,0 +1,98 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
class ConcreteScopedContentSharerImpl : public ScopedMessageBoxImpl,
private AsyncUpdater
{
public:
static ScopedMessageBox show (std::unique_ptr<ScopedContentSharerInterface>&& native,
ContentSharer::Callback callback)
{
return ScopedMessageBox (runAsync (std::move (native), std::move (callback)));
}
~ConcreteScopedContentSharerImpl() override
{
cancelPendingUpdate();
}
void close() override
{
cancelPendingUpdate();
nativeImplementation->close();
self.reset();
}
private:
static std::shared_ptr<ConcreteScopedContentSharerImpl> runAsync (std::unique_ptr<ScopedContentSharerInterface>&& p,
ContentSharer::Callback&& c)
{
std::shared_ptr<ConcreteScopedContentSharerImpl> result (new ConcreteScopedContentSharerImpl (std::move (p), std::move (c)));
result->self = result;
result->triggerAsyncUpdate();
return result;
}
ConcreteScopedContentSharerImpl (std::unique_ptr<ScopedContentSharerInterface>&& p,
ContentSharer::Callback&& c)
: callback (std::move (c)), nativeImplementation (std::move (p)) {}
void handleAsyncUpdate() override
{
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ConcreteScopedContentSharerImpl> (self)] (bool result, const String& error)
{
const auto notifyRecipient = [result, error, weakRecipient]
{
if (const auto locked = weakRecipient.lock())
{
NullCheckedInvocation::invoke (locked->callback, result, error);
locked->self.reset();
}
};
if (MessageManager::getInstance()->isThisTheMessageThread())
notifyRecipient();
else
MessageManager::callAsync (notifyRecipient);
});
}
ContentSharer::Callback callback;
std::unique_ptr<ScopedContentSharerInterface> nativeImplementation;
/* The 'old' native message box API doesn't have a concept of content sharer owners.
Instead, content sharers have to clean up after themselves, once they're done displaying.
To allow this mode of usage, the implementation keeps an owning reference to itself,
which is cleared once the content sharer is closed or asked to quit. To display a content
sharer box without a scoped lifetime, just create a Pimpl instance without using
the ScopedContentSharer wrapper, and the Pimpl will destroy itself after it is dismissed.
*/
std::shared_ptr<ConcreteScopedContentSharerImpl> self;
};
} // namespace juce::detail

View file

@ -0,0 +1,211 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
/*
Instances of this type can show and dismiss a content sharer.
This is an interface rather than a concrete type so that platforms can pick an implementation at
runtime if necessary.
*/
struct ScopedContentSharerInterface
{
virtual ~ScopedContentSharerInterface() = default;
/* Shows the content sharer.
When the content sharer exits normally, it should send the result to the passed-in function.
The passed-in function is safe to call from any thread at any time.
*/
virtual void runAsync (ContentSharer::Callback callback)
{
jassertfalse;
NullCheckedInvocation::invoke (callback, false, "Content sharing not available on this platform!");
}
/* Forcefully closes the content sharer.
This will be called when the content sharer handle has fallen out of scope.
If the content sharer has already been closed by the user, this shouldn't do anything.
*/
virtual void close() {}
/* Implemented differently for each platform. */
static std::unique_ptr<ScopedContentSharerInterface> shareFiles (const Array<URL>&, Component*);
static std::unique_ptr<ScopedContentSharerInterface> shareText (const String&, Component*);
/* Implemented below. */
static std::unique_ptr<ScopedContentSharerInterface> shareImages (const Array<Image>&, std::unique_ptr<ImageFileFormat>, Component*);
static std::unique_ptr<ScopedContentSharerInterface> shareData (MemoryBlock, Component*);
};
class TemporaryFilesDecorator : public ScopedContentSharerInterface,
private AsyncUpdater
{
public:
explicit TemporaryFilesDecorator (Component* parentIn)
: parent (parentIn) {}
void runAsync (ContentSharer::Callback cb) override
{
callback = std::move (cb);
task = std::async (std::launch::async, [this]
{
std::tie (temporaryFiles, error) = prepareTemporaryFiles();
triggerAsyncUpdate();
});
}
void close() override
{
if (inner != nullptr)
inner->close();
}
private:
virtual std::tuple<Array<URL>, String> prepareTemporaryFiles() const = 0;
void handleAsyncUpdate() override
{
if (error.isNotEmpty())
{
NullCheckedInvocation::invoke (callback, false, error);
return;
}
inner = shareFiles (temporaryFiles, parent);
if (inner == nullptr)
{
NullCheckedInvocation::invoke (callback, false, TRANS ("Failed to create file sharer"));
return;
}
inner->runAsync (callback);
}
Array<URL> temporaryFiles;
String error;
std::unique_ptr<ScopedContentSharerInterface> inner;
ContentSharer::Callback callback;
std::future<void> task;
Component* parent = nullptr;
};
std::unique_ptr<ScopedContentSharerInterface> ScopedContentSharerInterface::shareImages (const Array<Image>& images,
std::unique_ptr<ImageFileFormat> format,
Component* parent)
{
class Decorator : public TemporaryFilesDecorator
{
public:
Decorator (Array<Image> imagesIn, std::unique_ptr<ImageFileFormat> formatIn, Component* parentIn)
: TemporaryFilesDecorator (parentIn), images (std::move (imagesIn)), format (std::move (formatIn)) {}
private:
std::tuple<Array<URL>, String> prepareTemporaryFiles() const override
{
const auto extension = format->getFormatName().toLowerCase();
Array<URL> result;
for (const auto& image : images)
{
File tempFile = File::createTempFile (extension);
if (! tempFile.create().wasOk())
return { Array<URL>{}, TRANS ("Failed to create temporary file") };
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
if (outputStream == nullptr)
return { Array<URL>{}, TRANS ("Failed to open temporary file for writing") };
if (format->writeImageToStream (image, *outputStream))
result.add (URL (tempFile));
}
for (const auto& url : result)
jassertquiet (url.isLocalFile() && url.getLocalFile().existsAsFile());
return { std::move (result), String{} };
}
Array<Image> images;
std::unique_ptr<ImageFileFormat> format;
};
return std::make_unique<Decorator> (images,
format == nullptr ? std::make_unique<PNGImageFormat>() : std::move (format),
parent);
}
std::unique_ptr<ScopedContentSharerInterface> ScopedContentSharerInterface::shareData (MemoryBlock mb, Component* parent)
{
class Decorator : public TemporaryFilesDecorator
{
public:
Decorator (MemoryBlock mbIn, Component* parentIn)
: TemporaryFilesDecorator (parentIn), mb (std::move (mbIn)) {}
private:
std::tuple<Array<URL>, String> prepareTemporaryFiles() const override
{
File tempFile = File::createTempFile ("data");
if (! tempFile.create().wasOk())
return { Array<URL>{}, TRANS ("Failed to create temporary file") };
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
if (outputStream == nullptr)
return { Array<URL>{}, TRANS ("Failed to open temporary file for writing") };
size_t pos = 0;
size_t totalSize = mb.getSize();
while (pos < totalSize)
{
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
if (! outputStream->write (mb.begin() + pos, numToWrite))
return { Array<URL>{}, TRANS ("Failed to write to temporary file") };
pos += numToWrite;
}
return { Array<URL> { URL (tempFile) }, String{} };
}
MemoryBlock mb;
};
return std::make_unique<Decorator> (std::move (mb), parent);
}
} // namespace juce::detail

View file

@ -27,7 +27,16 @@ namespace juce::detail
{
//==============================================================================
class ScopedMessageBoxImpl : private AsyncUpdater
class ScopedMessageBoxImpl
{
public:
virtual ~ScopedMessageBoxImpl() = default;
virtual void close() = 0;
};
//==============================================================================
class ConcreteScopedMessageBoxImpl : public ScopedMessageBoxImpl,
private AsyncUpdater
{
public:
static ScopedMessageBox show (std::unique_ptr<ScopedMessageBoxInterface>&& native,
@ -50,12 +59,12 @@ public:
return 0;
}
~ScopedMessageBoxImpl() override
~ConcreteScopedMessageBoxImpl() override
{
cancelPendingUpdate();
}
void close()
void close() override
{
cancelPendingUpdate();
nativeImplementation->close();
@ -63,10 +72,10 @@ public:
}
private:
static std::shared_ptr<ScopedMessageBoxImpl> runAsync (std::unique_ptr<ScopedMessageBoxInterface>&& p,
static std::shared_ptr<ConcreteScopedMessageBoxImpl> runAsync (std::unique_ptr<ScopedMessageBoxInterface>&& p,
std::unique_ptr<ModalComponentManager::Callback>&& c)
{
std::shared_ptr<ScopedMessageBoxImpl> result (new ScopedMessageBoxImpl (std::move (p), std::move (c)));
std::shared_ptr<ConcreteScopedMessageBoxImpl> result (new ConcreteScopedMessageBoxImpl (std::move (p), std::move (c)));
result->self = result;
result->triggerAsyncUpdate();
return result;
@ -78,16 +87,16 @@ private:
return local != nullptr ? local->runSync() : 0;
}
explicit ScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p)
: ScopedMessageBoxImpl (std::move (p), nullptr) {}
explicit ConcreteScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p)
: ConcreteScopedMessageBoxImpl (std::move (p), nullptr) {}
ScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p,
ConcreteScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p,
std::unique_ptr<ModalComponentManager::Callback>&& c)
: callback (std::move (c)), nativeImplementation (std::move (p)) {}
void handleAsyncUpdate() override
{
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ScopedMessageBoxImpl> (self)] (int result)
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ConcreteScopedMessageBoxImpl> (self)] (int result)
{
const auto notifyRecipient = [result, weakRecipient]
{
@ -117,7 +126,7 @@ private:
message box without a scoped lifetime, just create a Pimpl instance without using
the ScopedMessageBox wrapper, and the Pimpl will destroy itself after it is dismissed.
*/
std::shared_ptr<ScopedMessageBoxImpl> self;
std::shared_ptr<ConcreteScopedMessageBoxImpl> self;
};
} // namespace juce::detail

View file

@ -26,247 +26,49 @@
namespace juce
{
#if JUCE_CONTENT_SHARING
//==============================================================================
class ContentSharer::PrepareImagesThread : private Thread
ScopedMessageBox ContentSharer::shareFilesScoped (const Array<URL>& files,
Callback callback,
Component* parent)
{
public:
PrepareImagesThread (ContentSharer& cs, const Array<Image>& imagesToUse,
ImageFileFormat* imageFileFormatToUse)
: Thread ("ContentSharer::PrepareImagesThread"),
owner (cs),
images (imagesToUse),
imageFileFormat (imageFileFormatToUse == nullptr ? new PNGImageFormat()
: imageFileFormatToUse),
extension (imageFileFormat->getFormatName().toLowerCase())
{
startThread();
}
~PrepareImagesThread() override
{
signalThreadShouldExit();
waitForThreadToExit (10000);
}
private:
void run() override
{
for (const auto& image : images)
{
if (threadShouldExit())
return;
File tempFile = File::createTempFile (extension);
if (! tempFile.create().wasOk())
break;
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
if (outputStream == nullptr)
break;
if (imageFileFormat->writeImageToStream (image, *outputStream))
owner.temporaryFiles.add (tempFile);
}
finish();
}
void finish()
{
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
}
ContentSharer& owner;
const Array<Image> images;
std::unique_ptr<ImageFileFormat> imageFileFormat;
String extension;
};
//==============================================================================
class ContentSharer::PrepareDataThread : private Thread
{
public:
PrepareDataThread (ContentSharer& cs, const MemoryBlock& mb)
: Thread ("ContentSharer::PrepareDataThread"),
owner (cs),
data (mb)
{
startThread();
}
~PrepareDataThread() override
{
signalThreadShouldExit();
waitForThreadToExit (10000);
}
private:
void run() override
{
File tempFile = File::createTempFile ("data");
if (tempFile.create().wasOk())
{
if (auto outputStream = std::unique_ptr<FileOutputStream> (tempFile.createOutputStream()))
{
size_t pos = 0;
size_t totalSize = data.getSize();
while (pos < totalSize)
{
if (threadShouldExit())
return;
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
outputStream->write (data.begin() + pos, numToWrite);
pos += numToWrite;
}
owner.temporaryFiles.add (tempFile);
}
}
finish();
}
void finish()
{
MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); });
}
ContentSharer& owner;
const MemoryBlock data;
};
#endif
//==============================================================================
JUCE_IMPLEMENT_SINGLETON (ContentSharer)
ContentSharer::ContentSharer() {}
ContentSharer::~ContentSharer() { clearSingletonInstance(); }
void ContentSharer::shareFiles ([[maybe_unused]] const Array<URL>& files,
std::function<void (bool, const String&)> callbackToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
pimpl->shareFiles (files);
#else
// Content sharing is not available on this platform!
jassertfalse;
if (callbackToUse)
callbackToUse (false, "Content sharing is not available on this platform!");
#endif
auto impl = detail::ScopedContentSharerInterface::shareFiles (files, parent);
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
}
#if JUCE_CONTENT_SHARING
void ContentSharer::startNewShare (std::function<void (bool, const String&)> callbackToUse)
ScopedMessageBox ContentSharer::shareTextScoped (const String& text,
Callback callback,
Component* parent)
{
// You should not start another sharing operation before the previous one is finished.
// Forcibly stopping a previous sharing operation is rarely a good idea!
jassert (pimpl == nullptr);
pimpl.reset();
auto impl = detail::ScopedContentSharerInterface::shareText (text, parent);
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
}
prepareDataThread = nullptr;
prepareImagesThread = nullptr;
ScopedMessageBox ContentSharer::shareImagesScoped (const Array<Image>& images,
std::unique_ptr<ImageFileFormat> format,
Callback callback,
Component* parent)
{
auto impl = detail::ScopedContentSharerInterface::shareImages (images, std::move (format), parent);
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
}
deleteTemporaryFiles();
ScopedMessageBox ContentSharer::shareDataScoped (const MemoryBlock& mb,
Callback callback,
Component* parent)
{
auto impl = detail::ScopedContentSharerInterface::shareData (mb, parent);
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
}
// You need to pass a valid callback.
jassert (callbackToUse);
callback = std::move (callbackToUse);
#if ! (JUCE_CONTENT_SHARING && (JUCE_IOS || JUCE_ANDROID))
auto detail::ScopedContentSharerInterface::shareFiles (const Array<URL>&, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
{
return std::make_unique<detail::ScopedContentSharerInterface>();
}
pimpl.reset (createPimpl());
auto detail::ScopedContentSharerInterface::shareText (const String&, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
{
return std::make_unique<detail::ScopedContentSharerInterface>();
}
#endif
void ContentSharer::shareText ([[maybe_unused]] const String& text,
std::function<void (bool, const String&)> callbackToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
pimpl->shareText (text);
#else
// Content sharing is not available on this platform!
jassertfalse;
if (callbackToUse)
callbackToUse (false, "Content sharing is not available on this platform!");
#endif
}
void ContentSharer::shareImages ([[maybe_unused]] const Array<Image>& images,
std::function<void (bool, const String&)> callbackToUse,
[[maybe_unused]] ImageFileFormat* imageFileFormatToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
prepareImagesThread.reset (new PrepareImagesThread (*this, images, imageFileFormatToUse));
#else
// Content sharing is not available on this platform!
jassertfalse;
if (callbackToUse)
callbackToUse (false, "Content sharing is not available on this platform!");
#endif
}
#if JUCE_CONTENT_SHARING
void ContentSharer::filesToSharePrepared()
{
Array<URL> urls;
for (const auto& tempFile : temporaryFiles)
urls.add (URL (tempFile));
prepareImagesThread = nullptr;
prepareDataThread = nullptr;
pimpl->shareFiles (urls);
}
#endif
void ContentSharer::shareData ([[maybe_unused]] const MemoryBlock& mb,
std::function<void (bool, const String&)> callbackToUse)
{
#if JUCE_CONTENT_SHARING
startNewShare (callbackToUse);
prepareDataThread.reset (new PrepareDataThread (*this, mb));
#else
if (callbackToUse)
callbackToUse (false, "Content sharing not available on this platform!");
#endif
}
void ContentSharer::sharingFinished (bool succeeded, const String& errorDescription)
{
deleteTemporaryFiles();
std::function<void (bool, String)> cb;
std::swap (cb, callback);
String error (errorDescription);
#if JUCE_CONTENT_SHARING
pimpl.reset();
#endif
if (cb)
cb (succeeded, error);
}
void ContentSharer::deleteTemporaryFiles()
{
for (auto& f : temporaryFiles)
f.deleteFile();
temporaryFiles.clear();
}
} // namespace juce

View file

@ -23,21 +23,30 @@
==============================================================================
*/
#pragma once
namespace juce
{
/** A singleton class responsible for sharing content between apps and devices.
//==============================================================================
/**
Functions that allow sharing content between apps and devices.
You can share text, images, files or an arbitrary data block.
@tags{GUI}
*/
class JUCE_API ContentSharer : public DeletedAtShutdown
class JUCE_API ContentSharer
{
public:
JUCE_DECLARE_SINGLETON (ContentSharer, false)
ContentSharer() = delete;
/** A callback of this type is passed when starting a content sharing
session.
When the session ends, the function will receive a flag indicating
whether the session was successful. In the case of failure, the
errorText argument may hold a string describing the problem.
*/
using Callback = std::function<void (bool success, const String& errorText)>;
/** Shares the given files. Each URL should be either a full file path
or it should point to a resource within the application bundle. For
@ -50,9 +59,16 @@ public:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
@param files the files to share
@param callback a callback that will be called on the main thread
when the sharing session ends
@param parent the component that should be used to host the
sharing view
*/
void shareFiles (const Array<URL>& files,
std::function<void (bool /*success*/, const String& /*error*/)> callback);
[[nodiscard]] static ScopedMessageBox shareFilesScoped (const Array<URL>& files,
Callback callback,
Component* parent = nullptr);
/** Shares the given text.
@ -60,9 +76,16 @@ public:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
@param text the text to share
@param callback a callback that will be called on the main thread
when the sharing session ends
@param parent the component that should be used to host the
sharing view
*/
void shareText (const String& text,
std::function<void (bool /*success*/, const String& /*error*/)> callback);
[[nodiscard]] static ScopedMessageBox shareTextScoped (const String& text,
Callback callback,
Component* parent = nullptr);
/** A convenience function to share an image. This is useful when you have images
loaded in memory. The images will be written to temporary files first, so if
@ -84,10 +107,20 @@ public:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
@param images the images to share
@param format the file format to use when saving the images.
If no format is provided, a sensible default will
be used.
@param callback a callback that will be called on the main thread
when the sharing session ends
@param parent the component that should be used to host the
sharing view
*/
void shareImages (const Array<Image>& images,
std::function<void (bool /*success*/, const String& /*error*/)> callback,
ImageFileFormat* imageFileFormatToUse = nullptr);
[[nodiscard]] static ScopedMessageBox shareImagesScoped (const Array<Image>& images,
std::unique_ptr<ImageFileFormat> format,
Callback callback,
Component* parent = nullptr);
/** A convenience function to share arbitrary data. The data will be written
to a temporary file and then that file will be shared. If you have
@ -97,47 +130,16 @@ public:
Sadly on Android the returned success flag may be wrong as there is no
standard way the sharing targets report if the sharing operation
succeeded. Also, the optional error message is always empty on Android.
@param mb the data to share
@param callback a callback that will be called on the main thread
when the sharing session ends
@param parent the component that should be used to host the
sharing view
*/
void shareData (const MemoryBlock& mb,
std::function<void (bool /*success*/, const String& /*error*/)> callback);
private:
ContentSharer();
~ContentSharer();
Array<File> temporaryFiles;
std::function<void (bool, String)> callback;
#if JUCE_CONTENT_SHARING
struct Pimpl
{
virtual ~Pimpl() {}
virtual void shareFiles (const Array<URL>& files) = 0;
virtual void shareText (const String& text) = 0;
};
std::unique_ptr<Pimpl> pimpl;
Pimpl* createPimpl();
void startNewShare (std::function<void (bool, const String&)>);
class ContentSharerNativeImpl;
friend class ContentSharerNativeImpl;
class PrepareImagesThread;
friend class PrepareImagesThread;
std::unique_ptr<PrepareImagesThread> prepareImagesThread;
class PrepareDataThread;
friend class PrepareDataThread;
std::unique_ptr<PrepareDataThread> prepareDataThread;
void filesToSharePrepared();
#endif
void deleteTemporaryFiles();
void sharingFinished (bool, const String&);
[[nodiscard]] static ScopedMessageBox shareDataScoped (const MemoryBlock& mb,
Callback callback,
Component* parent = nullptr);
};
} // namespace juce

View file

@ -122,6 +122,8 @@
#include "detail/juce_ToolbarItemDragAndDropOverlayComponent.h"
#include "detail/juce_ScopedMessageBoxInterface.h"
#include "detail/juce_ScopedMessageBoxImpl.h"
#include "detail/juce_ScopedContentSharerInterface.h"
#include "detail/juce_ScopedContentSharerImpl.h"
#include "detail/juce_WindowingHelpers.h"
#include "detail/juce_AlertWindowHelpers.h"
#include "detail/juce_TopLevelWindowManager.h"
@ -143,6 +145,7 @@
#include "native/accessibility/juce_ios_Accessibility.mm"
#include "native/juce_ios_Windowing.mm"
#include "native/juce_ios_NativeMessageBox.mm"
#include "native/juce_ios_NativeModalWrapperComponent.h"
#include "native/juce_ios_FileChooser.mm"
#if JUCE_CONTENT_SHARING

View file

@ -0,0 +1,42 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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.
==============================================================================
*/
package com.rmsl.juce;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
//==============================================================================
public class Receiver extends BroadcastReceiver
{
@Override
public void onReceive (Context context, Intent intent)
{
onBroadcastResultNative (intent.getIntExtra ("com.rmsl.juce.JUCE_REQUEST_CODE", 0));
}
private native void onBroadcastResultNative (int requestCode);
}

File diff suppressed because it is too large Load diff

View file

@ -26,25 +26,58 @@
namespace juce
{
class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
private Component
class NativeScopedContentSharerInterface : public detail::ScopedContentSharerInterface,
public detail::NativeModalWrapperComponent
{
public:
ContentSharerNativeImpl (ContentSharer& cs)
: owner (cs)
NativeScopedContentSharerInterface (Component* parentIn, NSUniquePtr<NSArray> itemsIn)
: parent (parentIn), items (std::move (itemsIn)) {}
void runAsync (std::function<void (bool, const String&)> callback) override
{
static PopoverDelegateClass cls;
popoverDelegate.reset ([cls.createInstance() init]);
if ([items.get() count] == 0)
{
jassertfalse;
NullCheckedInvocation::invoke (callback, false, "No valid items found for sharing.");
return;
}
~ContentSharerNativeImpl() override
controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items.get()
applicationActivities: nil]);
controller.get().excludedActivityTypes = nil;
controller.get().completionWithItemsHandler = ^([[maybe_unused]] UIActivityType type, BOOL completed,
[[maybe_unused]] NSArray* returnedItems, NSError* error)
{
const auto errorDescription = error != nil ? nsStringToJuce ([error localizedDescription])
: String();
exitModalState (0);
NullCheckedInvocation::invoke (callback, completed && errorDescription.isEmpty(), errorDescription);
};
displayNativeWindowModally (parent);
enterModalState (true, nullptr, false);
}
void shareFiles (const Array<URL>& files) override
void close() override
{
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) files.size()];
[controller.get() dismissViewControllerAnimated: YES completion: nil];
}
private:
UIViewController* getViewController() const override { return controller.get(); }
Component* parent = nullptr;
NSUniquePtr<UIActivityViewController> controller;
NSUniquePtr<NSArray> items;
};
auto detail::ScopedContentSharerInterface::shareFiles (const Array<URL>& files, Component* parent) -> std::unique_ptr<ScopedContentSharerInterface>
{
NSUniquePtr<NSMutableArray> urls ([[NSMutableArray arrayWithCapacity: (NSUInteger) files.size()] retain]);
for (const auto& f : files)
{
@ -77,128 +110,16 @@ public:
}
if (nativeFilePath != nil)
[urls addObject: [NSURL fileURLWithPath: nativeFilePath]];
[urls.get() addObject: [NSURL fileURLWithPath: nativeFilePath]];
}
share (urls);
}
return std::make_unique<NativeScopedContentSharerInterface> (parent, std::move (urls));
}
void shareText (const String& text) override
{
auto array = [NSArray arrayWithObject: juceStringToNS (text)];
share (array);
}
private:
void share (NSArray* items)
{
if ([items count] == 0)
{
jassertfalse;
owner.sharingFinished (false, "No valid items found for sharing.");
return;
}
controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items
applicationActivities: nil]);
controller.get().excludedActivityTypes = nil;
controller.get().completionWithItemsHandler = ^([[maybe_unused]] UIActivityType type, BOOL completed,
[[maybe_unused]] NSArray* returnedItems, NSError* error)
{
succeeded = completed;
if (error != nil)
errorDescription = nsStringToJuce ([error localizedDescription]);
exitModalState (0);
};
controller.get().modalTransitionStyle = UIModalTransitionStyleCoverVertical;
auto bounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
setBounds (bounds);
setAlwaysOnTop (true);
setVisible (true);
addToDesktop (0);
enterModalState (true,
ModalCallbackFunction::create ([this] (int)
{
owner.sharingFinished (succeeded, errorDescription);
}),
false);
}
static bool isIPad()
{
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
}
//==============================================================================
void parentHierarchyChanged() override
{
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
if (peer != newPeer)
{
peer = newPeer;
if (isIPad())
{
controller.get().preferredContentSize = peer->view.frame.size;
auto screenBounds = [UIScreen mainScreen].bounds;
auto* popoverController = controller.get().popoverPresentationController;
popoverController.sourceView = peer->view;
popoverController.sourceRect = CGRectMake (0.f, screenBounds.size.height - 10.f, screenBounds.size.width, 10.f);
popoverController.canOverlapSourceViewRect = YES;
popoverController.delegate = popoverDelegate.get();
}
if (auto* parentController = peer->controller)
[parentController showViewController: controller.get() sender: parentController];
}
}
//==============================================================================
struct PopoverDelegateClass : public ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>>
{
PopoverDelegateClass() : ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>> ("PopoverDelegateClass_")
{
addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), willRepositionPopover);
registerClass();
}
//==============================================================================
static void willRepositionPopover (id, SEL, UIPopoverPresentationController*, CGRect* rect, UIView*)
{
auto screenBounds = [UIScreen mainScreen].bounds;
rect->origin.x = 0.f;
rect->origin.y = screenBounds.size.height - 10.f;
rect->size.width = screenBounds.size.width;
rect->size.height = 10.f;
}
};
ContentSharer& owner;
UIViewComponentPeer* peer = nullptr;
NSUniquePtr<UIActivityViewController> controller;
NSUniquePtr<NSObject<UIPopoverPresentationControllerDelegate>> popoverDelegate;
bool succeeded = false;
String errorDescription;
};
//==============================================================================
ContentSharer::Pimpl* ContentSharer::createPimpl()
auto detail::ScopedContentSharerInterface::shareText (const String& text, Component* parent) -> std::unique_ptr<ScopedContentSharerInterface>
{
return new ContentSharerNativeImpl (*this);
NSUniquePtr<NSArray> array ([[NSArray arrayWithObject: juceStringToNS (text)] retain]);
return std::make_unique<NativeScopedContentSharerInterface> (parent, std::move (array));
}
} // namespace juce

View file

@ -39,8 +39,9 @@ namespace juce
#define JUCE_DEPRECATION_IGNORED 1
#endif
//==============================================================================
class FileChooser::Native : public FileChooser::Pimpl,
public Component,
public detail::NativeModalWrapperComponent,
public AsyncUpdater,
public std::enable_shared_from_this<Native>
{
@ -56,11 +57,6 @@ public:
return result;
}
~Native() override
{
exitModalState (0);
}
void launch() override
{
jassert (shared_from_this() != nullptr);
@ -90,24 +86,6 @@ public:
#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);
}
}
}
void handleAsyncUpdate() override
{
pickerWasCancelled();
@ -189,6 +167,8 @@ public:
}
private:
UIViewController* getViewController() const override { return controller.get(); }
Native (FileChooser& fileChooser, int flags)
: owner (fileChooser)
{
@ -241,40 +221,9 @@ private:
[controller.get() setAllowsMultipleSelection: (flags & FileBrowserComponent::canSelectMultipleItems) != 0];
}
[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().getPrimaryDisplay()->userArea;
setBounds (chooserBounds);
setAlwaysOnTop (true);
setVisible (true);
addToDesktop (0);
}
displayNativeWindowModally (fileChooser.parent);
}
void passResultsToInitiator (Array<URL> urls)
@ -363,7 +312,6 @@ private:
FileChooser& owner;
NSUniquePtr<NSObject<UIDocumentPickerDelegate>> delegate;
NSUniquePtr<FileChooserControllerClass> controller;
UIViewComponentPeer* peer = nullptr;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)

View file

@ -0,0 +1,132 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - 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 7 End-User License
Agreement and JUCE Privacy Policy.
End User License Agreement: www.juce.com/juce-7-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::detail
{
/**
Sets up a native control to be hosted on top of a JUCE component.
*/
class NativeModalWrapperComponent : public Component
{
public:
void parentHierarchyChanged() final
{
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
if (std::exchange (peer, newPeer) == newPeer)
return;
if (peer == nullptr)
return;
if (isIPad())
{
getViewController().preferredContentSize = peer->view.frame.size;
if (auto* popoverController = getViewController().popoverPresentationController)
{
popoverController.sourceView = peer->view;
popoverController.sourceRect = CGRectMake (0.f, getHeight() - 10.f, getWidth(), 10.f);
popoverController.canOverlapSourceViewRect = YES;
popoverController.delegate = popoverDelegate.get();
}
}
if (auto* parentController = peer->controller)
[parentController showViewController: getViewController() sender: parentController];
peer->toFront (false);
}
void displayNativeWindowModally (Component* parent)
{
setOpaque (false);
if (parent != nullptr)
{
[getViewController() setModalPresentationStyle: UIModalPresentationPageSheet];
setBounds (parent->getLocalBounds());
setAlwaysOnTop (true);
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().getPrimaryDisplay()->userArea;
setBounds (chooserBounds);
setAlwaysOnTop (true);
setVisible (true);
addToDesktop (0);
}
}
private:
virtual UIViewController* getViewController() const = 0;
static bool isIPad()
{
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
}
struct PopoverDelegateClass : public ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>>
{
PopoverDelegateClass()
: ObjCClass ("PopoverDelegateClass_")
{
addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), [] (id, SEL, UIPopoverPresentationController*, CGRect* rect, UIView*)
{
auto screenBounds = [UIScreen mainScreen].bounds;
rect->origin.x = 0.f;
rect->origin.y = screenBounds.size.height - 10.f;
rect->size.width = screenBounds.size.width;
rect->size.height = 10.f;
});
registerClass();
}
};
UIViewComponentPeer* peer = nullptr;
NSUniquePtr<NSObject<UIPopoverPresentationControllerDelegate>> popoverDelegate { []
{
static PopoverDelegateClass cls;
return cls.createInstance();
}() };
};
} // namespace juce::detail

View file

@ -37,7 +37,7 @@ static juce_wchar getDefaultPasswordChar() noexcept
static int showAlertWindowUnmanaged (const MessageBoxOptions& opts, ModalComponentManager::Callback* cb)
{
return detail::ScopedMessageBoxImpl::showUnmanaged (detail::AlertWindowHelpers::create (opts), cb);
return detail::ConcreteScopedMessageBoxImpl::showUnmanaged (detail::AlertWindowHelpers::create (opts), cb);
}
//==============================================================================
@ -693,7 +693,7 @@ ScopedMessageBox AlertWindow::showScopedAsync (const MessageBoxOptions& options,
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
return NativeMessageBox::showScopedAsync (options, std::move (callback));
return detail::ScopedMessageBoxImpl::show (detail::AlertWindowHelpers::create (options), std::move (callback));
return detail::ConcreteScopedMessageBoxImpl::show (detail::AlertWindowHelpers::create (options), std::move (callback));
}
//==============================================================================

View file

@ -78,7 +78,7 @@ static int showNativeBoxUnmanaged (const MessageBoxOptions& opts,
ResultCodeMappingMode mode)
{
auto implementation = makeNativeMessageBoxWithMappedResult (opts, mode);
return detail::ScopedMessageBoxImpl::showUnmanaged (std::move (implementation), cb);
return detail::ConcreteScopedMessageBoxImpl::showUnmanaged (std::move (implementation), cb);
}
#if JUCE_MODAL_LOOPS_PERMITTED
@ -152,7 +152,7 @@ void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options
ScopedMessageBox NativeMessageBox::showScopedAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
{
auto implementation = makeNativeMessageBoxWithMappedResult (options, ResultCodeMappingMode::alertWindow);
return detail::ScopedMessageBoxImpl::show (std::move (implementation), std::move (callback));
return detail::ConcreteScopedMessageBoxImpl::show (std::move (implementation), std::move (callback));
}
} // namespace juce

View file

@ -34,6 +34,9 @@ namespace juce
class ScopedMessageBox
{
public:
/** @internal */
explicit ScopedMessageBox (std::shared_ptr<detail::ScopedMessageBoxImpl>);
/** Constructor */
ScopedMessageBox();
@ -56,9 +59,6 @@ public:
void close();
private:
friend detail::ScopedMessageBoxImpl;
explicit ScopedMessageBox (std::shared_ptr<detail::ScopedMessageBoxImpl>);
std::shared_ptr<detail::ScopedMessageBoxImpl> impl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedMessageBox)