Skip to content

Commit 79ed81c

Browse files
committed
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.
1 parent d14761c commit 79ed81c

20 files changed

+1438
-1154
lines changed

modules/juce_gui_basics/juce_gui_basics.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,10 @@ namespace juce
254254
#include "widgets/juce_Toolbar.cpp"
255255
#include "widgets/juce_ToolbarItemPalette.cpp"
256256
#include "widgets/juce_TreeView.cpp"
257+
#include "windows/juce_MessageBoxOptions.cpp"
258+
#include "windows/juce_ScopedMessageBox.cpp"
257259
#include "windows/juce_AlertWindow.cpp"
260+
#include "windows/juce_NativeMessageBox.cpp"
258261
#include "windows/juce_CallOutBox.cpp"
259262
#include "windows/juce_ComponentPeer.cpp"
260263
#include "windows/juce_DialogWindow.cpp"
@@ -293,6 +296,7 @@ namespace juce
293296
#include "native/juce_ios_UIViewComponentPeer.mm"
294297
#include "native/accessibility/juce_ios_Accessibility.mm"
295298
#include "native/juce_ios_Windowing.mm"
299+
#include "native/juce_ios_NativeMessageBox.mm"
296300
#include "native/juce_ios_FileChooser.mm"
297301

298302
#if JUCE_CONTENT_SHARING
@@ -304,6 +308,7 @@ namespace juce
304308
#include "native/juce_mac_PerScreenDisplayLinks.h"
305309
#include "native/juce_mac_NSViewComponentPeer.mm"
306310
#include "native/juce_mac_Windowing.mm"
311+
#include "native/juce_mac_NativeMessageBox.mm"
307312
#include "native/juce_mac_MainMenu.mm"
308313
#include "native/juce_mac_FileChooser.mm"
309314
#endif
@@ -319,6 +324,7 @@ namespace juce
319324
#include "native/accessibility/juce_win32_AccessibilityElement.cpp"
320325
#include "native/accessibility/juce_win32_Accessibility.cpp"
321326
#include "native/juce_win32_Windowing.cpp"
327+
#include "native/juce_win32_NativeMessageBox.cpp"
322328
#include "native/juce_win32_DragAndDrop.cpp"
323329
#include "native/juce_win32_FileChooser.cpp"
324330

@@ -330,6 +336,7 @@ namespace juce
330336

331337
#include "native/x11/juce_linux_ScopedWindowAssociation.h"
332338
#include "native/juce_linux_Windowing.cpp"
339+
#include "native/juce_linux_NativeMessageBox.cpp"
333340
#include "native/x11/juce_linux_XWindowSystem.cpp"
334341

335342
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
@@ -362,6 +369,7 @@ static jobject makeAndroidPoint (Point<int> p)
362369
#include "juce_core/files/juce_common_MimeTypes.h"
363370
#include "native/accessibility/juce_android_Accessibility.cpp"
364371
#include "native/juce_android_Windowing.cpp"
372+
#include "native/juce_android_NativeMessageBox.cpp"
365373
#include "native/juce_android_FileChooser.cpp"
366374

367375
#if JUCE_CONTENT_SHARING

modules/juce_gui_basics/juce_gui_basics.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ namespace juce
269269
#include "widgets/juce_TreeView.h"
270270
#include "windows/juce_TopLevelWindow.h"
271271
#include "windows/juce_MessageBoxOptions.h"
272+
#include "windows/juce_ScopedMessageBox.h"
272273
#include "windows/juce_AlertWindow.h"
273274
#include "windows/juce_CallOutBox.h"
274275
#include "windows/juce_ComponentPeer.h"
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
==============================================================================
3+
4+
This file is part of the JUCE library.
5+
Copyright (c) 2022 - Raw Material Software Limited
6+
7+
JUCE is an open source library subject to commercial or open-source
8+
licensing.
9+
10+
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11+
Agreement and JUCE Privacy Policy.
12+
13+
End User License Agreement: www.juce.com/juce-7-licence
14+
Privacy Policy: www.juce.com/juce-privacy-policy
15+
16+
Or: You may also use this code under the terms of the GPL v3 (see
17+
www.gnu.org/licenses).
18+
19+
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20+
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21+
DISCLAIMED.
22+
23+
==============================================================================
24+
*/
25+
26+
namespace juce
27+
{
28+
29+
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
30+
{
31+
class AndroidMessageBox : public ScopedMessageBoxInterface
32+
{
33+
public:
34+
explicit AndroidMessageBox (const MessageBoxOptions& o) : opts (o) {}
35+
36+
void runAsync (std::function<void (int)> recipient) override
37+
{
38+
const auto makeDialogListener = [&recipient] (int result)
39+
{
40+
return new DialogListener ([recipient, result] { recipient (result); });
41+
};
42+
43+
auto* env = getEnv();
44+
45+
LocalRef<jobject> builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get()));
46+
47+
const auto setText = [&] (auto method, const String& text)
48+
{
49+
builder = LocalRef<jobject> (env->CallObjectMethod (builder, method, javaString (text).get()));
50+
};
51+
52+
setText (AndroidAlertDialogBuilder.setTitle, opts.getTitle());
53+
setText (AndroidAlertDialogBuilder.setMessage, opts.getMessage());
54+
builder = LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.setCancelable, true));
55+
56+
builder = LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.setOnCancelListener,
57+
CreateJavaInterface (makeDialogListener (0),
58+
"android/content/DialogInterface$OnCancelListener").get()));
59+
60+
const auto addButton = [&] (auto method, int index)
61+
{
62+
builder = LocalRef<jobject> (env->CallObjectMethod (builder,
63+
method,
64+
javaString (opts.getButtonText (index)).get(),
65+
CreateJavaInterface (makeDialogListener (index),
66+
"android/content/DialogInterface$OnClickListener").get()));
67+
};
68+
69+
addButton (AndroidAlertDialogBuilder.setPositiveButton, 0);
70+
71+
if (opts.getButtonText (1).isNotEmpty())
72+
addButton (AndroidAlertDialogBuilder.setNegativeButton, 1);
73+
74+
if (opts.getButtonText (2).isNotEmpty())
75+
addButton (AndroidAlertDialogBuilder.setNeutralButton, 2);
76+
77+
dialog = GlobalRef (LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.create)));
78+
79+
LocalRef<jobject> window (env->CallObjectMethod (dialog, AndroidDialog.getWindow));
80+
81+
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
82+
{
83+
env->CallVoidMethod (window, AndroidWindow.setFlags, FLAG_NOT_FOCUSABLE, FLAG_NOT_FOCUSABLE);
84+
LocalRef<jobject> decorView (env->CallObjectMethod (window, AndroidWindow.getDecorView));
85+
env->CallVoidMethod (decorView, AndroidView.setSystemUiVisibility, fullScreenFlags);
86+
}
87+
88+
env->CallVoidMethod (dialog, AndroidDialog.show);
89+
90+
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
91+
env->CallVoidMethod (window, AndroidWindow.clearFlags, FLAG_NOT_FOCUSABLE);
92+
}
93+
94+
int runSync() override
95+
{
96+
// Not implemented on this platform.
97+
jassertfalse;
98+
return 0;
99+
}
100+
101+
void close() override
102+
{
103+
if (dialog != nullptr)
104+
getEnv()->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
105+
}
106+
107+
private:
108+
const MessageBoxOptions opts;
109+
GlobalRef dialog;
110+
};
111+
112+
return std::make_unique<AndroidMessageBox> (options);
113+
}
114+
115+
} // namespace juce

modules/juce_gui_basics/native/juce_android_Windowing.cpp

Lines changed: 8 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -2349,164 +2349,31 @@ DECLARE_JNI_CLASS (AndroidDialogOnClickListener, "android/content/DialogInterfac
23492349
class DialogListener : public juce::AndroidInterfaceImplementer
23502350
{
23512351
public:
2352-
DialogListener (std::shared_ptr<ModalComponentManager::Callback> callbackToUse, int resultToUse)
2353-
: callback (std::move (callbackToUse)), result (resultToUse)
2354-
{}
2352+
explicit DialogListener (std::function<void()> cb) : callback (std::move (cb)) {}
23552353

2356-
void onResult (jobject dialog)
2357-
{
2358-
auto* env = getEnv();
2359-
env->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
2360-
2361-
if (callback != nullptr)
2362-
callback->modalStateFinished (result);
2363-
2364-
callback = nullptr;
2365-
}
2366-
2367-
private:
23682354
jobject invoke (jobject proxy, jobject method, jobjectArray args) override
23692355
{
23702356
auto* env = getEnv();
23712357
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName));
23722358

23732359
if (methodName == "onCancel" || methodName == "onClick")
23742360
{
2375-
onResult (env->GetObjectArrayElement (args, 0));
2361+
auto* dialog = env->GetObjectArrayElement (args, 0);
2362+
env->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
2363+
2364+
NullCheckedInvocation::invoke (callback);
2365+
23762366
return nullptr;
23772367
}
23782368

23792369
// invoke base class
23802370
return AndroidInterfaceImplementer::invoke (proxy, method, args);
23812371
}
23822372

2383-
std::shared_ptr<ModalComponentManager::Callback> callback;
2384-
int result;
2373+
private:
2374+
std::function<void()> callback;
23852375
};
23862376

2387-
//==============================================================================
2388-
static void createAndroidDialog (const MessageBoxOptions& opts,
2389-
ModalComponentManager::Callback* callbackIn,
2390-
AlertWindowMappings::MapFn mapFn)
2391-
{
2392-
auto* env = getEnv();
2393-
2394-
LocalRef<jobject> builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get()));
2395-
2396-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setTitle, javaString (opts.getTitle()).get()));
2397-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setMessage, javaString (opts.getMessage()).get()));
2398-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setCancelable, true));
2399-
2400-
std::shared_ptr<ModalComponentManager::Callback> sharedCallback (AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
2401-
2402-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setOnCancelListener,
2403-
CreateJavaInterface (new DialogListener (sharedCallback, 0),
2404-
"android/content/DialogInterface$OnCancelListener").get()));
2405-
2406-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setPositiveButton,
2407-
javaString (opts.getButtonText (0)).get(),
2408-
CreateJavaInterface (new DialogListener (sharedCallback, 0),
2409-
"android/content/DialogInterface$OnClickListener").get()));
2410-
2411-
if (opts.getButtonText (1).isNotEmpty())
2412-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNegativeButton,
2413-
javaString (opts.getButtonText (1)).get(),
2414-
CreateJavaInterface (new DialogListener (sharedCallback, 1),
2415-
"android/content/DialogInterface$OnClickListener").get()));
2416-
2417-
if (opts.getButtonText (2).isNotEmpty())
2418-
builder = LocalRef<jobject> (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNeutralButton,
2419-
javaString (opts.getButtonText (2)).get(),
2420-
CreateJavaInterface (new DialogListener (sharedCallback, 2),
2421-
"android/content/DialogInterface$OnClickListener").get()));
2422-
2423-
LocalRef<jobject> dialog (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.create));
2424-
2425-
LocalRef<jobject> window (env->CallObjectMethod (dialog.get(), AndroidDialog.getWindow));
2426-
2427-
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
2428-
{
2429-
env->CallVoidMethod (window.get(), AndroidWindow.setFlags, FLAG_NOT_FOCUSABLE, FLAG_NOT_FOCUSABLE);
2430-
LocalRef<jobject> decorView (env->CallObjectMethod (window.get(), AndroidWindow.getDecorView));
2431-
env->CallVoidMethod (decorView.get(), AndroidView.setSystemUiVisibility, fullScreenFlags);
2432-
}
2433-
2434-
env->CallVoidMethod (dialog.get(), AndroidDialog.show);
2435-
2436-
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
2437-
env->CallVoidMethod (window.get(), AndroidWindow.clearFlags, FLAG_NOT_FOCUSABLE);
2438-
}
2439-
2440-
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/,
2441-
const String& title, const String& message,
2442-
Component* /*associatedComponent*/,
2443-
ModalComponentManager::Callback* callback)
2444-
{
2445-
createAndroidDialog (MessageBoxOptions()
2446-
.withTitle (title)
2447-
.withMessage (message)
2448-
.withButton (TRANS("OK")),
2449-
callback, AlertWindowMappings::messageBox);
2450-
}
2451-
2452-
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/,
2453-
const String& title, const String& message,
2454-
Component* /*associatedComponent*/,
2455-
ModalComponentManager::Callback* callback)
2456-
{
2457-
createAndroidDialog (MessageBoxOptions()
2458-
.withTitle (title)
2459-
.withMessage (message)
2460-
.withButton (TRANS("OK"))
2461-
.withButton (TRANS("Cancel")),
2462-
callback, AlertWindowMappings::okCancel);
2463-
2464-
return false;
2465-
}
2466-
2467-
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/,
2468-
const String& title, const String& message,
2469-
Component* /*associatedComponent*/,
2470-
ModalComponentManager::Callback* callback)
2471-
{
2472-
createAndroidDialog (MessageBoxOptions()
2473-
.withTitle (title)
2474-
.withMessage (message)
2475-
.withButton (TRANS("Yes"))
2476-
.withButton (TRANS("No"))
2477-
.withButton (TRANS("Cancel")),
2478-
callback, AlertWindowMappings::yesNoCancel);
2479-
2480-
return 0;
2481-
}
2482-
2483-
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/,
2484-
const String& title, const String& message,
2485-
Component* /*associatedComponent*/,
2486-
ModalComponentManager::Callback* callback)
2487-
{
2488-
createAndroidDialog (MessageBoxOptions()
2489-
.withTitle (title)
2490-
.withMessage (message)
2491-
.withButton (TRANS("Yes"))
2492-
.withButton (TRANS("No")),
2493-
callback, AlertWindowMappings::okCancel);
2494-
2495-
return 0;
2496-
}
2497-
2498-
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
2499-
ModalComponentManager::Callback* callback)
2500-
{
2501-
createAndroidDialog (options, callback, AlertWindowMappings::noMapping);
2502-
}
2503-
2504-
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
2505-
std::function<void (int)> callback)
2506-
{
2507-
showAsync (options, ModalCallbackFunction::create (callback));
2508-
}
2509-
25102377
//==============================================================================
25112378
static bool androidScreenSaverEnabled = true;
25122379

0 commit comments

Comments
 (0)