AlertWindow with text box async?

I’m trying to create a small window with a text box and cancel & ok buttons, and then show it asynchronously in an iOS app & plugin and I’m stumped.

AlertWindow lets you create a custom alert with the required components, but I can’t figure out a way to show it async. All the async methods don’t seem to allow a custom alert.

On a side note, it seems that all the static AlertWindow async methods are the exact same thing as the NativeMessageBox class. I’m confused.

I have exactly such an editor AlertWindow in my PluginGuiMagic, so I checked how I did it back then:

enterModalState() is available even when JUCE_MODAL_LOOPS_PERMITTED is not set.

This makes the component modal, so that messages are blocked from reaching any components other than this one and its children, but unlike runModalLoop(), this method returns immediately.

This is how it works for me:
https://github.com/ffAudio/foleys_gui_magic/blob/main/Editor/foleys_PropertiesEditor.cpp#L200

Ah! I always forget to look at the parent classes…wouldn’t have thought that Component had that.

Thanks @Daniel!

Sorry to revive this thread, but I can’t find a way to show up an AlertWindow at all on iOS 16, AUv3 version of the product. Standalone app works fine, but from inside a DAW looks like no modal dialogs are permitted. Is there any way to do that?

This just hit me as well. The AlertWindow shows up in the standalone, but not in the AUv3 when inside a host. I’m calling enterModalState().

How did you fix this?

AlertWindows are children of the main window context that is held by the application.

In the case of a standalone application, that parent/child context is made automatically by the native calls to set up the AlertWindow.

This is how events such as “Show Window/drag window/repaint/etc.” are propagated from the application main window to all of its sub-windows.

PopupMenu’s and AlertWindow’s must be linked as sub-windows to a main window - or else, they don’t handle the UI events propagated in the native window stacks properly. The reason the modal alerts are not appearing in the UI hierarchy, is because they are ‘detached’ from the main (Plugin host application) window context - by design, as it turns out.

Because in the case of AUv3 Component plugin, the main window context of the host is no longer available to the plugin process - the plugin process itself is run “sandbox’ed” from the host application.

There was some workaround or fix for rebuilding a functioning window event stack even in the context of the sandboxed AUv3 plugin, but I confess I didn’t dig deeper, as, instead, I just removed all sub-windows UI elements from our projects, resulting in a better user experience, anyway …

Thanks for the reply.

So the idea is to create an equivalent using just a child component? Which means having to add all the functionality of whatever is in the alert in that new child component?

I do this same thing. All “dialogs” are child components layered on top of the MainComponent and made to resemble a dialog. If I want it to look “modal”, I place a blocking layer over the mainComponent and draw black or white with alpha to give the background a disabled look, and to block mouse clicks.

I created a subclass of AlertWindow (and also slightly modified AlertWindow) to be able to install an AlertWindow as a child component, rather than a desktop window. So I can still use much of the functionality of AlertWindow rather than having to reinvent the wheel.

This makes sense. Do you mind me asking what that slight modification to AlertWindow was, as I’ll probably me doing the same thing? Thanks!

I misremembered - I checked and the modifications I made to AlertWindow had to do with getting various elements and text to position according to my peculiar perfectionistic tendencies, by modifying ‘AlertWindow::updateLayout()’.

The actual subclass does this in the Constructor:

KAlertWindow::KAlertWindow(AppearanceData* adIn,
                           const String& title,
                           const String& message,
                           MessageBoxIconType iconType,
                           MainComponent* comp)
: AlertWindow(title, message, iconType, comp)
, ad (adIn)
, mainComponent (comp)
{   
    // if the mainComponent is valid, we install it into the parent component
    // otherwise, it becomes a desktop window in AlertWindow constructor
    if (mainComponent != nullptr)
    {        
        // make it not a separate TopLevel Window
        removeFromDesktop();

        // rather, install it into the parent (but not visible)
        mainComponent->addChildComponent(this);
        
        setDropShadowEnabled(false);    // turn off juce's DropShadower
        
    }// else it will be a desktop window, so DropShadower is used

    setOpaque(false);   // round corners

    setLookAndFeel(&ad->mainLF);
    
    // setting a title allows the dialog to be an accessibility group/container
    setTitle("Alert Dialog:");
    setDescription("Alert Dialog:");    // replace the one in AlertWindow
    
    // allow looping tab key around components
    setFocusContainerType(FocusContainerType::keyboardFocusContainer);
}

There was actually quite a bit to this approach, I’m not sure how much to try and share here, not that I wouldn’t, but I’m not sure what’s applicable. For example, when I launch a dialog, it’s something like this (I simplified and removed extraneous stuff, so I hope it still makes sense):

    auto aw = new KAlertWindow(getAppData(),
                          string1,
                          string2,
                          AlertWindow::WarningIcon,
                          mainComponent);
    
    // buttons go in the order you want them, left to right
    aw->addButton ("OK",     1, KeyPress (KeyPress::returnKey));
    aw->addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey));
    aw->display();  // put it up, disable background

The KAlertWindow::display() function unhides the dialog and positions it (since it’s not a TopLevelWindow anymore), and does other things (including adding my dropShadower and the Blocker):

void KAlertWindow::display ()
{
    // -----------------------------------------------------------------------
    // replacement for TopLevelWindow::centreAroundComponent()
    // because desktopScale variable in AlertWindow is set to my currentScaleFactor
    // in AlertWindow constructor, and there's no way to get at it
    
    auto width = getWidth();
    auto height = getHeight();

    // we may not have a mainComponent
    // so add it to the desktop with TopLevelWindow::centreAroundComponent()
    if (mainComponent == nullptr)
    {
        centreAroundComponent(nullptr, width, height);
    }
    else
    {
        const auto scale = 1.0; // ignore scaling factor's here, my transform will take care of it

        auto targetCentre = mainComponent->localPointToGlobal (mainComponent->getLocalBounds().getCentre()) / scale;
        auto parentArea = mainComponent->getParentMonitorArea();

        if (auto* p = getParentComponent())
        {
            targetCentre = p->getLocalPoint (nullptr, targetCentre);
            parentArea   = p->getLocalBounds();
        }
        
        setBounds (Rectangle<int> (targetCentre.x - width / 2,
                                   targetCentre.y - height / 2,
                                   width, height)
                   .constrainedWithin (parentArea.reduced (12, 12)));
        // -----------------------------------------------------------------------

        // install the component that provides a disabled look on the background
        blocker.reset(new Blocker(ad, this, mainComponent));
        
        // install my drop shadow in the parent, on top of the blocker
        shadow.reset(new Shadow(ad, this, mainComponent, true)); // rounded corners
    }

    // enter modal, disable menubar, accessibility etc.
    kc->myEnterModalState(this);
}

I hope that helps. Good luck, it took me a while to get this approach ironed out.

1 Like

Hey thanks so much. And now I have my project for the weekend. :slight_smile:

Hey, can I see what your myEnterModalState() looks like?

Thanks again.

It’s basically a wrapper for enterModalState() so that I can do extra stuff, such as count the number of open modal dialogs of this sort (and be able to kill them from elsewhere), disable my menuBar, etc.

void KContainer::myEnterModalState(Component* comp)
{
    modalComponents.add(comp); // count the number of open modals
    
    // disable all menubar items, and mainComponent accessibility
    auto* mainComponent = getMainComponent();
    if (mainComponent && modalComponents.size())
        mainComponent->enableMenuBarAndAccessibility(false);
    
    comp->enterModalState(comp->isShowing());
    
    // make Visible (was added with addChildComponent()) and bring to front
    comp->setVisible(true);
    comp->toFront(true);
}

You’re probably wondering where the callback is. In my previous code, I guess I forgot to show an example of the handling of the callback (if it’s a type of dialog that requires action):

           // buttons go in the order you want them, left to right
           aw->addButton ("OK",     1, KeyPress (KeyPress::returnKey));
           aw->addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey));
           aw->callback = [safeThis = SafePointer<MainComponent>(this)] (int result)
           {
                if (safeThis)
                   DBG(String::formatted("here we are - result %d", result));
           };
           aw->display();  // put it up, disable background

The problem is, this doesn’t make sense without getting into the fact that I store the callback in the KAlertWindow class:

public:
    std::function<void(int result)> callback;

…and then provide a way to replace the lambda functions of the OK and cancel buttons so that they call a function in the KAlertWindow class and I can execute the callback. I’m sorry that I can’t post all the source for this as it’s spread out through the project, but here’s some more pieces:

Modification to juce_AlertWindow.h to allow getting the buttons:

public:
    // provide access to the buttons to replace onClick() lambdas
    OwnedArray<TextButton>* getButtons() { return &buttons; } 

Override of AlertWindow::addButton() to allow reassigning the lambda:

void KAlertWindow::addButton (const String& name,
                              int returnValue,
                              const KeyPress& shortcutKey1,
                              const KeyPress& shortcutKey2)
{
    // do the normal stuff
    AlertWindow::addButton(name, returnValue, shortcutKey1, shortcutKey2);
    
    // overwite the button onClick() lambdas to go to my function
    // get the most recent one just assigned
    if (auto b = getButtons()->getLast())
        b->onClick = [this, b] { exitKAlert (b); };
}

The function that gets called when the buttons are clicked:

//==============================================================================
void KAlertWindow::exitKAlert (Button* button)
{
    // the button onClick() lambdas are redirected here
    
    kc->myExitModalState(safeThis);
        
    // execute the callback with the result
    callback(button->getCommandID());

    // destroy the KAlertWindow
    delete safeThis;
}

myExitModalState:

void KContainer::myExitModalState(Component* comp)
{
    // store pointers to open alerts for deleting if open when quit
    // do not delete object, we are already doing it
    modalComponents.removeObject(comp, false);
    
    // reenable all menuBar items when the last open "modal" is closed, and accessibility
    auto* mainComponent = getMainComponent();
    if (mainComponent && modalComponents.size() == 0)
        mainComponent->enableMenuBarAndAccessibility(true);

    // simply exit the modal state; we already handled the callback
    comp->exitModalState();
}

I’ve removed extraneous stuff that wouldn’t make any sense, so I hope it still works. This may be just my convoluted way of doing it so it may not be useful, but I hope it is. :slight_smile:

Digging in. Thanks!