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:
parent
557d690ff4
commit
9d1a6a3b28
22 changed files with 1304 additions and 1033 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue