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

@ -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,
std::unique_ptr<ModalComponentManager::Callback>&& c)
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,
std::unique_ptr<ModalComponentManager::Callback>&& c)
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