Really struggling for a few days - dismissing this alertWindow on Mac, what am I missing here...?

For a few days now I just can’t seem to fix the dismissing of my alertWindow in Logic Pro without it crashing. This is when I close the GUI plugin window in Logic (no other DAW), it keeps the alertWindow alive. From this point if I press Cancel it dismisses OK, but if I try Save then it crashes. I’ve tried a few things.
2024-02-2412-28-54-ezgif.com-crop

Here’s how I am currently set up.
PluginEditor.h

std::unique_ptr<juce::AlertWindow> currentAlertWindow;    //private member var

PluginEditor.cpp

void myplugin::saveUserPreset() {
   
    currentAlertWindow = std::make_unique<juce::AlertWindow>("Save Preset", "Enter preset name:", juce::AlertWindow::NoIcon);
    currentAlertWindow->addTextEditor("presetName", "", "");
    currentAlertWindow->getTextEditor("presetName")->setInputRestrictions(20);
    currentAlertWindow->addButton("Save", 0, juce::KeyPress(juce::KeyPress::returnKey));
    currentAlertWindow->addButton("Cancel", 1, juce::KeyPress(juce::KeyPress::escapeKey));

    auto callback = [this](int result) {
        if (result == 0) { // If the 'Save' button is pressed
	//just general handling of results
        }

        // Resetting and cleaning up the alert window
        currentAlertWindow->setLookAndFeel(nullptr);
        currentAlertWindow.reset();       //with out without this, it crashes
    };

    currentAlertWindow->enterModalState(true, juce::ModalCallbackFunction::create(callback), true);
}

I ran a debug in Xcode and am getting this error Thread 1: EXC_BAD_ACCESS (code=1, address=0x3a4) (which happens before I get to the Editor destructor):

`myplugin`juce::ModalComponentManager::Callback* juce::ModalCallbackFunction::create<mypluginAudioProcessorEditor::saveUserPreset()::$_12&>(mypluginAudioProcessorEditor::saveUserPreset()::$_12&)::Callable::modalStateFinished(int):
    0x172f1a5f0 <+0>:    pushq  %rbp
...etc.
    0x172f1a60a <+26>:   jne    0x172f1afb0               ; <+2496>
->  0x172f1a651 <+97>:   movslq 0x3a4(%rbx), %rax`.

Does this alertWindow need to be made into a child somehow, what am I missing? I tried giving making the currentAlertWindow a local smart pointer, but not sure that is the solution, or if I implemented it correctly.

1 Like

Here’s an example of how I do this:

auto *p_alert = new AlertWindow( "Rename " + m_p_presets->get_patch_name(), "Please enter new name", AlertWindow::AlertIconType::NoIcon );
p_alert->addTextEditor( "Patchname", preset.get_name() );

p_alert->enterModalState( true, ModalCallbackFunction::create( [ = ]( int res ) {
   if ( res ) {
   }
                    
   delete p_alert;
} ) );

I think you need to call exitModalState

1 Like

deleting the component has the same effect

Where is that indicated? The docs don’t indicate that, and while I don’t claim to fully understand the Component code, there is definately code in exitModalState which is not run in ~Component.

Ah! After reading the docs more closely, you do not have to call exitModalState if you pass true for deleteWhenDismissed in the enterModalState call.

If deleteWhenDismissed is true, then when it is dismissed, the component will be deleted and then the callback will be called. (This will safely handle the situation where the component is deleted before its exitModalState() method is called).

1 Like

This was my code before I went with the smart pointer option which is similar to yours. My issue is the destruction of the alertwindow (specifically in Logic Pro) when the alertwindow is active on screen, I click the close window button on my plugin, and it crashes the whole DAW. If I cancel/save preset the alertwindow first, then all is well - its dismissed properly. Other DAWS work fine, its just Logic Pro. I need to have some way of destroying/setting up alertwindow properly for Logic AU.
image

    juce::AlertWindow* alertWindow = new juce::AlertWindow("",  " ", juce::AlertWindow::NoIcon);

    alertWindow->addTextEditor("presetName", "", "");
    alertWindow->getTextEditor("presetName")->setInputRestrictions(20); // Limit the preset name to a maximum of 20 characters
    alertWindow->addButton("Save", 0, juce::KeyPress(juce::KeyPress::returnKey));
    alertWindow->addButton("Cancel", 1, juce::KeyPress(juce::KeyPress::escapeKey));

    auto alertLambda = [processor = &audioProcessor, alertWindow, this](int result)
    {
        auto presetExists = [processor](const juce::String& presetName)
        {
//handling here...
        alertWindow->setLookAndFeel(nullptr); // Reset the LookAndFeel
        delete alertWindow;
    };

    auto callback = juce::ModalCallbackFunction::create(alertLambda);
    alertWindow->enterModalState(true, callback, true);

I can’t say for sure, but in re-reading the docs, it seems like:

  • Passing in false for deleteWhenDismissed, you delete the component manually.
  • Passing in true for deleteWhenDismissed, you don’t delete the component manually.

So, your last code example is doing it wrong (specifying true AND deleteing manually). In my code, I use unique_ptr, because I try not to use raw pointers, I set deleteWhenDismissed to false, and I delete manually in the callback. But, this is in stand alone code, so I may not be running in the same context as the problem you are experiencing.

In further considering this, it seems like you CANNOT use a unique_ptr with deleteWhenDismissed set to true, because the Component does not have access to the unique_ptr, only the raw this pointer.

So, my first suggestion would be to use a raw pointer, setting deleteWhenDismissed to true, and not deleting manually.

I made those changes, not deleting manually. This is what happens when I close my plugin window. The alertwindow still stays active. Saving a preset crashes it from this point. Pressing cancel dismisses it Ok. Note this only happens in Logic Pro (plugin window closes, alertwindow stays active).
2024-02-2412-28-54-ezgif.com-crop

Here’s my current relevant code

    juce::AlertWindow* alertWindow = new juce::AlertWindow(""," ",Juce::AlertWindow::NoIcon);
...
    //delete alertWindow;     //not doing this anymore

    auto callback = juce::ModalCallbackFunction::create(alertLambda);
    alertWindow->enterModalState(true, callback, true);

Ok, what about going the opposite direction. Set deleteWhenDismissed to false, and manually deleting it. I would go back to using a unique_ptr in that case.

I feel like I am just throwing ideas at the wall, so I apologize if it isn’t more educated advice.

How about using AlertWindow::showAsync(...) ?

I tried the showAsync() . It still remained alive when I closed the plugin, but it didn’t crash Logic. I didn’t implement a text editor or try and fetch the preset name though, so I don’t know if that would have crashed it. Also as far as I understand, its harder to apply a lookandfeel to async dialogs(?)
Its also a little hard to create a texteditor. If I am assuming wrongly and its straighforward then let me know and I might investigate it further.

@cpr2323 appreciate the ideas, …but this had the same issue of staying alive and then crashing.

Is there a way of making the alertWindow a child so when the Editor is destroyed, the alertWindow is destroyed too, or discerning if its out of focus (adding a listener to it)?

I personally don’t use AlertWindows or any kind of windows outside the plugin window because they can easily get hidden and mislead the user. Instead I draw my own custom alert window kind of components superimposed on my plugin UI. That simplifies their destruction when the plugin UI is closed and also their communication with other events in the plugin (e.g. if you want to warn the user that a sample rate is not supported while you still have a message of “preset not found”).

1 Like

Sounds like a good idea to avoid those windows. If I could start again, I’d perhaps go that route. Ironically my plugin is 99.9% done, just for this one bug.
I tried changing the visibility when focus was lost by making a new class, but this didn’t even work. : s. Running out of ideas.

class AutoDismissAlertWindow : public juce::AlertWindow {
public:
    AutoDismissAlertWindow(const juce::String& title,
                           const juce::String& message,
                           juce::MessageBoxIconType iconType,
                           juce::Component* associatedComponent = nullptr)
        : juce::AlertWindow(title, message, iconType, associatedComponent) {}

    void focusLost(FocusChangeType cause) override {
        // Close the window when focus is lost
        setVisible(false);
    }
};

Just stop using sub-Windows, folks. No more popup menus, no more modal alert dialogs that consume valuable HWND’s, no more fussing with parent/child contexts and so on… and anyway, AUv2 and the sandbox make this UI element unworkable - they are a relic of an era when people had patience to use their newfangled mouse to find the little windows.

If you need a modal alert, super-impose it above your own base Component in your UI view, and leave the whole messy issue of submenus and floating windows and alert modality enforced by the OS UI paradigms of the 90’s, in the dust … using sliding drawers instead, design your UI with a ‘master component’ that can be easily shown/hidden/overlaid with a ‘modal’ component with something that alerts the user … use sliding drawers instead of popupmenus, etc.

(Disclaimer: I’ve been in this hell, and solved it all by eradicating all instantiations of sub-windows in my projects, including popup menus’, alert dialogs, and so on. The UX has massively improved as a result of it!)

1 Like

For your case, I’d just add a new custom “AlertComponent” which setVisible(FALSE)'s the rest of the UI components until the user presses the OK button …

Much easier than setting the rest invisible is to create a full size overlay component that paints with 0.5 alpha.

class OverlayDialog : public juce::Component
{
public:
    OverlayDialog()
    {
        addAndMakeVisible (closeButton);
    }

    void paint (juce::Graphics& g) override
    {
        g.fillall (juce::Colours::black.withAlpha (0.6f);
    }

    void resized() override
    {
        closeButton.setBounds (getLocalBounds().withSizeKeepingCentre (100, 24));
    }

    juce::TextButton close { "Close" };
};

// and use it like:
std::unique_ptr<OverlayDialog> dialog;

dialog = std::make_unique<OverlayDialog>();
dialog->closeButton.onClick = [this]{ dialog.reset(); };
dialog->setBounds (getLocalBounds());
addAndMakeVisible (dialog.get());

// optionally in resized:
if (dialog)
    dialog->setBounds (getLocalBounds());

You can ofc. make it more generic with multiple buttons, text etc.

2 Likes

@Daniel @anon48770766
This goes into the category “things I wish I’d known before I started my first plugin”. In the future I’m definitely going with a custom made dialog over the GUI.

So now though, just to stop it crashing I am checking for the Editor existing before I act on the alertwindow.

    juce::Component::SafePointer<MyPluginAudioProcessorEditor> safeEditor = this;

// in my lambda that handles the buttonpressed:
        // Check if the Editor is still active before proceeding
        if (safeEditor == nullptr)
        {
            //alertWindow->setLookAndFeel(nullptr); // Reset the LookAndFeel
            delete alertWindow; // Clean up the alert window
            return; // Exit the lambda function
        }

The only thing is, when I try and set the lookandfeel to nullptr, it crashes. And if the lookandfeel is set beforehand and code above is ran, it also crashes.

Is there a way to keep my lookandfeel and handle the destruction of this alertwindow?

crashes here:

LookAndFeel::~LookAndFeel()
{
    jassert (masterReference.getNumActiveWeakReferences() == 0
              || (masterReference.getNumActiveWeakReferences() == 1
                   && this == &getDefaultLookAndFeel()));
}

Looks like I am doing this though…

   Generally the fix for this will be to make sure you call
   Component::setLookAndFeel (nullptr)

A jassert is not a crash, it just stops the debugger to give you a heads up.

This jassert tells you, that the LookAndFeel is still used while you are destroying it. The hint is one possible issue, but not the only one possible:

A common situation is, that the top level component owns the lookAndFeel and also has the lookAndFeel set. This means that the lookAndFeel as member is destroyed before the top level component itself. It can’t be ruled out that the lookAndFeel is accessed while the component is destroyed, hence the hint to call setLookAndFeel(nullptr). But if you had set the lookAndFeel explicitly to some other component, that is still alive, it will trigger this jassert as well.

Btw. I think this condition is inversed and you are leaking the dialog:

1 Like

Yes, this is what’s happening. I have myCustomAlertDialog which is being destroyed with the Editor when the plugin window is closed, and my alertwindow’s is left dangling.

What’s a good approach in this scenario? To set myCustomAlertDialog to nullptr in the Editor destructor ? This would immediately visibly change the lookandfeel back to vanilla of my alertwindow still on screen wouldn’t it?