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

@ -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