Alt-key menu-popup broken


#1

Here is a notepad-like demo that allows you to activate menus with Alt-letter combinations. It’s Windows XP, haven’t tested recently on Linux.

With juce 1.4.6, there was, IIRC, a bug that caused the first Alt-letter press to fail but when pressing two times, it worked.

This was fixed in august or so, but now it’s gone bad again. With newer versions, it doesn’t work - a short flash is shown but no menu. It works with a version I think is from 2008-11-17.

The sample I provide here isn’t tidy but it’s reasonable short.

Compile and start on a MS Windows box, then press Alt+C or Alt+S with the focus in the text window. This should activate the menus - and does with slightly older juce trunk, but unfortunately not on the newest.

I tried to debug but I don’t understand what’s going on, it seems the modal show is cancelled by a WM_SYSCOMMAND… then, there are timers involved which makes debugging difficult.

#include <juce.h>

class MyJUCEApp
	: public juce::JUCEApplication, public juce::KeyListener
{
	class MenuWindow : public juce::MenuBarModel, public juce::DocumentWindow 
	{
	public:
		MenuWindow() : DocumentWindow("JuceMenOops", juce::Colour(200, 200, 100), 0)
		{
			setMenuBar(this);
		}
		const juce::StringArray getMenuBarNames()
		{
			juce::StringArray names;
			names.add(T("Smoking"));
			names.add(T("Cigarettes"));
			return names;
		}

		const juce::PopupMenu getMenuForIndex(int  index, const juce::String &name)
		{
			juce::PopupMenu menu;
			menu.addItem(1, "Quit");
			menu.addItem(2, "Cough");
			return menu;
		}

		void menuItemSelected(int item, int)
		{
			if (item == 1) JUCEApplication::getInstance()->quit();
		}

		void showPopupMenu(int index)
		{
			for (int i = 0; i < getNumChildComponents(); i++)
			{
				if (MenuBarComponent *menuBar = dynamic_cast<MenuBarComponent *>(getChildComponent(i)))
				{
					menuBar->showMenu(index);
					return;
				}
			}
		}

		juce_UseDebuggingNewOperator
	};
	MenuWindow *window;
	juce::TextEditor *pane;

public:
	MyJUCEApp() : window (0), pane(0) {}
	void initialise (const String& commandLine)
	{
		window = new MenuWindow;
		window->setBounds (100, 100, 400, 500);
		pane = new juce::TextEditor();
		pane->setMultiLine(true);
		pane->setText("Writing something nasty on the wall...");
		window->setContentComponent(pane);
		window->setVisible(true);
		pane->grabKeyboardFocus();
		pane->addKeyListener(this);
	}
	void shutdown()
	{
		delete window;
	}
	const String getApplicationName()
	{
		return T("JuceMenOops");
	}
	const String getApplicationVersion()
	{
		return T("1.0");
	}
	bool keyPressed(const juce::KeyPress &slam, juce::Component *)
	{
		if (slam.getModifiers().isAltDown())
		{
			int index = 0;
			switch (slam.getKeyCode())
			{
			case 'S':
				index = 0;
				break;
			case 'C':
				index = 1;
				break;
			default:
				return false;
			}
			window->showPopupMenu(index);
		}
		return false;
	}
	juce_UseDebuggingNewOperator
};

START_JUCE_APPLICATION (MyJUCEApp)

#2

I traced it to svn version 621


#3

It’s in build/win32/platform_specific_code/juce_win32_Windowing.cpp.

If I outcomment lines 2136-2137 as shown below, the problem goes away.

I don’t really understand the code (win32 dialogs and keyboard handling is such a mess) so I can’t tell if it is needed to correct some other problem with modal dialogs. Perhaps isCurrentlyBlockedByAnotherModalComponent() gets called on wrong assumptions?

[code] case WM_SYSCOMMAND:
switch (wParam & 0xfff0)
{
case SC_CLOSE:
if (sendInputAttemptWhenModalMessage())
return 0;

                        if (hasTitleBar())
                        {
                            PostMessage (h, WM_CLOSE, 0, 0);
                            return 0;
                        }
                        break;

                    case SC_KEYMENU:

//This causes the problem with ALT+letter menu selection:
// if (sendInputAttemptWhenModalMessage())
// return 0;

                        if (hasTitleBar() && h == GetCapture())
                            ReleaseCapture();

                        break;

                    case SC_MAXIMIZE:
                        if (sendInputAttemptWhenModalMessage())
                            return 0;

                        setFullScreen (true);
                        return 0;[/code]

#4

You should be returning ‘true’ from your keyDown method, to indicate that the keypress was actually used. If you return false, the event will get passed back to windows, and used for something else - in this case, windows is presumably trying to re-use it as a menu key shortcut.


#5

I changed keyDown to return true but the problem persists. I changed it to return true ony in the case where the key is recognized and the menu activated - still the same.

This is what happens on execution when Alt+S is pressed:

The keyDown calls my MenuWindow::showPopupMenu() which passes the index 0 to MenuBarComponent::showMenu(), for showing the first menu.

MenuBarComponent::showMenu calls enterModalState(false), then creates the MenuComponent and calls its enterModalState(false) and then its runModalLoop() which calls MessageManger::runDispatchLoopUntil(20) which calls juce_dispatchNextMessageOnSystemQueue ()

This will eventually proces is the WM_SYSCOMMAND generated by Windows for the keypress - I’m not the guy who designed Windows, I would have left it to the default message processing to generate it only if the keypress wasn’t handled otherwise but I wasn’t asked. If the Alt key is down, we get the WM_SYSCOMMAND regardless of whether the keypress is handled or not, and in this case it is processed before the keyPress handler even returns, which is why returning true doesn’t help.

In Win32ComponentPeer::peerWindowProc, this triggers sendInputAttemptWhenModalMessage() (this is the code change that caused my problem).

Herein, Component::isCurrentlyBlockedByAnotherModalComponent() returns true because the menu is modal, and executes the menu’s inputAttemptWhenModal()

Here, timerCallback calls dismissMenu()

There aren’t too many comments in this code so it’s not quite clear to me what is supposed to happen. I don’t know what problem or feature led to inserting of the sendInputAttemptWhenModalMessage() so I can’t tell if it is really required for the WM_SYSCOMMAND/SC_KEYMENU case. If so, would it be possible to insert a test so that this particular case is ignored?

The win32 sdk docs on WM_SYSCOMMAND/SC_KEYMENU say it’s used for selecting the window system menu but they don’t tell the full story.


#6

Ok, I see… Looking at it again, I think I only added the sendInputAttemptWhenModalMessage() call when I was adding it to the other WM_SYSCOMMAND events, and it’s probably not really necessary here. As I can’t think of any other fix for this solution, I guess I’ll remove it!