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.