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

ScopedMessageBox: Add new helper type to bound alert window lifetimes

The biggest new feature in this commit is the addition of
NativeMessageBox::scopedAsync and AlertWindow::scopedAsync, both of
which return an instance of ScopedMessageBox that will hide the message
box in its destructor.

The code for displaying modal dialogs on Windows has also been updated.
Now, the dialog itself is run from a new thread with its own message
loop. This means that when the dialog is dismissed, the background
thread can be joined safely. In plugins, this means that there's no
danger of the plugin view being destroyed from within the message box
runloop, for example.
This commit is contained in:
reuk 2023-02-15 22:05:10 +00:00
parent d14761c523
commit 79ed81c24a
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
20 changed files with 1438 additions and 1154 deletions

View file

@ -254,7 +254,10 @@ namespace juce
#include "widgets/juce_Toolbar.cpp"
#include "widgets/juce_ToolbarItemPalette.cpp"
#include "widgets/juce_TreeView.cpp"
#include "windows/juce_MessageBoxOptions.cpp"
#include "windows/juce_ScopedMessageBox.cpp"
#include "windows/juce_AlertWindow.cpp"
#include "windows/juce_NativeMessageBox.cpp"
#include "windows/juce_CallOutBox.cpp"
#include "windows/juce_ComponentPeer.cpp"
#include "windows/juce_DialogWindow.cpp"
@ -293,6 +296,7 @@ namespace juce
#include "native/juce_ios_UIViewComponentPeer.mm"
#include "native/accessibility/juce_ios_Accessibility.mm"
#include "native/juce_ios_Windowing.mm"
#include "native/juce_ios_NativeMessageBox.mm"
#include "native/juce_ios_FileChooser.mm"
#if JUCE_CONTENT_SHARING
@ -304,6 +308,7 @@ namespace juce
#include "native/juce_mac_PerScreenDisplayLinks.h"
#include "native/juce_mac_NSViewComponentPeer.mm"
#include "native/juce_mac_Windowing.mm"
#include "native/juce_mac_NativeMessageBox.mm"
#include "native/juce_mac_MainMenu.mm"
#include "native/juce_mac_FileChooser.mm"
#endif
@ -319,6 +324,7 @@ namespace juce
#include "native/accessibility/juce_win32_AccessibilityElement.cpp"
#include "native/accessibility/juce_win32_Accessibility.cpp"
#include "native/juce_win32_Windowing.cpp"
#include "native/juce_win32_NativeMessageBox.cpp"
#include "native/juce_win32_DragAndDrop.cpp"
#include "native/juce_win32_FileChooser.cpp"
@ -330,6 +336,7 @@ namespace juce
#include "native/x11/juce_linux_ScopedWindowAssociation.h"
#include "native/juce_linux_Windowing.cpp"
#include "native/juce_linux_NativeMessageBox.cpp"
#include "native/x11/juce_linux_XWindowSystem.cpp"
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
@ -362,6 +369,7 @@ static jobject makeAndroidPoint (Point<int> p)
#include "juce_core/files/juce_common_MimeTypes.h"
#include "native/accessibility/juce_android_Accessibility.cpp"
#include "native/juce_android_Windowing.cpp"
#include "native/juce_android_NativeMessageBox.cpp"
#include "native/juce_android_FileChooser.cpp"
#if JUCE_CONTENT_SHARING

View file

@ -269,6 +269,7 @@ namespace juce
#include "widgets/juce_TreeView.h"
#include "windows/juce_TopLevelWindow.h"
#include "windows/juce_MessageBoxOptions.h"
#include "windows/juce_ScopedMessageBox.h"
#include "windows/juce_AlertWindow.h"
#include "windows/juce_CallOutBox.h"
#include "windows/juce_ComponentPeer.h"

View file

@ -0,0 +1,115 @@
/*
==============================================================================
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
{
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
{
class AndroidMessageBox : public ScopedMessageBoxInterface
{
public:
explicit AndroidMessageBox (const MessageBoxOptions& o) : opts (o) {}
void runAsync (std::function<void (int)> recipient) override
{
const auto makeDialogListener = [&recipient] (int result)
{
return new DialogListener ([recipient, result] { recipient (result); });
};
auto* env = getEnv();
LocalRef<jobject> builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get()));
const auto setText = [&] (auto method, const String& text)
{
builder = LocalRef<jobject> (env->CallObjectMethod (builder, method, javaString (text).get()));
};
setText (AndroidAlertDialogBuilder.setTitle, opts.getTitle());
setText (AndroidAlertDialogBuilder.setMessage, opts.getMessage());
builder = LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.setCancelable, true));
builder = LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.setOnCancelListener,
CreateJavaInterface (makeDialogListener (0),
"android/content/DialogInterface$OnCancelListener").get()));
const auto addButton = [&] (auto method, int index)
{
builder = LocalRef<jobject> (env->CallObjectMethod (builder,
method,
javaString (opts.getButtonText (index)).get(),
CreateJavaInterface (makeDialogListener (index),
"android/content/DialogInterface$OnClickListener").get()));
};
addButton (AndroidAlertDialogBuilder.setPositiveButton, 0);
if (opts.getButtonText (1).isNotEmpty())
addButton (AndroidAlertDialogBuilder.setNegativeButton, 1);
if (opts.getButtonText (2).isNotEmpty())
addButton (AndroidAlertDialogBuilder.setNeutralButton, 2);
dialog = GlobalRef (LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.create)));
LocalRef<jobject> window (env->CallObjectMethod (dialog, AndroidDialog.getWindow));
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
{
env->CallVoidMethod (window, AndroidWindow.setFlags, FLAG_NOT_FOCUSABLE, FLAG_NOT_FOCUSABLE);
LocalRef<jobject> decorView (env->CallObjectMethod (window, AndroidWindow.getDecorView));
env->CallVoidMethod (decorView, AndroidView.setSystemUiVisibility, fullScreenFlags);
}
env->CallVoidMethod (dialog, AndroidDialog.show);
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
env->CallVoidMethod (window, AndroidWindow.clearFlags, FLAG_NOT_FOCUSABLE);
}
int runSync() override
{
// Not implemented on this platform.
jassertfalse;
return 0;
}
void close() override
{
if (dialog != nullptr)
getEnv()->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
}
private:
const MessageBoxOptions opts;
GlobalRef dialog;
};
return std::make_unique<AndroidMessageBox> (options);
}
} // namespace juce

View file

@ -2349,22 +2349,8 @@ DECLARE_JNI_CLASS (AndroidDialogOnClickListener, "android/content/DialogInterfac
class DialogListener : public juce::AndroidInterfaceImplementer
{
public:
DialogListener (std::shared_ptr<ModalComponentManager::Callback> callbackToUse, int resultToUse)
: callback (std::move (callbackToUse)), result (resultToUse)
{}
explicit DialogListener (std::function<void()> cb) : callback (std::move (cb)) {}
void onResult (jobject dialog)
{
auto* env = getEnv();
env->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
if (callback != nullptr)
callback->modalStateFinished (result);
callback = nullptr;
}
private:
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
{
auto* env = getEnv();
@ -2372,7 +2358,11 @@ private:
if (methodName == "onCancel" || methodName == "onClick")
{
onResult (env->GetObjectArrayElement (args, 0));
auto* dialog = env->GetObjectArrayElement (args, 0);
env->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
NullCheckedInvocation::invoke (callback);
return nullptr;
}
@ -2380,133 +2370,10 @@ private:
return AndroidInterfaceImplementer::invoke (proxy, method, args);
}
std::shared_ptr<ModalComponentManager::Callback> callback;
int result;
private:
std::function<void()> callback;
};
//==============================================================================
static void createAndroidDialog (const MessageBoxOptions& opts,
ModalComponentManager::Callback* callbackIn,
AlertWindowMappings::MapFn mapFn)
{
auto* env = getEnv();
LocalRef<jobject> builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get()));
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setTitle, javaString (opts.getTitle()).get()));
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setMessage, javaString (opts.getMessage()).get()));
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setCancelable, true));
std::shared_ptr<ModalComponentManager::Callback> sharedCallback (AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setOnCancelListener,
CreateJavaInterface (new DialogListener (sharedCallback, 0),
"android/content/DialogInterface$OnCancelListener").get()));
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setPositiveButton,
javaString (opts.getButtonText (0)).get(),
CreateJavaInterface (new DialogListener (sharedCallback, 0),
"android/content/DialogInterface$OnClickListener").get()));
if (opts.getButtonText (1).isNotEmpty())
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNegativeButton,
javaString (opts.getButtonText (1)).get(),
CreateJavaInterface (new DialogListener (sharedCallback, 1),
"android/content/DialogInterface$OnClickListener").get()));
if (opts.getButtonText (2).isNotEmpty())
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNeutralButton,
javaString (opts.getButtonText (2)).get(),
CreateJavaInterface (new DialogListener (sharedCallback, 2),
"android/content/DialogInterface$OnClickListener").get()));
LocalRef<jobject> dialog (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.create));
LocalRef<jobject> window (env->CallObjectMethod (dialog.get(), AndroidDialog.getWindow));
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
{
env->CallVoidMethod (window.get(), AndroidWindow.setFlags, FLAG_NOT_FOCUSABLE, FLAG_NOT_FOCUSABLE);
LocalRef<jobject> decorView (env->CallObjectMethod (window.get(), AndroidWindow.getDecorView));
env->CallVoidMethod (decorView.get(), AndroidView.setSystemUiVisibility, fullScreenFlags);
}
env->CallVoidMethod (dialog.get(), AndroidDialog.show);
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
env->CallVoidMethod (window.get(), AndroidWindow.clearFlags, FLAG_NOT_FOCUSABLE);
}
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
createAndroidDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK")),
callback, AlertWindowMappings::messageBox);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
createAndroidDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withButton (TRANS("Cancel")),
callback, AlertWindowMappings::okCancel);
return false;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
createAndroidDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withButton (TRANS("Cancel")),
callback, AlertWindowMappings::yesNoCancel);
return 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
createAndroidDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No")),
callback, AlertWindowMappings::okCancel);
return 0;
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
createAndroidDialog (options, callback, AlertWindowMappings::noMapping);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
//==============================================================================
static bool androidScreenSaverEnabled = true;

View file

@ -0,0 +1,110 @@
/*
==============================================================================
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
{
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
{
class MessageBox : public ScopedMessageBoxInterface
{
public:
explicit MessageBox (const MessageBoxOptions& opts) : options (opts) {}
void runAsync (std::function<void (int)> recipient) override
{
if (iOSGlobals::currentlyFocusedPeer == nullptr)
{
// Since iOS8, alert windows need to be associated with a window, so you need to
// have at least one window on screen when you use this
jassertfalse;
return;
}
alert.reset ([[UIAlertController alertControllerWithTitle: juceStringToNS (options.getTitle())
message: juceStringToNS (options.getMessage())
preferredStyle: UIAlertControllerStyleAlert] retain]);
for (auto i = 0; i < options.getNumButtons(); ++i)
{
const auto text = options.getButtonText (i);
if (text.isEmpty())
continue;
auto* action = [UIAlertAction actionWithTitle: juceStringToNS (text)
style: UIAlertActionStyleDefault
handler: ^(UIAlertAction*)
{
MessageManager::callAsync ([recipient, i] { NullCheckedInvocation::invoke (recipient, i); });
}];
[alert.get() addAction: action];
if (i == 0)
[alert.get() setPreferredAction: action];
}
[iOSGlobals::currentlyFocusedPeer->controller presentViewController: alert.get()
animated: YES
completion: nil];
}
int runSync() override
{
int result = -1;
JUCE_AUTORELEASEPOOL
{
runAsync ([&result] (int r) { result = r; });
while (result < 0)
{
JUCE_AUTORELEASEPOOL
{
[[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
}
}
}
return result;
}
void close() override
{
if (auto* alertViewController = alert.get())
[alertViewController dismissViewControllerAnimated: YES completion: nil];
}
private:
const MessageBoxOptions options;
NSUniquePtr<UIAlertController> alert;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageBox)
};
return std::make_unique<MessageBox> (options);
}
} // namespace juce

View file

@ -470,197 +470,6 @@ void LookAndFeel::playAlertSound()
// TODO
}
//==============================================================================
class iOSMessageBox
{
public:
iOSMessageBox (const MessageBoxOptions& opts,
std::unique_ptr<ModalComponentManager::Callback>&& cb,
bool deleteOnCompletion)
: callback (std::move (cb)),
shouldDeleteThis (deleteOnCompletion)
{
if (iOSGlobals::currentlyFocusedPeer != nullptr)
{
UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (opts.getTitle())
message: juceStringToNS (opts.getMessage())
preferredStyle: UIAlertControllerStyleAlert];
addButton (alert, opts.getButtonText (0));
addButton (alert, opts.getButtonText (1));
addButton (alert, opts.getButtonText (2));
[iOSGlobals::currentlyFocusedPeer->controller presentViewController: alert
animated: YES
completion: nil];
}
else
{
// Since iOS8, alert windows need to be associated with a window, so you need to
// have at least one window on screen when you use this
jassertfalse;
}
}
int getResult()
{
jassert (callback == nullptr);
JUCE_AUTORELEASEPOOL
{
while (result < 0)
[[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
}
return result;
}
void buttonClicked (int buttonIndex) noexcept
{
result = buttonIndex;
if (callback != nullptr)
callback->modalStateFinished (result);
if (shouldDeleteThis)
delete this;
}
private:
void addButton (UIAlertController* alert, const String& text)
{
if (! text.isEmpty())
{
const auto index = [[alert actions] count];
[alert addAction: [UIAlertAction actionWithTitle: juceStringToNS (text)
style: UIAlertActionStyleDefault
handler: ^(UIAlertAction*) { this->buttonClicked ((int) index); }]];
}
}
int result = -1;
std::unique_ptr<ModalComponentManager::Callback> callback;
const bool shouldDeleteThis;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox)
};
//==============================================================================
static int showDialog (const MessageBoxOptions& options,
ModalComponentManager::Callback* callbackIn,
AlertWindowMappings::MapFn mapFn)
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (callbackIn == nullptr)
{
JUCE_AUTORELEASEPOOL
{
jassert (mapFn != nullptr);
iOSMessageBox messageBox (options, nullptr, false);
return mapFn (messageBox.getResult());
}
}
#endif
const auto showBox = [options, callbackIn, mapFn]
{
new iOSMessageBox (options,
AlertWindowMappings::getWrappedCallback (callbackIn, mapFn),
true);
};
if (MessageManager::getInstance()->isThisTheMessageThread())
showBox();
else
MessageManager::callAsync (showBox);
return 0;
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/)
{
showDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK")),
nullptr, AlertWindowMappings::messageBox);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
return showDialog (options, nullptr, AlertWindowMappings::noMapping);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
showDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK")),
callback, AlertWindowMappings::messageBox);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withButton (TRANS("Cancel")),
callback, AlertWindowMappings::okCancel) != 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withButton (TRANS("Cancel")),
callback, AlertWindowMappings::yesNoCancel);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/,
const String& title, const String& message,
Component* /*associatedComponent*/,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No")),
callback, AlertWindowMappings::okCancel);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
showDialog (options, callback, AlertWindowMappings::noMapping);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
//==============================================================================
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function<void()>)
{

View file

@ -0,0 +1,34 @@
/*
==============================================================================
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
{
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
{
return createAlertWindowImpl (options);
}
} // namespace juce

View file

@ -851,132 +851,6 @@ void LookAndFeel::playAlertSound()
std::cout << "\a" << std::flush;
}
//==============================================================================
static int showDialog (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback,
Async async)
{
const auto dummyCallback = [] (int) {};
switch (options.getNumButtons())
{
case 2:
{
if (async == Async::yes && callback == nullptr)
callback = ModalCallbackFunction::create (dummyCallback);
return AlertWindow::showOkCancelBox (options.getIconType(),
options.getTitle(),
options.getMessage(),
options.getButtonText (0),
options.getButtonText (1),
options.getAssociatedComponent(),
callback) ? 1 : 0;
}
case 3:
{
if (async == Async::yes && callback == nullptr)
callback = ModalCallbackFunction::create (dummyCallback);
return AlertWindow::showYesNoCancelBox (options.getIconType(),
options.getTitle(),
options.getMessage(),
options.getButtonText (0),
options.getButtonText (1),
options.getButtonText (2),
options.getAssociatedComponent(),
callback);
}
case 1:
default:
break;
}
#if JUCE_MODAL_LOOPS_PERMITTED
if (async == Async::no)
{
AlertWindow::showMessageBox (options.getIconType(),
options.getTitle(),
options.getMessage(),
options.getButtonText (0),
options.getAssociatedComponent());
}
else
#endif
{
AlertWindow::showMessageBoxAsync (options.getIconType(),
options.getTitle(),
options.getMessage(),
options.getButtonText (0),
options.getAssociatedComponent(),
callback);
}
return 0;
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* /*associatedComponent*/)
{
AlertWindow::showMessageBox (iconType, title, message);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
return showDialog (options, nullptr, Async::no);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
AlertWindow::showMessageBoxAsync (iconType, title, message, {}, associatedComponent, callback);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return AlertWindow::showOkCancelBox (iconType, title, message, {}, {}, associatedComponent, callback);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return AlertWindow::showYesNoCancelBox (iconType, title, message, {}, {}, {},
associatedComponent, callback);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return AlertWindow::showOkCancelBox (iconType, title, message, TRANS("Yes"), TRANS("No"),
associatedComponent, callback);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
showDialog (options, callback, Async::yes);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
//==============================================================================
Image juce_createIconForFile (const File&)
{

View file

@ -0,0 +1,134 @@
/*
==============================================================================
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
{
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
{
class OSXMessageBox : public ScopedMessageBoxInterface
{
public:
explicit OSXMessageBox (const MessageBoxOptions& opts)
: options (opts) {}
void runAsync (std::function<void (int)> recipient) override
{
makeAlert();
const auto onDone = [recipient] (NSModalResponse result)
{
recipient (convertResult (result));
};
if (auto* comp = options.getAssociatedComponent())
{
if (auto* peer = comp->getPeer())
{
if (auto* view = static_cast<NSView*> (peer->getNativeHandle()))
{
if (auto* window = [view window])
{
if (@available (macOS 10.9, *))
{
[alertWindow.get() beginSheetModalForWindow: window completionHandler: ^(NSModalResponse result)
{
onDone (result);
}];
return;
}
}
}
}
}
const auto result = [alertWindow.get() runModal];
onDone (result);
}
int runSync() override
{
makeAlert();
return convertResult ([alertWindow.get() runModal]);
}
void close() override
{
if (auto* alert = alertWindow.get())
[[alert window] close];
}
private:
static int convertResult (NSModalResponse response)
{
switch (response)
{
case NSAlertFirstButtonReturn: return 0;
case NSAlertSecondButtonReturn: return 1;
case NSAlertThirdButtonReturn: return 2;
default: break;
}
jassertfalse;
return 0;
}
static void addButton (NSAlert* alert, const String& button)
{
if (! button.isEmpty())
[alert addButtonWithTitle: juceStringToNS (button)];
}
void makeAlert()
{
NSAlert* alert = [[NSAlert alloc] init];
[alert setMessageText: juceStringToNS (options.getTitle())];
[alert setInformativeText: juceStringToNS (options.getMessage())];
[alert setAlertStyle: options.getIconType() == MessageBoxIconType::WarningIcon ? NSAlertStyleCritical
: NSAlertStyleInformational];
const auto button1Text = options.getButtonText (0);
addButton (alert, button1Text.isEmpty() ? "OK" : button1Text);
addButton (alert, options.getButtonText (1));
addButton (alert, options.getButtonText (2));
alertWindow.reset (alert);
}
NSUniquePtr<NSAlert> alertWindow;
MessageBoxOptions options;
std::unique_ptr<ModalComponentManager::Callback> callback;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXMessageBox)
};
return std::make_unique<OSXMessageBox> (options);
}
} // namespace juce

View file

@ -31,219 +31,6 @@ void LookAndFeel::playAlertSound()
NSBeep();
}
//==============================================================================
class OSXMessageBox : private AsyncUpdater
{
public:
OSXMessageBox (const MessageBoxOptions& opts,
std::unique_ptr<ModalComponentManager::Callback>&& c)
: options (opts), callback (std::move (c))
{
}
int getResult() const
{
return convertResult ([getAlert() runModal]);
}
using AsyncUpdater::triggerAsyncUpdate;
private:
static int convertResult (NSModalResponse response)
{
switch (response)
{
case NSAlertFirstButtonReturn: return 0;
case NSAlertSecondButtonReturn: return 1;
case NSAlertThirdButtonReturn: return 2;
default: break;
}
jassertfalse;
return 0;
}
void handleAsyncUpdate() override
{
if (auto* comp = options.getAssociatedComponent())
{
if (auto* peer = comp->getPeer())
{
if (auto* view = static_cast<NSView*> (peer->getNativeHandle()))
{
if (auto* window = [view window])
{
if (@available (macOS 10.9, *))
{
[getAlert() beginSheetModalForWindow: window completionHandler: ^(NSModalResponse result)
{
handleModalFinished (result);
}];
return;
}
}
}
}
}
handleModalFinished ([getAlert() runModal]);
}
void handleModalFinished (NSModalResponse result)
{
if (callback != nullptr)
callback->modalStateFinished (convertResult (result));
delete this;
}
static void addButton (NSAlert* alert, const String& button)
{
if (! button.isEmpty())
[alert addButtonWithTitle: juceStringToNS (button)];
}
NSAlert* getAlert() const
{
NSAlert* alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText: juceStringToNS (options.getTitle())];
[alert setInformativeText: juceStringToNS (options.getMessage())];
[alert setAlertStyle: options.getIconType() == MessageBoxIconType::WarningIcon ? NSAlertStyleCritical
: NSAlertStyleInformational];
const auto button1Text = options.getButtonText (0);
addButton (alert, button1Text.isEmpty() ? "OK" : button1Text);
addButton (alert, options.getButtonText (1));
addButton (alert, options.getButtonText (2));
return alert;
}
MessageBoxOptions options;
std::unique_ptr<ModalComponentManager::Callback> callback;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXMessageBox)
};
static int showDialog (const MessageBoxOptions& options,
ModalComponentManager::Callback* callbackIn,
AlertWindowMappings::MapFn mapFn)
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (callbackIn == nullptr)
{
jassert (mapFn != nullptr);
OSXMessageBox messageBox (options, nullptr);
return mapFn (messageBox.getResult());
}
#endif
auto messageBox = std::make_unique<OSXMessageBox> (options,
AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
messageBox->triggerAsyncUpdate();
messageBox.release();
return 0;
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent)
{
showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withAssociatedComponent (associatedComponent),
nullptr, AlertWindowMappings::messageBox);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
return showDialog (options, nullptr, AlertWindowMappings::noMapping);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::messageBox);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withButton (TRANS("Cancel"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::okCancel) != 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withButton (TRANS("Cancel"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::yesNoCancel);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::okCancel);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
showDialog (options, callback, AlertWindowMappings::noMapping);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
//==============================================================================
static NSRect getDragRect (NSView* view, NSEvent* event)
{

View file

@ -0,0 +1,346 @@
/*
==============================================================================
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
{
#if JUCE_MSVC
// required to enable the newer dialog box on vista and above
#pragma comment(linker, \
"\"/MANIFESTDEPENDENCY:type='Win32' " \
"name='Microsoft.Windows.Common-Controls' " \
"version='6.0.0.0' " \
"processorArchitecture='*' " \
"publicKeyToken='6595b64144ccf1df' " \
"language='*'\"" \
)
#endif
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
{
class WindowsMessageBoxBase : public ScopedMessageBoxInterface
{
public:
explicit WindowsMessageBoxBase (Component* comp)
: associatedComponent (comp) {}
void runAsync (std::function<void (int)> recipient) override
{
future = std::async (std::launch::async, [showMessageBox = getShowMessageBox(), recipient]
{
const auto initComResult = CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (initComResult != S_OK)
return;
const ScopeGuard scope { [] { CoUninitialize(); } };
const auto messageResult = showMessageBox != nullptr ? showMessageBox() : 0;
NullCheckedInvocation::invoke (recipient, messageResult);
});
}
int runSync() override
{
if (auto showMessageBox = getShowMessageBox())
return showMessageBox();
return 0;
}
void close() override
{
if (auto* toClose = windowHandle.exchange (nullptr))
EndDialog (toClose, 0);
}
void setDialogWindowHandle (HWND dialogHandle)
{
windowHandle = dialogHandle;
}
private:
std::function<int()> getShowMessageBox()
{
const auto parent = associatedComponent != nullptr ? (HWND) associatedComponent->getWindowHandle() : nullptr;
return getShowMessageBoxForParent (parent);
}
/* Returns a function that should display a message box and return the result.
getShowMessageBoxForParent() will be called on the message thread.
The returned function will be called on a separate thread, in order to avoid blocking the
message thread.
'this' is guaranteed to be alive when the returned function is called.
*/
virtual std::function<int()> getShowMessageBoxForParent (HWND parent) = 0;
Component::SafePointer<Component> associatedComponent;
std::atomic<HWND> windowHandle { nullptr };
std::future<void> future;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase)
};
class PreVistaMessageBox : public WindowsMessageBoxBase
{
public:
PreVistaMessageBox (const MessageBoxOptions& opts, UINT extraFlags)
: WindowsMessageBoxBase (opts.getAssociatedComponent()),
flags (extraFlags | getMessageBoxFlags (opts.getIconType())),
title (opts.getTitle()), message (opts.getMessage()) {}
private:
std::function<int()> getShowMessageBoxForParent (const HWND parent) override
{
JUCE_ASSERT_MESSAGE_THREAD
static std::map<DWORD, PreVistaMessageBox*> map;
static std::mutex mapMutex;
return [this, parent]
{
const auto threadId = GetCurrentThreadId();
{
const std::scoped_lock scope { mapMutex };
map.emplace (threadId, this);
}
const ScopeGuard eraseFromMap { [threadId]
{
const std::scoped_lock scope { mapMutex };
map.erase (threadId);
} };
const auto hookCallback = [] (int nCode, const WPARAM wParam, const LPARAM lParam)
{
auto* params = reinterpret_cast<CWPSTRUCT*> (lParam);
if (nCode >= 0
&& params != nullptr
&& (params->message == WM_INITDIALOG || params->message == WM_DESTROY))
{
const auto callbackThreadId = GetCurrentThreadId();
const std::scoped_lock scope { mapMutex };
if (const auto iter = map.find (callbackThreadId); iter != map.cend())
iter->second->setDialogWindowHandle (params->message == WM_INITDIALOG ? params->hwnd : nullptr);
}
return CallNextHookEx ({}, nCode, wParam, lParam);
};
const auto hook = SetWindowsHookEx (WH_CALLWNDPROC,
hookCallback,
(HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
threadId);
const ScopeGuard removeHook { [hook] { UnhookWindowsHookEx (hook); } };
const auto result = MessageBox (parent, message.toWideCharPointer(), title.toWideCharPointer(), flags);
if (result == IDYES || result == IDOK) return 0;
if (result == IDNO && ((flags & 1) != 0)) return 1;
return 2;
};
}
static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept
{
// this window can get lost behind JUCE windows which are set to be alwaysOnTop
// so if there are any set it to be topmost
const auto topmostFlag = juce_areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0;
const auto iconFlags = [&]() -> decltype (topmostFlag)
{
switch (iconType)
{
case MessageBoxIconType::QuestionIcon: return MB_ICONQUESTION;
case MessageBoxIconType::WarningIcon: return MB_ICONWARNING;
case MessageBoxIconType::InfoIcon: return MB_ICONINFORMATION;
case MessageBoxIconType::NoIcon: break;
}
return 0;
}();
return static_cast<UINT> (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags);
}
const UINT flags;
const String title, message;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox)
};
class WindowsTaskDialog : public WindowsMessageBoxBase
{
static auto getTaskDialogFunc()
{
using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*);
static const auto result = [&]() -> TaskDialogIndirectFunc
{
if (SystemStats::getOperatingSystemType() < SystemStats::WinVista)
return nullptr;
const auto comctl = "Comctl32.dll";
LoadLibraryA (comctl);
const auto comctlModule = GetModuleHandleA (comctl);
if (comctlModule != nullptr)
return (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
return nullptr;
}();
return result;
}
public:
explicit WindowsTaskDialog (const MessageBoxOptions& opts)
: WindowsMessageBoxBase (opts.getAssociatedComponent()),
iconType (opts.getIconType()),
title (opts.getTitle()), message (opts.getMessage()),
buttons { opts.getButtonText (0), opts.getButtonText (1), opts.getButtonText (2) } {}
static bool isAvailable()
{
return getTaskDialogFunc() != nullptr;
}
private:
std::function<int()> getShowMessageBoxForParent (const HWND parent) override
{
JUCE_ASSERT_MESSAGE_THREAD
return [this, parent]
{
TASKDIALOGCONFIG config{};
config.cbSize = sizeof (config);
config.hwndParent = parent;
config.pszWindowTitle = title.toWideCharPointer();
config.pszContent = message.toWideCharPointer();
config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
config.lpCallbackData = reinterpret_cast<LONG_PTR> (this);
config.pfCallback = [] (HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR lpRefData)
{
if (auto* t = reinterpret_cast<WindowsTaskDialog*> (lpRefData))
{
switch (msg)
{
case TDN_CREATED:
case TDN_DIALOG_CONSTRUCTED:
t->setDialogWindowHandle (hwnd);
break;
case TDN_DESTROYED:
t->setDialogWindowHandle (nullptr);
break;
}
}
return S_OK;
};
if (iconType == MessageBoxIconType::QuestionIcon)
{
if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
{
config.hMainIcon = questionIcon;
config.dwFlags |= TDF_USE_HICON_MAIN;
}
}
else
{
config.pszMainIcon = [&]() -> LPWSTR
{
switch (iconType)
{
case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON;
case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON;
case MessageBoxIconType::QuestionIcon: JUCE_FALLTHROUGH
case MessageBoxIconType::NoIcon:
break;
}
return nullptr;
}();
}
std::vector<TASKDIALOG_BUTTON> buttonLabels;
for (const auto& buttonText : buttons)
if (buttonText.isNotEmpty())
buttonLabels.push_back ({ (int) buttonLabels.size(), buttonText.toWideCharPointer() });
config.pButtons = buttonLabels.data();
config.cButtons = (UINT) buttonLabels.size();
int buttonIndex = 0;
if (auto* func = getTaskDialogFunc())
func (&config, &buttonIndex, nullptr, nullptr);
else
jassertfalse;
return buttonIndex;
};
}
const MessageBoxIconType iconType;
const String title, message;
const std::array<String, 3> buttons;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
};
if (WindowsTaskDialog::isAvailable())
return std::make_unique<WindowsTaskDialog> (options);
const auto extraFlags = [&options]
{
const auto numButtons = options.getNumButtons();
if (numButtons == 3)
return MB_YESNOCANCEL;
if (numButtons == 2)
return options.getButtonText (0) == "OK" ? MB_OKCANCEL
: MB_YESNO;
return MB_OK;
}();
return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags);
}
} // namespace juce

View file

@ -4804,349 +4804,6 @@ bool juce_areThereAnyAlwaysOnTopWindows()
return anyAlwaysOnTopFound;
}
//==============================================================================
#if JUCE_MSVC
// required to enable the newer dialog box on vista and above
#pragma comment(linker, \
"\"/MANIFESTDEPENDENCY:type='Win32' " \
"name='Microsoft.Windows.Common-Controls' " \
"version='6.0.0.0' " \
"processorArchitecture='*' " \
"publicKeyToken='6595b64144ccf1df' " \
"language='*'\"" \
)
#endif
class WindowsMessageBoxBase : private AsyncUpdater
{
public:
WindowsMessageBoxBase (Component* comp,
std::unique_ptr<ModalComponentManager::Callback>&& cb)
: associatedComponent (comp),
callback (std::move (cb))
{
}
virtual int getResult() = 0;
HWND getParentHWND() const
{
if (associatedComponent != nullptr)
return (HWND) associatedComponent->getWindowHandle();
return nullptr;
}
using AsyncUpdater::triggerAsyncUpdate;
private:
void handleAsyncUpdate() override
{
const auto result = getResult();
if (callback != nullptr)
callback->modalStateFinished (result);
delete this;
}
Component::SafePointer<Component> associatedComponent;
std::unique_ptr<ModalComponentManager::Callback> callback;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase)
};
class PreVistaMessageBox : public WindowsMessageBoxBase
{
public:
PreVistaMessageBox (const MessageBoxOptions& opts,
UINT extraFlags,
std::unique_ptr<ModalComponentManager::Callback>&& cb)
: WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (cb)),
flags (extraFlags | getMessageBoxFlags (opts.getIconType())),
title (opts.getTitle()), message (opts.getMessage())
{
}
int getResult() override
{
const auto result = MessageBox (getParentHWND(), message.toWideCharPointer(), title.toWideCharPointer(), flags);
if (result == IDYES || result == IDOK) return 0;
if (result == IDNO && ((flags & 1) != 0)) return 1;
return 2;
}
private:
static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept
{
// this window can get lost behind JUCE windows which are set to be alwaysOnTop
// so if there are any set it to be topmost
const auto topmostFlag = juce_areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0;
const auto iconFlags = [&]() -> decltype (topmostFlag)
{
switch (iconType)
{
case MessageBoxIconType::QuestionIcon: return MB_ICONQUESTION;
case MessageBoxIconType::WarningIcon: return MB_ICONWARNING;
case MessageBoxIconType::InfoIcon: return MB_ICONINFORMATION;
case MessageBoxIconType::NoIcon: break;
}
return 0;
}();
return static_cast<UINT> (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags);
}
const UINT flags;
const String title, message;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox)
};
using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*);
static TaskDialogIndirectFunc taskDialogIndirect = nullptr;
class WindowsTaskDialog : public WindowsMessageBoxBase
{
public:
WindowsTaskDialog (const MessageBoxOptions& opts,
std::unique_ptr<ModalComponentManager::Callback>&& cb)
: WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (cb)),
iconType (opts.getIconType()),
title (opts.getTitle()), message (opts.getMessage()),
button1 (opts.getButtonText (0)), button2 (opts.getButtonText (1)), button3 (opts.getButtonText (2))
{
}
int getResult() override
{
TASKDIALOGCONFIG config{};
config.cbSize = sizeof (config);
config.hwndParent = getParentHWND();
config.pszWindowTitle = title.toWideCharPointer();
config.pszContent = message.toWideCharPointer();
config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
if (iconType == MessageBoxIconType::QuestionIcon)
{
if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
{
config.hMainIcon = questionIcon;
config.dwFlags |= TDF_USE_HICON_MAIN;
}
}
else
{
auto icon = [this]() -> LPWSTR
{
switch (iconType)
{
case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON;
case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON;
case MessageBoxIconType::QuestionIcon: JUCE_FALLTHROUGH
case MessageBoxIconType::NoIcon:
break;
}
return nullptr;
}();
if (icon != nullptr)
config.pszMainIcon = icon;
}
std::vector<TASKDIALOG_BUTTON> buttons;
for (const auto* buttonText : { &button1, &button2, &button3 })
if (buttonText->isNotEmpty())
buttons.push_back ({ (int) buttons.size(), buttonText->toWideCharPointer() });
config.pButtons = buttons.data();
config.cButtons = (UINT) buttons.size();
int buttonIndex = 0;
taskDialogIndirect (&config, &buttonIndex, nullptr, nullptr);
return buttonIndex;
}
static bool loadTaskDialog()
{
static bool hasChecked = false;
if (! hasChecked)
{
hasChecked = true;
const auto comctl = "Comctl32.dll";
LoadLibraryA (comctl);
const auto comctlModule = GetModuleHandleA (comctl);
if (comctlModule != nullptr)
taskDialogIndirect = (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
}
return taskDialogIndirect != nullptr;
}
private:
MessageBoxIconType iconType;
String title, message, button1, button2, button3;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
};
static std::unique_ptr<WindowsMessageBoxBase> createMessageBox (const MessageBoxOptions& options,
std::unique_ptr<ModalComponentManager::Callback> callback)
{
const auto useTaskDialog =
#if JUCE_MODAL_LOOPS_PERMITTED
callback != nullptr &&
#endif
SystemStats::getOperatingSystemType() >= SystemStats::WinVista
&& WindowsTaskDialog::loadTaskDialog();
if (useTaskDialog)
return std::make_unique<WindowsTaskDialog> (options, std::move (callback));
const auto extraFlags = [&options]
{
const auto numButtons = options.getNumButtons();
if (numButtons == 3)
return MB_YESNOCANCEL;
if (numButtons == 2)
return options.getButtonText (0) == "OK" ? MB_OKCANCEL
: MB_YESNO;
return MB_OK;
}();
return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags, std::move (callback));
}
static int showDialog (const MessageBoxOptions& options,
ModalComponentManager::Callback* callbackIn,
AlertWindowMappings::MapFn mapFn)
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (callbackIn == nullptr)
{
jassert (mapFn != nullptr);
auto messageBox = createMessageBox (options, nullptr);
return mapFn (messageBox->getResult());
}
#endif
auto messageBox = createMessageBox (options,
AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
messageBox->triggerAsyncUpdate();
messageBox.release();
return 0;
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent)
{
showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withAssociatedComponent (associatedComponent),
nullptr, AlertWindowMappings::messageBox);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
return showDialog (options, nullptr, AlertWindowMappings::noMapping);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::messageBox);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withButton (TRANS("Cancel"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::okCancel) != 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withButton (TRANS("Cancel"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::yesNoCancel);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showDialog (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withAssociatedComponent (associatedComponent),
callback, AlertWindowMappings::okCancel);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
showDialog (options, callback, AlertWindowMappings::noMapping);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
//==============================================================================
bool MouseInputSource::SourceList::addSource()
{

View file

@ -35,6 +35,83 @@ static juce_wchar getDefaultPasswordChar() noexcept
#endif
}
static std::unique_ptr<ScopedMessageBoxInterface> createAlertWindowImpl (const MessageBoxOptions& opts)
{
class AlertWindowImpl : public ScopedMessageBoxInterface
{
public:
explicit AlertWindowImpl (const MessageBoxOptions& opts) : options (opts) {}
void runAsync (std::function<void (int)> recipient) override
{
if (auto* comp = setUpAlert())
comp->enterModalState (true, ModalCallbackFunction::create (std::move (recipient)), true);
else
NullCheckedInvocation::invoke (recipient, 0);
}
int runSync() override
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (auto comp = rawToUniquePtr (setUpAlert()))
return comp->runModalLoop();
#endif
jassertfalse;
return 0;
}
void close() override
{
if (alert != nullptr)
if (alert->isCurrentlyModal())
alert->exitModalState();
alert = nullptr;
}
private:
Component* setUpAlert()
{
auto* component = options.getAssociatedComponent();
auto& lf = component != nullptr ? component->getLookAndFeel()
: LookAndFeel::getDefaultLookAndFeel();
alert = lf.createAlertWindow (options.getTitle(),
options.getMessage(),
options.getButtonText (0),
options.getButtonText (1),
options.getButtonText (2),
options.getIconType(),
options.getNumButtons(),
component);
if (alert == nullptr)
{
// You have to return an alert box!
jassertfalse;
return nullptr;
}
alert->setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
return alert;
}
const MessageBoxOptions options;
Component::SafePointer<AlertWindow> alert;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindowImpl)
};
return std::make_unique<AlertWindowImpl> (opts);
}
static int showAlertWindowUnmanaged (const MessageBoxOptions& opts, ModalComponentManager::Callback* cb)
{
return ScopedMessageBox::Pimpl::showUnmanaged (createAlertWindowImpl (opts), cb);
}
//==============================================================================
AlertWindow::AlertWindow (const String& title,
const String& message,
@ -564,97 +641,7 @@ int AlertWindow::getDesktopWindowStyleFlags() const
return getLookAndFeel().getAlertBoxWindowFlags();
}
enum class Async { no, yes };
//==============================================================================
class AlertWindowInfo
{
public:
AlertWindowInfo (const MessageBoxOptions& opts,
std::unique_ptr<ModalComponentManager::Callback>&& cb,
Async showAsync)
: options (opts),
callback (std::move (cb)),
async (showAsync)
{
}
int invoke() const
{
MessageManager::getInstance()->callFunctionOnMessageThread (showCallback, (void*) this);
return returnValue;
}
private:
static void* showCallback (void* userData)
{
static_cast<AlertWindowInfo*> (userData)->show();
return nullptr;
}
void show()
{
auto* component = options.getAssociatedComponent();
auto& lf = (component != nullptr ? component->getLookAndFeel()
: LookAndFeel::getDefaultLookAndFeel());
std::unique_ptr<AlertWindow> alertBox (lf.createAlertWindow (options.getTitle(), options.getMessage(),
options.getButtonText (0), options.getButtonText (1), options.getButtonText (2),
options.getIconType(), options.getNumButtons(), component));
jassert (alertBox != nullptr); // you have to return one of these!
alertBox->setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
#if JUCE_MODAL_LOOPS_PERMITTED
if (async == Async::no)
returnValue = alertBox->runModalLoop();
else
#endif
{
ignoreUnused (async);
alertBox->enterModalState (true, callback.release(), true);
alertBox.release();
}
}
MessageBoxOptions options;
std::unique_ptr<ModalComponentManager::Callback> callback;
const Async async;
int returnValue = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindowInfo)
};
namespace AlertWindowMappings
{
using MapFn = int (*) (int);
static inline int noMapping (int buttonIndex) { return buttonIndex; }
static inline int messageBox (int) { return 0; }
static inline int okCancel (int buttonIndex) { return buttonIndex == 0 ? 1 : 0; }
static inline int yesNoCancel (int buttonIndex) { return buttonIndex == 2 ? 0 : buttonIndex + 1; }
static std::unique_ptr<ModalComponentManager::Callback> getWrappedCallback (ModalComponentManager::Callback* callbackIn,
MapFn mapFn)
{
jassert (mapFn != nullptr);
if (callbackIn == nullptr)
return nullptr;
auto wrappedCallback = [innerCallback = rawToUniquePtr (callbackIn), mapFn] (int buttonIndex)
{
innerCallback->modalStateFinished (mapFn (buttonIndex));
};
return rawToUniquePtr (ModalCallbackFunction::create (std::move (wrappedCallback)));
}
}
#if JUCE_MODAL_LOOPS_PERMITTED
void AlertWindow::showMessageBox (MessageBoxIconType iconType,
const String& title,
@ -675,8 +662,7 @@ int AlertWindow::show (const MessageBoxOptions& options)
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
return NativeMessageBox::show (options);
AlertWindowInfo info (options, nullptr, Async::no);
return info.invoke();
return showAlertWindowUnmanaged (options, nullptr);
}
bool AlertWindow::showNativeDialogBox (const String& title,
@ -694,14 +680,9 @@ bool AlertWindow::showNativeDialogBox (const String& title,
void AlertWindow::showAsync (const MessageBoxOptions& options, ModalComponentManager::Callback* callback)
{
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
{
NativeMessageBox::showAsync (options, callback);
}
else
{
AlertWindowInfo info (options, rawToUniquePtr (callback), Async::yes);
info.invoke();
}
showAlertWindowUnmanaged (options, callback);
}
void AlertWindow::showAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
@ -716,37 +697,31 @@ void AlertWindow::showMessageBoxAsync (MessageBoxIconType iconType,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
showAsync (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (buttonText.isEmpty() ? TRANS("OK") : buttonText)
.withAssociatedComponent (associatedComponent),
callback);
auto options = MessageBoxOptions::makeOptionsOk (iconType,
title,
message,
buttonText,
associatedComponent);
showAsync (options, callback);
}
static int showMaybeAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callbackIn,
AlertWindowMappings::MapFn mapFn)
ModalComponentManager::Callback* callbackIn)
{
const auto showAsync = (callbackIn != nullptr ? Async::yes
: Async::no);
auto callback = AlertWindowMappings::getWrappedCallback (callbackIn, mapFn);
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (showAsync == Async::no)
return mapFn (NativeMessageBox::show (options));
if (callbackIn == nullptr)
return NativeMessageBox::show (options);
#endif
NativeMessageBox::showAsync (options, callback.release());
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
NativeMessageBox::showAsync (options, callbackIn);
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
return false;
}
AlertWindowInfo info (options, std::move (callback), showAsync);
return info.invoke();
return showAlertWindowUnmanaged (options, callbackIn);
}
bool AlertWindow::showOkCancelBox (MessageBoxIconType iconType,
@ -757,17 +732,13 @@ bool AlertWindow::showOkCancelBox (MessageBoxIconType iconType,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showMaybeAsync (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS("OK") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS("Cancel") : button2Text)
.withAssociatedComponent (associatedComponent),
callback,
LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows()
? AlertWindowMappings::okCancel
: AlertWindowMappings::noMapping) == 1;
auto options = MessageBoxOptions::makeOptionsOkCancel (iconType,
title,
message,
button1Text,
button2Text,
associatedComponent);
return showMaybeAsync (options, callback) == 1;
}
int AlertWindow::showYesNoCancelBox (MessageBoxIconType iconType,
@ -779,18 +750,22 @@ int AlertWindow::showYesNoCancelBox (MessageBoxIconType iconType,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showMaybeAsync (MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS("Yes") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS("No") : button2Text)
.withButton (button3Text.isEmpty() ? TRANS("Cancel") : button3Text)
.withAssociatedComponent (associatedComponent),
callback,
LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows()
? AlertWindowMappings::yesNoCancel
: AlertWindowMappings::noMapping);
auto options = MessageBoxOptions::makeOptionsYesNoCancel (iconType,
title,
message,
button1Text,
button2Text,
button3Text,
associatedComponent);
return showMaybeAsync (options, callback);
}
ScopedMessageBox AlertWindow::showScopedAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
{
if (LookAndFeel::getDefaultLookAndFeel().isUsingNativeAlertWindows())
return NativeMessageBox::showScopedAsync (options, std::move (callback));
return ScopedMessageBox::Pimpl::show (createAlertWindowImpl (options), std::move (callback));
}
//==============================================================================

View file

@ -413,6 +413,39 @@ public:
ModalComponentManager::Callback* callback);
#endif
/** Shows an alert window using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
This function is always asynchronous, even if the callback is null.
The result codes returned by the alert window are as follows.
- One button:
- button[0] returns 0
- Two buttons:
- button[0] returns 1
- button[1] returns 0
- Three buttons:
- button[0] returns 1
- button[1] returns 2
- button[2] returns 0
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed with the index of the
button that was clicked as its argument.
The callback object will be owned and deleted by the system, so make sure
that it works safely and doesn't keep any references to objects that might
be deleted before it gets called.
@returns a ScopedMessageBox instance. The message box will remain visible for no
longer than the ScopedMessageBox remains alive.
@see MessageBoxOptions
*/
[[nodiscard]] static ScopedMessageBox showScopedAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
//==============================================================================
#if JUCE_MODAL_LOOPS_PERMITTED && ! defined (DOXYGEN)
/** Shows an operating-system native dialog box.

View file

@ -0,0 +1,93 @@
/*
==============================================================================
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
{
MessageBoxOptions MessageBoxOptions::makeOptionsOk (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (buttonText.isEmpty() ? TRANS ("OK") : buttonText)
.withAssociatedComponent (associatedComponent);
}
MessageBoxOptions MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS ("OK") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS ("Cancel") : button2Text)
.withAssociatedComponent (associatedComponent);
}
MessageBoxOptions MessageBoxOptions::makeOptionsYesNo (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS ("Yes") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS ("No") : button2Text)
.withAssociatedComponent (associatedComponent);
}
MessageBoxOptions MessageBoxOptions::makeOptionsYesNoCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text,
const String& button2Text,
const String& button3Text,
Component* associatedComponent)
{
return MessageBoxOptions()
.withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (button1Text.isEmpty() ? TRANS ("Yes") : button1Text)
.withButton (button2Text.isEmpty() ? TRANS ("No") : button2Text)
.withButton (button3Text.isEmpty() ? TRANS ("Cancel") : button3Text)
.withAssociatedComponent (associatedComponent);
}
} // namespace juce

View file

@ -124,6 +124,50 @@ public:
*/
Component* getAssociatedComponent() const noexcept { return associatedComponent; }
/** Creates options suitable for a message box with a single button.
If no button text is supplied, "OK" will be used.
*/
static MessageBoxOptions makeOptionsOk (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& buttonText = String(),
Component* associatedComponent = nullptr);
/** Creates options suitable for a message box with two buttons.
If no button text is supplied, "OK" and "Cancel" will be used.
*/
static MessageBoxOptions makeOptionsOkCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text = String(),
const String& button2Text = String(),
Component* associatedComponent = nullptr);
/** Creates options suitable for a message box with two buttons.
If no button text is supplied, "Yes" and "No" will be used.
*/
static MessageBoxOptions makeOptionsYesNo (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text = String(),
const String& button2Text = String(),
Component* associatedComponent = nullptr);
/** Creates options suitable for a message box with three buttons.
*
If no button text is supplied, "Yes", "No", and "Cancel" will be used.
*/
static MessageBoxOptions makeOptionsYesNoCancel (MessageBoxIconType iconType,
const String& title,
const String& message,
const String& button1Text = String(),
const String& button2Text = String(),
const String& button3Text = String(),
Component* associatedComponent = nullptr);
private:
//==============================================================================
template <typename Member, typename Item>

View file

@ -0,0 +1,127 @@
/*
==============================================================================
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
{
template <ScopedMessageBox::Pimpl::MapFn mapFn = nullptr>
static int showNativeBoxUnmanaged (const MessageBoxOptions& opts, ModalComponentManager::Callback* cb)
{
return ScopedMessageBox::Pimpl::showUnmanaged<mapFn> (ScopedMessageBoxInterface::create (opts), cb);
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent)
{
showNativeBoxUnmanaged<ScopedMessageBox::Pimpl::messageBox> (MessageBoxOptions().withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withAssociatedComponent (associatedComponent),
nullptr);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
return showNativeBoxUnmanaged<> (options, nullptr);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
showNativeBoxUnmanaged<ScopedMessageBox::Pimpl::messageBox> (MessageBoxOptions().withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withAssociatedComponent (associatedComponent),
callback);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showNativeBoxUnmanaged<ScopedMessageBox::Pimpl::okCancel> (MessageBoxOptions().withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("OK"))
.withButton (TRANS("Cancel"))
.withAssociatedComponent (associatedComponent),
callback) != 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showNativeBoxUnmanaged<ScopedMessageBox::Pimpl::yesNoCancel> (MessageBoxOptions().withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withButton (TRANS("Cancel"))
.withAssociatedComponent (associatedComponent),
callback);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
const String& title, const String& message,
Component* associatedComponent,
ModalComponentManager::Callback* callback)
{
return showNativeBoxUnmanaged<ScopedMessageBox::Pimpl::okCancel> (MessageBoxOptions().withIconType (iconType)
.withTitle (title)
.withMessage (message)
.withButton (TRANS("Yes"))
.withButton (TRANS("No"))
.withAssociatedComponent (associatedComponent),
callback);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
ModalComponentManager::Callback* callback)
{
showNativeBoxUnmanaged<> (options, callback);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
std::function<void (int)> callback)
{
showAsync (options, ModalCallbackFunction::create (callback));
}
ScopedMessageBox NativeMessageBox::showScopedAsync (const MessageBoxOptions& options, std::function<void (int)> callback)
{
return ScopedMessageBox::Pimpl::show (ScopedMessageBoxInterface::create (options), std::move (callback));
}
} // namespace juce

View file

@ -257,6 +257,26 @@ public:
ModalComponentManager::Callback* callback);
#endif
/** Shows a dialog box using the specified options.
The box will be displayed and placed into a modal state, but this method will return
immediately, and the callback will be invoked later when the user dismisses the box.
@param options the options to use when creating the dialog.
@param callback if this is non-null, the callback will receive a call to its
modalStateFinished() when the box is dismissed with the index of the
button that was clicked as its argument.
The callback object will be owned and deleted by the system, so make sure
that it works safely and doesn't keep any references to objects that might
be deleted before it gets called.
@returns a ScopedMessageBox instance. The message box will remain visible for no
longer than the ScopedMessageBox remains alive.
@see MessageBoxOptions
*/
[[nodiscard]] static ScopedMessageBox showScopedAsync (const MessageBoxOptions& options,
std::function<void (int)> callback);
private:
NativeMessageBox() = delete;
JUCE_DECLARE_NON_COPYABLE (NativeMessageBox)

View file

@ -0,0 +1,181 @@
/*
==============================================================================
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
{
/*
Instances of this type can show and dismiss a message box.
This is an interface rather than a concrete type so that platforms can pick an implementation at
runtime if necessary.
*/
struct ScopedMessageBoxInterface
{
virtual ~ScopedMessageBoxInterface() = default;
/* Shows the message box.
When the message box 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 (std::function<void (int)>) = 0;
/* Shows the message box and blocks. */
virtual int runSync() = 0;
/* Forcefully closes the message box.
This will be called when the message box handle has fallen out of scope.
If the message box has already been closed by the user, this shouldn't do anything.
*/
virtual void close() = 0;
/* Implemented differently for each platform. */
static std::unique_ptr<ScopedMessageBoxInterface> create (const MessageBoxOptions& options);
};
//==============================================================================
class ScopedMessageBox::Pimpl : private AsyncUpdater
{
public:
static ScopedMessageBox show (std::unique_ptr<ScopedMessageBoxInterface>&& native, std::function<void (int)> callback)
{
return ScopedMessageBox (runAsync (std::move (native), rawToUniquePtr (ModalCallbackFunction::create (std::move (callback)))));
}
static int showUnmanaged (std::unique_ptr<ScopedMessageBoxInterface>&& native,
ModalComponentManager::Callback* cb)
{
#if JUCE_MODAL_LOOPS_PERMITTED
if (cb == nullptr)
return runSync (std::move (native));
#endif
runAsync (std::move (native), rawToUniquePtr (cb));
return 0;
}
~Pimpl() override
{
cancelPendingUpdate();
}
void close()
{
cancelPendingUpdate();
nativeImplementation->close();
self.reset();
}
private:
static std::shared_ptr<Pimpl> runAsync (std::unique_ptr<ScopedMessageBoxInterface>&& p,
std::unique_ptr<ModalComponentManager::Callback>&& c)
{
std::shared_ptr<Pimpl> result (new Pimpl (std::move (p), std::move (c)));
result->self = result;
result->triggerAsyncUpdate();
return result;
}
static int runSync (std::unique_ptr<ScopedMessageBoxInterface>&& p)
{
auto local = std::move (p);
return local != nullptr ? local->runSync() : 0;
}
explicit Pimpl (std::unique_ptr<ScopedMessageBoxInterface>&& p)
: Pimpl (std::move (p), nullptr) {}
Pimpl (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<Pimpl> (self)] (int result)
{
const auto notifyRecipient = [result, weakRecipient]
{
if (const auto locked = weakRecipient.lock())
{
if (auto* cb = locked->callback.get())
cb->modalStateFinished (result);
locked->self.reset();
}
};
if (MessageManager::getInstance()->isThisTheMessageThread())
notifyRecipient();
else
MessageManager::callAsync (notifyRecipient);
});
}
std::unique_ptr<ModalComponentManager::Callback> callback;
std::unique_ptr<ScopedMessageBoxInterface> nativeImplementation;
/* The 'old' native message box API doesn't have a concept of message box owners.
Instead, message boxes 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 message box is closed or asked to quit. To display a native
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<Pimpl> self;
};
//==============================================================================
ScopedMessageBox::ScopedMessageBox() = default;
ScopedMessageBox::ScopedMessageBox (std::shared_ptr<Pimpl> p)
: pimpl (std::move (p)) {}
ScopedMessageBox::~ScopedMessageBox() noexcept
{
close();
}
ScopedMessageBox::ScopedMessageBox (ScopedMessageBox&& other) noexcept
: pimpl (std::exchange (other.pimpl, nullptr)) {}
ScopedMessageBox& ScopedMessageBox::operator= (ScopedMessageBox&& other) noexcept
{
ScopedMessageBox temp (std::move (other));
std::swap (temp.pimpl, pimpl);
return *this;
}
void ScopedMessageBox::close()
{
if (pimpl != nullptr)
pimpl->close();
pimpl.reset();
}
} // namespace juce

View file

@ -0,0 +1,69 @@
/*
==============================================================================
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
{
/**
Objects of this type can be used to programmatically close message boxes.
@see NativeMessageBox::showScopedAsync(), AlertWindow::showScopedAsync()
*/
class ScopedMessageBox
{
public:
/** Constructor */
ScopedMessageBox();
/** Destructor */
~ScopedMessageBox() noexcept;
/** Move constructor */
ScopedMessageBox (ScopedMessageBox&&) noexcept;
/** Move assignment operator */
ScopedMessageBox& operator= (ScopedMessageBox&&) noexcept;
/** Closes the message box, if it is currently showing.
This is also called automatically during ~ScopedMessageBox. This is useful if you want
to display a message corresponding to a particular view, and hide the message automatically
when the view is hidden. This situation commonly arises when displaying messages in plugin
editors.
*/
void close();
/** @internal */
class Pimpl;
private:
explicit ScopedMessageBox (std::shared_ptr<Pimpl>);
std::shared_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedMessageBox)
};
} // namespace juce