Popup menu crashes on exit


#1

If a TextEdit is in progress with a right click PopupMenu visible and someone exits the application this causes an access violation and crash.

I first found the bug by having a right click menu on my plugin. As a workaround I tried adding the menu to it’s own window via the addToDesktop method but the menu still remains visible and fails to receive the focusLost virtual method when it has lost focus by someone clicking on the host.

Any ideas?


#2

It’s the toughest problem I’ve had with plugins so far. I think the only workaround might be some kind of special bodge that uses a normal OS modal state to block the rest of the app from getting events while the menu is there, but I tried that a while ago and hit all sorts of other stupid problems…


#3

I have sorted out the focus problem, so the following system seems to be working pretty well.

class DesktopPopupMenu;

class DesktopPopupMenuListener
{
public:
	virtual ~DesktopPopupMenuListener () {};
	virtual void menuAccepted (DesktopPopupMenu* menu, const int item) = 0;
	virtual void menuRejected (DesktopPopupMenu* menu) = 0;
};

class DesktopPopupMenu
	: public PopupMenu
	, public ComponentListener
{
public:
	DesktopPopupMenu (const tchar* name, DesktopPopupMenuListener* listener);
	virtual ~DesktopPopupMenu ();
	const String& getName () const;
	void showAt (int screenX, int screenY);
	void showAt (Component* anchor);

protected:
	void handleMenuExit ();
	virtual void componentVisibilityChanged (Component& component);

	String name;
	DesktopPopupMenuListener* listener;
	Component* menuwindow;
};


DesktopPopupMenu::DesktopPopupMenu (const tchar* name_, DesktopPopupMenuListener* listener_)
: PopupMenu ()
, name (name_)
, listener (listener_)
, menuwindow (0)
{
	jassert (listener);
}

DesktopPopupMenu::~DesktopPopupMenu ()
{
	handleMenuExit ();
}

const juce::String& DesktopPopupMenu::getName () const
{
	return name;
}

void DesktopPopupMenu::showAt (Component* anchor_)
{
	Rectangle menurect = anchor_->getBounds ();
	menurect.setPosition (anchor_->getScreenX(), anchor_->getScreenY());
	showAt (menurect.getX (), menurect.getY () + menurect.getHeight ());
}
void DesktopPopupMenu::showAt (int screenX_, int screenY_)
{
	menuwindow = createMenuComponent (screenX_, screenY_, 1, 1, 0, 0, 0, false, 0, 0, 0, 0);
	menuwindow->setWantsKeyboardFocus (true);
	menuwindow->setVisible (true);
	menuwindow->addComponentListener (this);
	menuwindow->addToDesktop (0, 0);
	menuwindow->enterModalState ();
};

void DesktopPopupMenu::handleMenuExit ()
{
	if (menuwindow)
	{
		menuwindow->removeComponentListener (this);
		int item = menuwindow->getAndRemoveModalReturnValue ();
		delete menuwindow;
		menuwindow = 0;
		if (item > 0)
		{
			listener->menuAccepted (this, item);
		}
		else
		{
			listener->menuRejected (this);
		}
	}
}

void DesktopPopupMenu::componentVisibilityChanged (Component& component_)
{
	if (!component_.isVisible ())
	{
		handleMenuExit ();
		delete this;
	}
}

#4

Below is an example usage of the DesktopPopupMenu, then menu removes itself so you just have to match whatever you called the menu in your callback, that way you can have multiple different menus doing different sets of commands through the same interface and not worry about trying to keeps the ids different:

void TheGlueEditorComponent::PopupPresetMenu ()
{
	DesktopPopupMenu* menu = new DesktopPopupMenu (T("PresetMenu"), this);
	menu->addItem (Menu_Rename, T("Rename"), true);
	menu->addSeparator ();
	menu->addItem (Menu_Load, T("Load..."), true);
	menu->addItem (Menu_Save, T("Save..."), true);
	menu->addSeparator ();
	menu->addItem (Menu_Copy, T("Copy"), true);
	menu->addItem (Menu_Paste, T("Paste"), true);
	if (buttonab && buttonab->getToggleState ())
	{
		menu->addItem (Menu_Duplicate, T("Copy B->A"), true);
	}
	else
	{
		menu->addItem (Menu_Duplicate, T("Copy A->B"), true);
	} 
	menu->addSeparator ();
	menu->addItem (Menu_About, T("About..."), true);

	menu->showAt (buttonpreset);
}

void TheGlueEditorComponent::menuAccepted (DesktopPopupMenu* menu, const int item)
{
	if (menu->getName ().equalsIgnoreCase (T("PresetMenu")))
	{
		switch (item)
		{
		case Menu_None     : break;
		case Menu_Rename   : CommandRename (); break;
		case Menu_Load     : CommandLoad (); break;
		case Menu_Save     : CommandSave (); break;
		case Menu_Copy     : CommandCopy (); break;
		case Menu_Paste    : CommandPaste (); break;
		case Menu_Duplicate: CommandDuplicate (); break;
		case Menu_About    : CommandAbout (); break;
		}
	}
}

void TheGlueEditorComponent::menuRejected (DesktopPopupMenu* menu)
{
	// do nothing
}

#5

Seems like a cunning plan!


#6

Nice, but this is only a kind of workaround for a problem that is not specific to PopupMenu’s only. When there’s a modal dialog opened, the same problem arises. So one would also need to apply it to that.


#7

this might help too:


int Component::getAndRemoveModalReturnValue ()
{
    const int modalIndex = modalComponentReturnValueKeys.indexOf (this);
    int returnValue = 0;

    if (modalIndex >= 0)
    {
        modalComponentReturnValueKeys.remove (modalIndex);
        returnValue = modalReturnValues.remove (modalIndex);
    }

    modalComponentStack.removeValue (this);

	return returnValue;
}