Select popupmenu with rotary encoder

I am working with a raspberry pi. I have a rotary encoder with a push button connected. I have code that reads the encoder and button fine.

But I would like to create a popup menu when the button is pressed, then turn the encoder to move around the menu the select when the button is pressed. Creating and shown the menu is easy, but how do I get control of the menu from program input from of encoder?

Since juce has a lot of abstraction layers.
You can easily mock the desired action.

Look at bool keyPressed (const KeyPress& key).
So you can ā€˜mockā€™ a keyPressed with up/down/left/right parsed from your encoder.

The problem with that idea is that popup menus work on mouse over, not click. I dont see any mouse classes that do that. Even if I did I would need to know the x/y coordinates of each selection to move the mouse around. If that is how it needs to be done it would be easier to create my own menu dialog using controls that can be selected easily.

I think what @ttg is suggesting is you send fake keypresses to the menu, as once the menu is open up/down/left/right/enter/esc will let you navigate the menus. How you achieve that with a PopupMenu is another question.

PopupMenu does also supports keyPressed (checkout the juce_PopupMenu.cpp).
It seems to be tricky to obtain the actual popup. butā€¦
You can implement in your custom LookAndFeel:
void preparePopupMenuWindow (Component&) override;
which gives you the current menu you can mock the keypress on.

1 Like

Oh, I see. Thanks, that will work.

I thought I knew how to do this but I realized that I donā€™t know how to generate keyboard input. I tried KeyPressed(key, this) but that calls my keyListener and the menu never gets it.

Someone asked about creating keyboard input programmatically and Jules answered that the only way sending a message on windows message queue. I am using linux and would also like it to work on windows.

So how do I do that for linux (and windows)?

I thought maybe I could call the menu directly with keyPressed() but that method does not exist in PopupMenu.

So what am I missing.

Iā€™ve made a VERY dirty hacky example code for you.
The worst thing in my example is that I keep a ā€˜danglingā€™ pointer to the popup. (it will crash when it is closed).

But this example works on macOS at least. and youā€™re not actually calling keyboard. you just ā€œmockā€ the keyboard callback on the PopupMenu itself.

#pragma once

#include <JuceHeader.h>

//==============================================================================
/*
    This component lives inside our window, and this is where you should put all
    your controls and content.
*/
class MainComponent  : public juce::Component
, juce::Timer
{
public:
    //==============================================================================
    MainComponent()
    {
        for (auto i = 0; i < 8; i++)
        {
            m.addItem ("Item " + juce::String (i), {});
        }
        setSize (400, 400);
        m.setLookAndFeel (&popupCatcher);
        m.showMenuAsync (juce::PopupMenu::Options());
        startTimerHz (1);
    }
    ~MainComponent() override {}

    void timerCallback() override
    {
        if (popupCatcher.lastMenuWindow != nullptr)
        {
            popupCatcher.lastMenuWindow->keyPressed (juce::KeyPress (juce::KeyPress::downKey));
        }
    }

    struct PopupCatcher : juce::LookAndFeel_V4
    {
        void preparePopupMenuWindow (juce::Component& c) override
        {
            // that's NOT cool. you should make sure you don't keep this dangling!! this is purely for quick and dirty example.
            lastMenuWindow = &c;
        }
        juce::Component* lastMenuWindow {nullptr};
    } popupCatcher;
private:
    juce::PopupMenu m;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Iā€™m trying to make this work but Iā€™m not a c++ wizard. I cannot get the struct to compile.
I inserted the struct in a dialog class that contains the popup menu. But I get compile errors (the raspberry pi compiler always points to the wrong lines on the error). I get 2 errors:

ā€¦/ā€¦/Source/ChannelDlgPM.h:139:2: error: expected class-name before ā€˜{ā€™ token

and

ā€¦/ā€¦/Source/ChannelDlgPM.h:140:15: error: ā€˜void ChannelDlgPM::PopupCatcher::preparePopupMenuWindow(juce::Component&)ā€™ marked ā€˜overrideā€™, but does not override
void preparePopupMenuWindow (juce::Component& c) override

The code:
struct PopupCatcher : juce::LookAndFeel_V4
{ <ā€” line 139
void preparePopupMenuWindow (juce::Component& c) override <ā€” line 140
{
lastMenuWindow = &c;
}
juce::Component* lastMenuWindow ;
} popupCatcher ;

Why doesnā€™t this compile???

Iā€™m on an older release of juce, and LookAndFeel_V4 doesnā€™t compile. Compiling with V3 compiles. I donā€™t know why the compiler does complain that LookAndFeel_v4 but now it compiles. Now to see if I can make it work.

ā€¦

It works.

Thanksā€¦

Now for the last question. As you noted, I have a pointer to an object that can go away without my knowing it. If someone clicks, the menu goes away. So if I rotate the encode, or press the button it will crash.

Is there a way to catch the destructor of the popup, clear the pointer, then call the destructor?

You should have some mutex when accessing it and remove reference on release.

Would a SafePointer work in this case? https://docs.juce.com/master/classComponent_1_1SafePointer.html

1 Like

Ahh, forgot about safepointer! sorry about that. Thanks @asimilon

I replaced lastPopupWIndow with nullptr when the menu is closed. I forgot that it returns 0 if the menu is closed without a selection. Then check for nullptr before using.

I should have see that right from the start.

Thanks for the help.

This has been working great but I have one issue. When I press the encoder, the menu pops up.

Using the popup catcher idea, I send right arrow to go to a submenu and return to select. This is easy when the popup menu has all submenus, and each supbmenu has only items. The popup catcher sends right arrow on first press, return on the second. But the popup catcher has no idea what menus are currently displayed. So if a menu were open that has both submenus and items, the catcher is unaware and may send the wrong keystroke.

Is there a way check what menu/item is selected when the popup catcher get the command? Alternatively is there a way to make a popup menu either select the item with a right arrow or move to the selected sub menu even if a return is pressed?

(dont see the docs how menus are supposed to respond to keyboard input)