How to access rightText of parent menuItem

I have a port menu submenu of boolean menu items currently defined as follows

    menu->addChild(createSubmenuItem("Gate bits","",
      [=](Menu* menu) {
        for (unsigned char bit=1, id=1; id<=8; bit<<=1, id++){
          menu->addChild(createBoolMenuItem(std::to_string(id),"",
            [=]() { return gateBits[portId]&bit; },
            [=](bool val) {
              if (val && setCount(gateBits[portId])<4) gateBits[portId] |= bit;
              else gateBits[portId] &= ~bit;
              //
              // How do I access rightText of parent menuItem from here?
              //
            }
          ));
        }
      }
    ));

As shown in the comment, how do I access the rightText of the parent MenuItem? If the code needs to be refactored, that is fine. I’m guessing I need to first create the empty parent menuItem, and then add the submenu items, at which point I will have the pointer of the parent. But I can’t wrap my head around how to do that.

I’m thinking my strategy would be

  1. Call createSubmenuItem(...) first, but store in a local pointer variable instead of passing immediately to menu->addChild. Then turn around and call menu->addChild(pointerVar).
  2. Then in your for loop you can copy-access the outer scope local variable pointerVar into your lambda function.

I haven’t even tried to figure out how you access the right-text, but I figured that wasn’t the part getting in your way.

The cool part is you don’t really have to refactor anything. The rest of the code should remain the same.

EDIT: I realize you might actually need to declare the variable first…

ui::MenuItem* pointerVar = nullptr;
pointerVar = createSubmenuItem(...);

because the variable has to exist before you can refer to it in your lambda function. And you don’t have to worry about accessing a null pointer because pointerVar gets assigned its real value before the lambda gets called.

Yeah, that is partially what I was hung up on. I was confused between the difference between when he lambda is declare vs. when it is called. But I tried the following, and it crashes as soon as I click one of the boolean menu items.

    MenuItem* submenu = nullptr;
    submenu = createSubmenuItem("Gate bits","",
      [=](Menu* menu) {
        for (unsigned char bit=1, id=1; id<=8; bit<<=1, id++){
          menu->addChild(createBoolMenuItem(std::to_string(id),"",
            [=]() { return gateBits[portId]&bit; },
            [=](bool val) {
              if (val && setCount(gateBits[portId])<4) gateBits[portId] |= bit;
              else gateBits[portId] &= ~bit;
              submenu->rightText = "test";
            }
          ));
        }
      }
    );
    menu->addChild(submenu);

Oh, I see what is happening. Even though submenu is assigned a pointer to the correct object before your lambda is called, because we have [=] declaring the lambda, the pointer value currently held by the variable submenu is copied into the closure. And that value is null because the assignment statement hasn’t completed yet.

Here is an experiment that might get you closer to a solution. Can you try commenting out the line that causes the crash, and add two INFO log statements:

    MenuItem* submenu = nullptr;
    submenu = createSubmenuItem("Gate bits","",
      [=](Menu* menu) {
        INFO("outer lambda menu = %p", menu);
        for (unsigned char bit=1, id=1; id<=8; bit<<=1, id++){
          menu->addChild(createBoolMenuItem(std::to_string(id),"",
            [=]() { return gateBits[portId]&bit; },
            [=](bool val) {
              if (val && setCount(gateBits[portId])<4) gateBits[portId] |= bit;
              else gateBits[portId] &= ~bit;
              //submenu->rightText = "test";
            }
          ));
        }
      }
    );
    INFO("submenu = %p", submenu);
    menu->addChild(submenu);

Suddenly it occurred to me that the outer lambda parameter menu is probably what you already want. I think it will contain the same address as the variable submenu when all is said and done. Check to see whether the logged pointers are the same address. If so, you can do menu->rightText = "test".

1 Like

I have many wholes in my understanding of cpp. The wholes for lambdas and closure are both pretty deep, so I never would have been able to figure that out. So thanks!

I had a very similar idea last night. But Menu does not have a rightText attribute. I naively thought maybe I could cast Menu* to MenuItem*, but the compiler choked on that. Once I looked at the documentation it became obvious why - MenuItem is not derived from Menu, they are totally different. A MenuItem is a derivative of MenuEntry, and menu entries are stored in the Menu widget children list. The whole menu construct is slowly starting to make some sense.

Based on my current level of understanding (however flawed it may be), I think the following will give me what I want. But I am not in a location where I can test at the moment.

    menu->addChild(createSubmenuItem("Gate bits","",
      [=](Menu* menu) {
        for (unsigned char bit=1, id=1; id<=8; bit<<=1, id++){
          menu->addChild(createBoolMenuItem(std::to_string(id),"",
            [=]() { return gateBits[portId]&bit; },
            [=](bool val) {
              if (val && setCount(gateBits[portId])<4) gateBits[portId] |= bit;
              else gateBits[portId] &= ~bit;
             
              // ---- I hope this is the solution ----
              static_cast<MenuItem*>(menu->parentMenu->activeEntry)->rightText = "test";
            }
          ));
        }
      }
    ));

Ah yes, you are correct. Sigh.

OK, so maybe it’s worthwhile to step back and look at the actual goal. Maybe there’s a different way to accomplish this. I can tell you are interested in calculating some string and updating the right-text of the parent submenu. Is it possible you can pre-calculate what the text should be and just pass it into createSubmenuItem instead of passing in ""?

Yes, I will do that. But then I want to dynamically update the string as the user Ctrl-Clicks multiple submenu items. It is a non-issue if the user simply clicks and the menu immediately closes. But when selecting which bits to include in the gate logic, it makes sense to Ctrl-Click and keep it open.

I am pretty hopeful that my proposed solution above will work.

1 Like

Nope! That does not work either! :frowning:

It compiles just fine - I can cast the parentMenu->activeEntry to a MenuItem* just fine. But the moment I then try to access that pointer it crashes. Even simply testing if the pointer is NULL in an if condition causes a crash :thinking:

So at this point I am giving up on this idea. I will simply ignore the right text value

Hi Dave is this module by 4ms doing what you want ?

Huh? No, I don’t see any context menu in that module where the menu action updates the right text of the menu.

1 Like

Sorry I probably don’t know what a context menu is.Or a menu action.

Maybe subclassing MenuItem in the first place helps. I’m coding “oldschool” by not using lambdas. Maybe check out my code FlyingFaderWidget.hpp and FlyingFaderWidget.cpp: