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:
parent
d14761c523
commit
79ed81c24a
20 changed files with 1438 additions and 1154 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
115
modules/juce_gui_basics/native/juce_android_NativeMessageBox.cpp
Normal file
115
modules/juce_gui_basics/native/juce_android_NativeMessageBox.cpp
Normal 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
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
110
modules/juce_gui_basics/native/juce_ios_NativeMessageBox.mm
Normal file
110
modules/juce_gui_basics/native/juce_ios_NativeMessageBox.mm
Normal 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
|
||||
|
|
@ -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()>)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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&)
|
||||
{
|
||||
|
|
|
|||
134
modules/juce_gui_basics/native/juce_mac_NativeMessageBox.mm
Normal file
134
modules/juce_gui_basics/native/juce_mac_NativeMessageBox.mm
Normal 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
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
346
modules/juce_gui_basics/native/juce_win32_NativeMessageBox.cpp
Normal file
346
modules/juce_gui_basics/native/juce_win32_NativeMessageBox.cpp
Normal 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
|
||||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
93
modules/juce_gui_basics/windows/juce_MessageBoxOptions.cpp
Normal file
93
modules/juce_gui_basics/windows/juce_MessageBoxOptions.cpp
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
127
modules/juce_gui_basics/windows/juce_NativeMessageBox.cpp
Normal file
127
modules/juce_gui_basics/windows/juce_NativeMessageBox.cpp
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
181
modules/juce_gui_basics/windows/juce_ScopedMessageBox.cpp
Normal file
181
modules/juce_gui_basics/windows/juce_ScopedMessageBox.cpp
Normal 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
|
||||
69
modules/juce_gui_basics/windows/juce_ScopedMessageBox.h
Normal file
69
modules/juce_gui_basics/windows/juce_ScopedMessageBox.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue